- 投稿日:2020-05-18T16:45:23+09:00
Laradockを使った開発環境の構築方法
nginx mysqlを使用して進めます。
ローカルにファイルを作成
最終的に以下の構造になります。
app ├── app └── laradocklaradockのインストール
git clone https://github.com/Laradock/laradock.git cd laradock cp env-example .env mkdir ../applaradockをgithubからインストールし、設定ファイルをコピーします。
また、ローカルのフォルダも作成しておきます。.envを編集
APP_CODE_PATH_HOST=../appローカルで編集できるようにするためAPP_CODE_PATH_HOSTの設定を変えます。
Laradockのコンテナ起動
$ docker-compose up -d nginx mysql Recreating laradock_docker-in-docker_1 ... done Recreating laradock_mysql_1 ... done Recreating laradock_workspace_1 ... done Recreating laradock_php-fpm_1 ... done Recreating laradock_nginx_1 ... doneworkspaceコンテナのパッケージのアップデート
docker exec -it laradock_workspace_1 bashでコンテナの中に入ります。
-itオプションは以下の意味です。
-i アタッチしていなくても STDIN をオープンにし続ける(標準入力を使えるようにする)
-t 疑似ターミナル (pseudo-TTY) の割り当て(ターミナルの形で表示させる)apt-get update仮想OSをupdateします。
laravelのプロジェクトの作成
composer create-project laravel/laravel .仮想OSのプロジェクト直下にlaravelのプロジェクトを作成します。
APP_CODE_PATH_HOSTの設定を変えたため、実際は、../appに作成されます。MySQLの設定
ローカルに作成されたlaravelプロジェクト内の
.env
を編集します。DB_CONNECTION=mysql DB_HOST=laradock_mysql_1 DB_PORT=3306 DB_DATABASE=default DB_USERNAME=default DB_PASSWORD=secretlocalhostにアクセスしてみる
localhostにアクセスしてみましょう。
この画面が表示されれば、うまく行っています。
- 投稿日:2020-05-18T15:59:12+09:00
【Laravel】現在のURLからメニューをActiveにする
やりたいこと
Bootstrapでメニューに現在のURLから
active
クラスを付与したい適当にルーティング用意
web.phpRoute::get('/hoge','HogeController@index')->name('hoge'); Route::get('/fufa/{fuga_id}','FugaController@index')->name('fuga'); Route::get('/piyo/piyo','PiyoController@index')->name('piyo');メニューのサンプル
<li> <a href="{{ route('hoge') }}"> hoge </a> </li> <li class="active"> <a href="{{ route('fuga',['fuga_id' => 1]) }}"> fuga </a> </li> <li> <a href="{{ route('piyo') }}"> piyo </a> </li>やり方
1
{{ request()->route()->named('hoge') ? 'active' : '' }} {{ request()->route()->named('fuga') ? 'active' : '' }} {{ request()->route()->named('piyo') ? 'active' : '' }}現在のリクエストが指定した名前付きルートのものであるかを判定したい場合は、Routeインスタンスのnamedメソッドを使います。
これが一番良い方法
必須パラメータ除いても判定できる2
{{ reqeust()->is('hoge') ? 'active' : '' }} {{ reqeust()->is('fuga/*') ? 'active' : '' }} {{ reqeust()->is('piyo/piyo') ? 'active' : '' }}isメソッドにより、リクエストのURIが指定されたパターンに合致するかを確認できます。このメソッドでは*をワイルドカードとして使用できます。
変更に弱い。URL変えるたびにこちらも変える必要がある参考
https://quickadminpanel.com/blog/laravel-how-to-make-menu-item-active-by-urlroute/
- 投稿日:2020-05-18T15:32:15+09:00
LaravelのIlluminate\Http\Requestについてメモ
hasメソッド
if ($request->has('name')) { // } if ($request->has(['name', 'email'])) { // } if ($request->filled('name')) { // }Illuminate\Http\Requestクラスのhasメソッドは、リクエストに値が存在する場合に、trueを返します。
配列を指定した場合、hasメソッドは指定値がすべて存在するかを判定します。
値がリクエストに存在しており、かつ空でないことを判定したい場合は、filledメソッドを使います。
@if(Request::has('confirm')) @endifbladeの中で使うならこんな感じ
flashメソッド
$request->flash(); $request->flashOnly(['username', 'email']); $request->flashExcept('password');入力をフラッシュデータとして保存します。
Illuminate\Http\Requestクラスのflashメソッドは、現在の入力をセッションへ、アプリケーションに要求される次のユーザーリクエストの処理中だけ利用できるフラッシュデータとして保存します。
セッションへ入力の一部をフラッシュデータとして保存するには、flashOnlyとflashExceptが使用できます。両メソッドは、パスワードなどの機密情報をセッションに含めないために便利です。
oldメソッド
$username = $request->old('username');<input type="text" name="username" value="{{ old('username') }}">直前のリクエストのフラッシュデータを取得するには、Requestインスタンスに対しoldメソッドを使用してください。oldメソッドはセッションにフラッシュデータとして保存されている入力を取り出すために役に立ちます。
Laravelではoldグローバルヘルパ関数も用意しています。特にBladeテンプレートで直前の入力値を表示したい場合に、oldヘルパは便利です。指定した文字列の入力が存在していないときは、nullを返します。
参考
- 投稿日:2020-05-18T15:09:35+09:00
Laravel リレーションの覚え方
混乱する人向け。
手っ取り早い考え方
Q1 どっちが
belongsTo()
?
- A. 外部キー(FK)がある方が
belongsTo()
Q2.
hasMany()
,hasOne()
どっちを使えばいい?
- A.
belongsTo()
側のTBLに、対象FKのレコードが複数回登場するならhasMany()
。uniqueならhasOne()
Sample Database Table
アカウント情報
users TBL 型 id autoincrement PK string password string name string アカウントユーザの住所
address TBL 型 id autoincrement PK user_id FK users.id prefecture_code string address string 掲示板投稿
posts TBL column 型 id autoincrement PK user_id FK users.id title string 掲示板投稿に対するコメント
comment TBL column 型 id autoincrement PK post_id FK posts.id user_id FK users.id message string User Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $table = 'users'; public function address() { return $this->hasOne('App\Models\Address', 'user_id'); } public function posts() { return $this->hasMany('App\Models\Post', 'user_id'); } public function comments() { return $this->hasMany('App\Models\Comment', 'user_id'); } }Address Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Address extends Model { protected $table = 'address'; public function user() { return $this->belongsTo('App\Models\User', 'user_id'); } }Post Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $table = 'posts'; public function user() { return $this->belongsTo('App\Models\User', 'user_id'); } public function comments() { return $this->hasMany('App\Models\Comment', 'post_id'); } }Comment Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Comment extends Model { protected $table = 'comments'; public function user() { return $this->belongsTo('App\Models\User', 'user_id'); } public function post() { return $this->belongsTo('App\Models\Post', 'post_id'); } }
- 投稿日:2020-05-18T14:03:09+09:00
LaravelでのJWT認証
Laravel + tymon/jwt-auth にて認証つきAPIサーバーを構築する
いろいろな情報を参照させて頂き試したが、AccessTokenが切れた状態 & RefreshTokenの期限が切れていない状態でのトークンの更新ができなかったので、少し工夫したところも記載しています。
なお、ここで構築した環境は以下にてアクセスできますのでご参照ください。
GitHub: https://github.com/sankosc/apitest-server
サーバー: https://develop.sankosc.co.jp/apitest/環境
- PHP 7.4.5
- Laravel Framework 7.10.3
- 10.4.11-MariaDB
- composerとnpmはインストール済み
準備
Laravelのインストール
$ composer create-project laravel/laravel [プロジェクト名] --prefer-dist
データベース作成
- PhpMyAdminで作成 ( utf8mb4_bin )
- .env編集
DB_DATABASE=[データベース名]
Laravelの認証機能を有効にする
$ composer require laravel/ui
$ php artisan ui vue --auth
$ npm install
$ npm run dev
マイグレーション
$ php artisan migrate
認証API構築
JWT認証ライブラリインポート
$ composer require tymon/jwt-auth $ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" $ php artisan jwt:secretユーザーモデル編集
app/User.php<?php namespace App; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Tymon\JWTAuth\Contracts\JWTSubject; class User extends Authenticatable implements JWTSubject { // // ~~ 省略 ~~ // /** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); } /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } }config/auth.phpを編集
config/auth.php'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],route追加
routes/api.php<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::post('login', 'Api\AuthController@login'); Route::get('refresh', 'Api\AuthController@refresh'); Route::group(['middleware' => ['jwt.auth']], function () { Route::post('logout', 'Api\AuthController@logout'); Route::get('me', 'Api\AuthController@me'); });AuthController作成
app/controllers/api/AuthController.php<?php namespace App\Http\Controllers\Api; use Illuminate\Http\Request; use Illuminate\Auth\Events\Registered; use Illuminate\Support\Facades\Auth; use App\Http\Controllers\Controller; class AuthController extends Controller { /** * Create a new AuthController instance. * * @return void */ public function __construct() { // $this->middleware('auth:api', ['except' => ['login']]); } /** * Get a JWT via given credentials. * * @return \Illuminate\Http\JsonResponse */ public function login() { $credentials = request(['email', 'password']); if (! $token = auth()->attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); } return $this->respondWithToken($token); } /** * Get the authenticated User. * * @return \Illuminate\Http\JsonResponse */ public function me() { return response()->json(auth()->user()); } /** * Log the user out (Invalidate the token). * * @return \Illuminate\Http\JsonResponse */ public function logout() { auth()->logout(); return response()->json(['message' => 'Successfully logged out']); } /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh() { try { return $this->respondWithToken(auth()->refresh()); } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) { return response()->json(['error' => 'Unauthorized'], 401); } } /** * Get the token array structure. * * @param string $token * * @return \Illuminate\Http\JsonResponse */ protected function respondWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth()->factory()->getTTL() * 60 ]); } }確認
Invoke-RestMethod -Uri "http://localhost/api/login" -Method POST -Body "email=sample@sankosc.co.jp&password=sample123"テスト用のAPI作成
認証なしのget API helloとpost API echoを作成します。
コントローラ作成
app/controllers/api/TestController.php<?php namespace App\Http\Controllers\APi; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class TestController extends Controller { public function hello() { return response()->json([ 'message' => 'hello' ]); } public function echo(Request $request) { return response()->json([ 'message' => $request->input('message') ]); } }route追加
routes/api.php<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::post('login', 'Api\AuthController@login'); Route::get('hello', 'Api\TestController@hello'); Route::post('echo', 'Api\TestController@echo'); Route::get('refresh', 'Api\AuthController@refresh'); Route::group(['middleware' => ['jwt.auth']], function () { Route::post('logout', 'Api\AuthController@logout'); Route::get('me', 'Api\AuthController@me'); });AccessTokenのRefreshについて
AccessTokenが切れた状態かつ、RefreshTokenの期限が切れていない状態でのトークンの更新する方法がどうしてもわかりませんでした。
jwt.refresh
をroutes/api.phpにて設定するように思えるのですがうまく動作しません。
なので、公式のQuick startで紹介されている方法から以下の2点を変更しています。
- route/api.phpにてrefreshを認証の外におく
- AuthControllerにてrefreshの処理を少し修正
routes/api.php(変更前:refreshが認証の中にいる)Route::post('login', 'Api\AuthController@login'); Route::get('hello', 'Api\TestController@hello'); Route::post('echo', 'Api\TestController@echo'); Route::group(['middleware' => ['jwt.auth']], function () { Route::post('logout', 'Api\AuthController@logout'); Route::get('refresh', 'Api\AuthController@refresh'); Route::get('me', 'Api\AuthController@me'); });routes/api.php(変更後:refreshが認証の外にいる)Route::post('login', 'Api\AuthController@login'); Route::get('hello', 'Api\TestController@hello'); Route::post('echo', 'Api\TestController@echo'); Route::get('refresh', 'Api\AuthController@refresh'); Route::group(['middleware' => ['jwt.auth']], function () { Route::post('logout', 'Api\AuthController@logout'); Route::get('me', 'Api\AuthController@me'); });AuthController.refresh修正
app/controllers/api/AuthController.php(変更前)public function refresh() { return $this->respondWithToken(auth()->refresh()); }AuthController.refresh修正
app/controllers/api/AuthController.php(変更後)public function refresh() { try { return $this->respondWithToken(auth()->refresh()); } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) { return response()->json(['error' => 'Unauthorized'], 401); } }こうすることで、完全に認証がない状態だと401を返し、RefreshTokenの期限が切れる前であれば、更新したトークン返すようになります。
- 投稿日:2020-05-18T13:56:55+09:00
「SQLSTATE[HY000] [2002] Connection refused」エラーの解決法
- 投稿日:2020-05-18T12:04:50+09:00
composerでrequireなどをするとインストールに失敗する
目的
- require時に発生するエラーを解決した話をまとめる
実施環境
- ハードウェア環境(下記の二つの環境で確認)
項目 情報 OS macOS Catalina(10.15.3) ハードウェア MacBook Pro (16-inch ,2019) プロセッサ 2.6 GHz 6コアIntel Core i7 メモリ 16 GB 2667 MHz DDR4 グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.3 Homwbrewを用いて導入 Laravel バージョン 7.0.8 commposerを用いて導入 MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入 エラー内容
laravel/uiをインストールしようと思い下記のコマンドを実行した。
$ composer require laravel/ui下記の様なエラーが発生しインストールが失敗してしまった。
>Using version ^2.0 for laravel/ui >./composer.json has been updated >Loading composer repositories with package information >Updating dependencies (including require-dev) >Nothing to install or update >Generating optimized autoload files >> Illuminate\Foundation\ComposerScripts::postAutoloadDump >> @php artisan package:discover --ansi >Discovered Package: facade/ignition >Discovered Package: fideloper/proxy >Discovered Package: fruitcake/laravel-cors >Discovered Package: laravel/tinker >Discovered Package: laravel/ui >Discovered Package: nesbot/carbon >Discovered Package: nunomaduro/collision >Package manifest generated successfully. >10 packages you are using are looking for funding. >Use the `composer fund` command to find out more! >Installation failed, reverting ./composer.json to its original content.原因
- すでにインストール済みのものをインストールしようとしてしまっていた。
- そもそも実行する必要のないコマンドを実行してエラーが出てしまっていた。
- 投稿日:2020-05-18T11:48:09+09:00
CakePHP2.0 を、Laravel, GitHub Actions, ECS, CloudFormation にしました
4ヶ月かかりました。
手動デプロイの CakePHP2.0 を Laravel, GitHub Actions, ECS, CloudFormation に切り替えました。
ほとんど触ってないプロダクトだったので、テスト書きつつ、仕様書かきつつ...なんとか終わって嬉しいです。この記事では、感想や困ったこと、主に感想を書きます。
感想も要約すると「さいこうー!」と言ってるだけです。ご了承ください。感想: CakePHP → Laravel 気持ちいい
最高です。とにかく気持ちいいです。CakePHP2.0 の配列地獄から抜け出して、プロパティへのアクセスが嬉しいです。
物量があるので、がんばって全部書き直しました。
テストコードがないので、リファクタリングはほどほどにやりながら進めました。
CakePHP → Rails のときより、PHP コードが使い回しできるので楽でした。感想: GitHub Actions 気持ちいい
CircleCI か GitHub Actions が候補でした。
社内(同事業部)で5つのサービスを運用しているのですが、他4つは全部 CircleCI です。
でも最近の GitHub Actions の盛り上がりを見て、試したくて仕方ありませんでした。結論、最高です。
Pull request の隣に鎮座している Actions が最高です。いちいちサービスを跨がなくてよいのが本当に気持ちいいです。
yaml は
- deploy-production.yml
- deploy-staging.yml
- lint.yml
- test.yml
といくつか作りました。
ECS をデプロイする際に、https://github.com/aws-actions がありますから、ここの actions を
使えば自分では何も考えずに済む点が良いです。(後述する、migration は少し詰まりました。)感想: CloudFormation の導入
社内では、まだインフラのコード化は進んでいません。ですが、すでに5つのサービスを運用していて、それぞれ似たようなインフラ構成ですので、コード管理して整備しておきたかったです。
CloudFormation か Terraform で迷うところですが。個人的に Terraform 使ったことないのと、CTO が CloudFormation でいいよと適当に呟いたのと、AWS リソースしか使わないので、という理由で選びました。AWS CDK も良いという声があり、それも迷いましたが、そこは段階的に行くとして、まずは CloudFormation 導入に至りました。
結論、最高です。
これで、リソースの名前づけや、変更など綺麗にやっていけそうです。
感想: ECS にした
EKS か ECS で悩んだのですが、やっぱり簡単なので ECS にしました。社内には EKS も1つあるのですが、ECS に統一することにしました。
これで本番がコンテナ化できたので、デプロイスクリプトとさよならできて嬉しいです。ローカルも本番も docker 化することができで差分がなくなったことも嬉しい。困ったこと: GitHub Actions のプルリクが動かない
プルリクを出したら lint や test を回すということがしたいのですが、最初それが出来ませんでした。なぜかというと、僕らの開発フローが fork したリポジトリからプルリクを出すスタイルだからです。
private リポジトリの場合、fork したリポジトリから出す pull_request ではワークフローが動きません。
なので、fork をやめました。 本体に commit して プルリクを作るスタイルに変えました。いまのところ、これで困っていません。
困ったこと: DB Migration をどうしたか
発想的には、one shot task でやるということでシンプルなのですが、GitHub Actions でどうやるんだ?というのに少しつまりました。
結論としては aws-actions/amazon-ecs-deploy-task-definition@v1 でタスク定義を更新して、
ecs run-task
するという以下のようなイメージです。# migration タスク定義の登録 - name: Register migration task def id: register-migration-task-def uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.task-def.outputs.task-definition }} cluster: sample # migration タスク実行 - name: migrate run: aws ecs run-task --launch-type FARGATE --cluster sample --task-definition ${{ steps.register-migration-task-def.outputs.task-definition-arn }}CloudFormation でまだよく掴めていないこと
今回初めて触りましたので、
どこまで、CloudFormation でやるのか?ということについて、答えは見えていません。
VPC、サブネット、ALB、セキュリティグループなどは、CloudFormation で作っていますが、DB インスタンス、ECS サービス、踏み台サーバインスタンスなどは、手動です。時間と体力がなく、今はこのような状態です。どこまで CloudFormation でやるのかは、今後の課題として模索していきたいと思います。今後対応していきたいこと
まず、Log です。いまは nginx も app からなんの加工もせずに CloudWatch に流していますが、見れたものではありません。json 形式にするとか FireLens とかやっていきたいとおもいます。
つづいて踏み台サーバです。そもそも VPN でやるとか、他にもいろいろ踏み台サーバをなくす方法があると思うのですが、この辺も考えて行きたいと思います。が、BI ツールの DOMO が DB に繋げないといけなかったりして、障壁はいろいろありそうです。
そして、AWS CDK。これは、追い追いでいいかなと思っています。良い良いという声は聞こえてくるので、CloudFormation に馴染んだら検討したいと思いました。
リリース作業で改めて感謝したツール
もうこれがないと、やってられないと思うほど、泣くほど嬉しいツールです。案の定、リリース作業直後バグは発生しました。でも SENTRY のおかげで、エラー監視ができすぐに対処できました。本当にありがたいツールです。
おわりに
今回の構成にしてみて、アプリケーションエンジニアにとって、デプロイやインフラの見通しがよくなったと思いました。今後もどんどん開発体験は良くなっていくんだろうなぁ。すごい。
CloudFormation のサンプルコードを置いておきます。誰かのお役に立てば幸いです。
github.com/mochizukikotaro/cloudformation-sampleありがとうございました。