20210417のlaravelに関する記事は8件です。

Laravelのログフォーマットのカスタムで改行が効かない時

はじめに Laravelでログのフォーマットを変更しようとした時に改行が思ったように効かない、そんな時の対処法 Laravelでログフォーマットを変更する方法 フォーマットを変更したいログのチャンネルに'tap' => [App\Logging\CustomizeFormatter::class],を追加 'channels' => [ 'stack' => [ 'driver' => 'stack', 'tap' => [App\Logging\CustomizeFormatter::class], 'channels' => ['error', 'app'], 'ignore_exceptions' => false, ], ] app/Logging/CustomizeFormatter.phpを作成してログのフォーマットを記述していきます laravel8のリファレンスに記載のコードは以下 <?php namespace App\Logging; use Monolog\Formatter\LineFormatter; class CustomizeFormatter { /** * 指定するロガーインスタンスをカスタマイズ * * @param \Illuminate\Log\Logger $logger * @return void */ public function __invoke($logger) { foreach ($logger->getHandlers() as $handler) { $handler->setFormatter(new LineFormatter( '[%datetime%] %channel%.%level_name%: %message% %context% %extra%' )); } } } このコードのままだと改行が効かずにログが一行で出力され続けます。見にくくて不便。 加えて改行のために末尾に \nを追加して'[%datetime%] %channel%.%level_name%: %message% %context% %extra%'\nとしても改行は効きません。 ログを改行させて表示したい LineFormatterクラスを参照すると public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false) { $this->format = $format === null ? static::SIMPLE_FORMAT : $format; $this->allowInlineLineBreaks = $allowInlineLineBreaks; $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; parent::__construct($dateFormat); } 第三引数がbool $allowInlineLineBreaks = falseで改行の可否を定めるものでありデフォルト値がfalseになっていることがわかります。 思うように改行が効かなかったのはこいつのせい。 ついでに第二引数を見ると?string $dateFormat = nullとなっていて日時のフォーマットを引数で渡すことができます。便利。 これらを踏まえてコードを書き換えると <?php namespace App\Logging; use Monolog\Formatter\LineFormatter; class CustomizeFormatter { /** * 指定するロガーインスタンスをカスタマイズ * * @param \Illuminate\Log\Logger $logger * @return void */ public function __invoke($logger) { foreach ($logger->getHandlers() as $handler) { $handler->setFormatter(new LineFormatter( "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", 'Y/m/d H:i:s', true )); } } } 出力されるログは以下のようになります。 改行が効いていること。日時フォーマットが指定したものになっていることが確認できました。 [2021/04/17 13:51:19] local.ERROR: this is error log [] [] [2021/04/17 13:51:23] local.INFO: this is info log [] []
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでログレベルにしたがってログの出力先を変更する

はじめに Laravelでログレベルにしたがってログの出力先を変更したい時の設定について 例えばerror以上はerror.log分けておきたい。みたいな時に使えます 環境 PHP 7.4.16 Laravel 8.34.0 やり方 config/loggerに以下を記述 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['error', 'app'], 'ignore_exceptions' => false, ], 'app' => [ 'driver' => 'single', 'path' => storage_path('logs/app.log'), 'ignore_exceptions' => false, 'level' => 'debug', ], 'error' => [ 'driver' => 'single', 'path' => storage_path('logs/error.log'), 'ignore_exceptions' => false, 'level' => 'error', ], ] デフォルトのチャンネルがstackになっているので.envファイル中のLOG_CHANNELに関して以下の設定になっていることを確認 LOG_CHANNEL=stack or LOG_CHANNEL= 結果 storage └── logs ├── app.log ├── error.log └── laravel.log app.log [2021-04-17 12:45:46] local.INFO: this is info log [2021-04-17 12:45:51] local.ERROR: this is error log error.log [2021-04-17 12:45:51] local.ERROR: this is error log app.log, error.logそれぞれに想定するログレベル以上のログが出力されることが確認できた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel Sanctumでログインは成功するが、要認証APIが401 Unauthorizedエラーを返す

問題 LaravelでAPIを作ってて、Sanctumを利用してユーザー認証を実装し、開発環境では動作しました。そこで検証環境に持って行ったところ「ログインは成功するが、そのあと認証が必要なAPIにアクセスすると『401 Unauthorized』エラーで弾かれる」という現象が発生しました。開発者ツールで見てもCookie等は正常に設定されているように見えますし、どこが問題なのか分かりません…… (私の場合の)原因 そもそも開発環境用の.envだけ設定してて、検証環境用の.envにSanctum周りの設定するの忘れてた 1を受けて設定したものの、.envのSANCTUM_STATEFUL_DOMAINSの記載が間違っていた 原因1に関しては「実装できた~✌」と完全に気が抜けてましたね……スーパーアホでした? 原因2に関してはDocker上で開発してて、雑にlocalhostで設定していても動いてしまっていたので、仕様をちゃんと理解できてなかったのが原因です。 調査 .envの反映し忘れの線はすぐに当たりましたが駄目で、ブラウザの開発者ツールで401しか出ず、Cookie周りも正常動作してるように見え、「(デプロイ自動化してないので)検証環境のライブラリ自体にロガー仕込むの面倒くさい」と、ググり倒したり設定弄り回したりあがいてみましたが駄目でした。 ただ調べる過程でこの記事を見かけて、どのクラスに仕込めばいいのか当たりがついたのでめちゃくちゃ助かりました? メソッド名から察しがつくかと思いますが、Webブラウザからのアクセス(Session使える)か、CLIアクセス(Session使えない)かの判定ですね。 // \vendor\laravel\sanctum\src\Http\Middleware\EnsureFrontendRequestsAreStateful.php public static function fromFrontend($request) { $domain = $request->headers->get('referer') ?: $request->headers->get('origin'); if (is_null($domain)) { return false; } $domain = Str::replaceFirst('https://', '', $domain); $domain = Str::replaceFirst('http://', '', $domain); $domain = Str::endsWith($domain, '/') ? $domain : "{$domain}/"; $stateful = array_filter(config('sanctum.stateful', [])); return Str::is(Collection::make($stateful)->map(function ($uri) { return trim($uri) . '/*'; })->all(), $domain); } なるほど「refererあるいはorigin、SANCUM_DOMAIN_ORIGINSを加工した上で比較してるんだな」というのは分かって、Str::isも調べてようやく、SANCTUM_STATEFUL_DOMAINSに「www.」のないドメインを登録してて、$domainとはマッチしないと気づきました。 .envの他の項目とごっちゃになったか、省略可能だろうと当てずっぽうに設定した線が濃厚です? 「www.」を含めたドメイン名を設定することで無事動きました。 感想 ググった限り、このトラブルはそこそこある上に、原因となる要素が多いんで、StackOverFlow等でも「更新後の.envちゃんと適応してる?」とか「sessionタイプをcookieにしろ(注:デフォルトのfile設定で動きます。誤解してる人の書き込みがあるので注意)」等、場当たり的なアドバイスが多い印象を受けました。チェックリストが必要ですね……誰か作ってください。(怠慢) またsession管理がファイルの場合だと書き込み権限絡みで失敗するパターンもあるみたいなんで、この問題にハマっている人はチェックしてみてください。 それと「(検証環境とはいえ)APP_DEBUG=trueにしたらもっと早く分かったんじゃね」と今書いてて思いましたw デプロイが手作業だし、マルウェア問題の記事を読んだ直後で、検証環境でAPP_DEBUG=trueするのに抵抗があったので、思いついても実行出来たかどうか微妙ですが……。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】初学者による初学者のための認可機能(Policy)の使い方

はじめに 例えば、あるサイトでユーザーID1の人がマイページのユーザー詳細画面を開いた時、URLが https://○○○○/user/1 のような形だったとします。1というのはユーザーIDを指します。 もしここを2に変えた時、何が起こると思いますか? なんと、ユーザーID1の人がユーザーID2の人のユーザー詳細画面に入れてしまうんです! 個人情報が全て筒抜けになってしまうんです。他にも、ユーザー情報の編集画面に入って他人の情報を書き換えることもできてしまったりします。 そんな危ないサイト使いたくないですよね。 なので、このような場合は必ず認可機能を実装してアクセス制限を行うようにしましょう!! ※IDを推測されやすい番号にしない、といった内容は今回の記事では取り扱わないことにします。 環境 Composer 1.10.20 PHP 7.4.15 Laravel 6.20.20 Policyとは Laravelでは認可のために方法がいくつか用意されています。 今回はその中からPolicyについてのみ説明しますが、詳しく知りたい方は以下の記事を読んでみてください。 Laravel Gate(ゲート)、Policy(ポリシー)を完全理解 Laravel Document (Laravel 6.x 認可 ) Policyとは、ある特定のモデルに対して行うアクション(作成、更新、削除、閲覧等)に関してアクセス制限を行う仕組みのことです。そのためモデルごとに個別の独立したPolicyファイルを作成する必要があります。 実装 ここから、実装の手順を説明していきます。 1. Policyの作成 まず、Policyファイルを作成します。 –-modelオプションでモデルを指定してPolicyを作成をすると、Policyで使用するメソッドが記述された状態でファイルを作成することができます。 今回はUserモデルに対してPolicyを生成します。 $ php artisan make:policy UserPolicy --model=User これを実行すると、app/Policiesディレクトリの中にUserPolicy.phpが作成されます。 UserPolicy.php namespace App\Policies; use App\User; use App\User; use Illuminate\Auth\Access\HandlesAuthorization; class UserPolicy { use HandlesAuthorization; /** * Determine whether the user can view any users. * * @param \App\User $user * @return mixed */ public function viewAny(User $user) { // } /** * Determine whether the user can view the user. * * @param \App\User $user * @param \App\User $user * @return mixed */ public function view(User $user, User $user) { // } } ※本来は他にcreate, update, delete, restore, forceDeleteのメソッドが記述された形で作成されますが、省略しています。 今回はviewメソッドを使用しますが、当時僕はここで「ん?」となりました。 User $user, User $userってどういうこと?同じじゃないの?? ここの説明と記述は後ほどします。 2. Policyの登録 Policyを作成したら、登録する必要があります(省略可※後述) app/Providersディレクトリ内のAuthServiceProvider.phpを編集します。 AuthServiceProvider.php protected $policies = [ 'App\User' => 'App\Policies\UserPolicy', ]; ※ Policyの自動検出 Laravel 5.8 で追加された機能です。 モデルとポリシーの標準命名規則に従っているポリシーをLaravelが自動的に見つけてくれる便利な機能で、この場合はPolicyの登録を省略できます。 例えば、appディレクトリ下にモデルがある場合、app/Policiesディレクトリ下のPolicyファイルを自動検出してくれます。 この時、モデルの名前に対応させたPolicyファイルを作成する必要があります。 (今回の場合はUserモデルに対するPolicyなので、UserPolicy.phpという名前をつけます。) 3. コントローラーの編集 次に、マイページ表示の処理を行っているshowアクションにauthorizeメソッドを追記していきます。 UserController.php public function show(User $user) { $this->authorize('view', $user);//追記 return view('mypage'); } authorizeメソッドの最初の引数をview、2番目の引数をUserモデルの変数$userとすることで、UserPolicy.phpのviewメソッドに$userの情報を渡します。 アクションが認可されない場合、authorizeメソッドは403エラーを返します。 4. Policyの編集 UserPolicy.phpに戻って、viewメソッドを編集していきます。 UserPolicy.php public function view(User $user, User $user) { return $user->id === $user->id } ここで、先ほどお話しした「User $user, User $userってどういうこと?」について説明します。 この2つの$userにはちゃんと違いがあります。1つ目の$userにはこのviewメソッドにて取得されたユーザーの情報が入っていて、2つ目の$userにはコントローラーのshowアクションから送られてきたユーザーの情報が入っています。 このままだと分かりづらいので、2つ目の方を$request_userに変更しておきましょう。 UserPolicy.php(変更後) public function view(User $user, User $request_user) { return $user->id === $request_user->id; } 完成! これで機能としては完成しましたが、処理の流れを簡単に説明したいと思います! IDが1のユーザーがログインしている状態と仮定して、 https://○○○○/user/1 というURLを https://○○○○/user/2 に変更してリロードした際の処理を説明します。 処理の流れ コントローラーのshowアクションで、UserモデルからユーザーID2の情報を$userとして取得。 $this->authorize('view', $user); で、ユーザーID2の情報をUserPolicy.phpに渡す。 UserPolicy.phpにて、Userモデルから本来のユーザーID1の情報を$userとして取得、$request_userにはコントローラーから送られた$user(今回はユーザーID2の情報)の値が入っている。 return 1 === 2;となるのでfalseを返し、403エラーを表示する。 最後に 以上、Laravelにおける認可機能の一部をご紹介しました! 初学者あるあるなのかなと思いますが、書いたコードをいざ言葉で説明するとなると、それが意外と難しいことに気づきました。 僕も初学者なので、初学者でも分かりやすい言葉で説明できるように意識したいと思います。 認識が間違っている点があれば、ぜひコメントをお願いいたします!! 参考文献 Laravel Document (Laravel 6.x 認可 ) Laravel Gate(ゲート)、Policy(ポリシー)を完全理解 【初心者向け】Laravel Policyの使い方(認可)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravel dockerでnpmを使う時

phpのDockerfileに # nodejs install RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - RUN apt-get install -y nodejs こちらの記述を書くこと 12.xはバージョン 参考 nodeのバージョン
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel Duskをローカル内のSelenium経由で使ってみる

Laravel Duskとは? Laravel Dusk 6.x Laravel ブラウザ自動操作 + ブラウザ用テストAPIをLaravel内のコードで実行できるテスト用ライブラリ スクリーンショットも取れるらしい <- 今回はやらない 事前にいるもの (前提) MacOS (自分がMacOSで動かしたので) Homebrew Laravel(v6.*) セットアップ chromedriverのインストール brew install chromedriver Seleniumのインストール brew install selenium-server-standalone ### .env の修正 APP_URLの行を以下のように変更 .env APP_URL=http://localhost:8000 パーミッション変更 $ chmod 755 vendor/laravel/dusk/bin/* Laravel Dusk のインストール $ composer require --dev laravel/dusk $ php artisan dusk:install tests/DuskTestCase.phpを以下のように変更する tests/DuskTestCase.php <?php namespace Tests; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Laravel\Dusk\TestCase as BaseTestCase; abstract class DuskTestCase extends BaseTestCase { use CreatesApplication; /** * Prepare for Dusk test execution. * * @beforeClass * @return void */ public static function prepare() { // static::startChromeDriver(); } /** * Create the RemoteWebDriver instance. * * @return \Facebook\WebDriver\Remote\RemoteWebDriver */ protected function driver() { $options = (new ChromeOptions)->addArguments([ '--disable-gpu', '--headless', '--window-size=1920,1080', ]); return RemoteWebDriver::create( 'http://localhost:4444/wd/hub',DesiredCapabilities::chrome()->setCapability( ChromeOptions::CAPABILITY, $options ) ); } } テストを実行してみる テストを書いてみる 初期設定のままだとただテスト成功するだけで面白くないので、 明らかに失敗するであろうメソッドtestChallengeExample()をtests/Browser/ExampleTest.phpに追加してみる tests/Browser/ExampleTest.php class ExampleTest extends DuskTestCase { ~~~ public function testChallengeExample() { $this->browse(function (Browser $browser) { $browser->visit('/') ->assertSee('CakePHP'); }); } } selenium-server を起動 $ selenium-server アプリケーションを起動 $ php artisan serve Dusk実行 $ php artisan dusk PHPUnit 9.5.4 by Sebastian Bergmann and contributors. .F 2 / 2 (100%) Time: 00:01.820, Memory: 18.00 MB There was 1 failure: 1) Tests\Browser\ExampleTest::testChallengeExample Did not see expected text [CakePHP] within element [body]. Failed asserting that false is true. /Users/tk_zawa/laravel-app/vendor/laravel/dusk/src/Concerns/MakesAssertions.php:181 /Users/tk_zawa/laravel-app/vendor/laravel/dusk/src/Concerns/MakesAssertions.php:152 /Users/tk_zawa/laravel-app/tests/Browser/ExampleTest.php:28 /Users/tk_zawa/laravel-app/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:68 /Users/tk_zawa/laravel-app/tests/Browser/ExampleTest.php:29 FAILURES! Tests: 2, Assertions: 2, Failures: 1. テストが無事実行 & (テスト内容として)失敗しましたね! 初回起動時... と出るので、一旦キャンセルを押す。 システム環境設定のセキュリティとプライバシー/一般を開いておく。 もう一度php artisan duskを実行。 そうすると、 と共に、 システム環境設定の画面にこのまま許可ボタンが表示される。 このまま許可を押し、アラートウィンドウはキャンセルを押す。 更にもう一度php artisan duskを実行。 と表示される為、開くを押す。(これで完了) 追記 2021/04/15時点: Laravel Dusk v6.15で要求されるchromedriverのバージョンはv90で、現時点でのChrome(ブラウザ)のバージョンはv89。 v89のChrome(ブラウザ)が入った状態では、Macに直接v90以上のchromedriverをインストールしても、 Laravel Duskを実行するとブラウザ版のドライバ(v89)が認識されて詰んでしまう為、Seleniumを経由することで回避
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【誰でもわかる】Laravel6.0リレーション入門

はじめに データベースを触り始めると確実に一度は躓きますよね。。 私も最近までデータベース設計がちんぷんかんぷんでした。 少しずつ理解出来てきたので今回は備忘録としてまとめつつ、初心者の方の助けになればと思いまとめてみました。 Laravel6.0の実装でお困り中の方は下記も見てみて下さい。 初歩的なことは下記でできるようになるかと思います。 1対1, 1対多, 多対多ってなんやねん まずは誰もが思うであろう、こちらから解説します。 1対1 まずこれはほとんど使わないので覚える必要はありません。 双方のレコードが1対1になるもしくは双方のPKが同じケースとなります。 例えるならば、 ユーザーとマイナンバーとかでしょうか。 1人のユーザーに対して1つのマイナンバーが割り振られます。 こうなるとまず思うのが、テーブル分ける必要ってある?という疑問です。 わざわざ分けるメリットもなく普通に同一テーブル内に入れろやということでほぼ使いません。 既存のテーブル構成を変えずに項目を追加したいときは採用するかも・・・ 1対多 ここからはよく使うので具体的なLaravelのコードも書いていきます。 Aテーブルのレコードは、Bテーブルの複数レコードと関連するが、 BテーブルのレコードはAテーブルのレコードの1つのみ関連するケースを指します。 例えるならば、 ユーザーと投稿でしょうか。 Twitterなどでも1人のユーザーが複数の投稿を行います。 これは国語力を活かして考えるとおすすめです。 上記の例で言うと、 1人のユーザーが複数の投稿を行う → 日本語として正しい 1つの投稿が複数のユーザーで〜〜 → おかしい ということでこれは、ユーザーテーブルが親で、投稿テーブルが子となります。 他の例としては、顧客と注文で考えてみましょう。 1人の顧客が複数の注文をする → 日本語として正しい 1つの注文が複数の顧客で〜〜 → おかしい このように考えてみるとわかりやすいのではないでしょうか?? Laravelコード解説 新たにマイグレーションファイルとモデルを作成する ターミナル php artisan make:model Models/Post --migration Migration 親 database/migrations/XXXX_XX_XX_XXXXXX_create_users_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 20文字以内の文字列 $table->string('name', 20)->comment('名前'); // 255文字以内で独自の文字列 $table->string('email', 255)->unique('email')->comment('メールアドレス'); // 時間 $table->timestamps(); }); } 子 database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 255文字以内の文字列 $table->string('body', 255)->comment('本文'); // 1対多リレーション(整数しか入らないのでunsignedBigIntegerを使用) $table->unsignedBigInteger('user_id')->comment('ユーザーID'); // 時間 $table->timestamps(); // 外部キー制約 $table->foreign("user_id")->references("id")->on("users"); }); } マイグレーションを実行する。 ターミナル php artisan migrate Model 親にhasMany、子にbelongsToを使います。 親 app/Http/Models/User.php class User extends Authenticatable { public function posts() { return $this->hasMany("App\Models\Post"); } } 子 app/Http/Models/Post.php class Post extends Model { public function user() { return $this->belongsTo("App\Models\User"); } } Controller コントローラーでのアクセス方法についてです。 app/Http/Controllers/XXXX.php // model読み込み use App\Models\User; use App\Models\Post; public function index() { // 親から子 // ユーザーのIDが1の投稿すべて User::find(1)->posts; // 子から親 // 投稿のIDが1のユーザーの名前 Post::find(1)->user->name; // 子から親 // 投稿すべてを取得 // viewファイルで親にアクセスする際は、{{ $post->user->name }}でuserのnameが取得可能です Post::all(); // よりスマートなのはwithを使いましょう。(親から子) User::with("posts"); } 多対多 AテーブルのレコードもBテーブルのレコードも、 双方の複数レコードと関連するケースを指します。 例えるならば、 投稿と投稿のカテゴリでしょうか。 1つの投稿に複数のカテゴリーがあり、 1つのカテゴリーに複数の投稿があります。 これも国語力を活かして考えるとおすすめです。 1対多の場合は入れ替えると片方は意味が通じて、もう一方は日本語として正しくない形となりました。 今回は多対多は入れ替えてもどちらとも日本語として正しくなります。 上記の例で言うと、 1つの投稿に複数のカテゴリーがあります → 日本語として正しい 1つのカテゴリーに複数の投稿があります → 日本語として正しい 私のこのqiitaの記事は合計9つのタグが紐づいています。 1つの投稿に複数のカテゴリーが紐付き、 このLaravelのタグは私以外の様々な方がこちらのタグを紐づけています。 このように考えていくと理解しやすいのではないでしょうか? では、Laravelのコード解説に移ります。 Laravelコード解説 多対多の場合は中間テーブルというものを使って構築していきます。 なぜ中間テーブルを使うのかは図解などがあった方がわかりやすいのですが自分に作成するスキルがないので割愛しますw しかし他の方の説明で素晴らしいものがあったので中間テーブルを詳しく知りたい方はこちらをご確認ください。 新たにマイグレーションファイルとモデルを作成しましょう。 今回のケースは、 親がPostとCategoryとなり、子がpost_categoryです。 ターミナル // カテゴリー php artisan make:model Models/Category --migration // 中間テーブルのマイグレーションファイル php artisan make:migration create_posts_categories_table Migration 親 database/migrations/XXXX_XX_XX_XXXXXX_create_categories_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 20文字以内の文字列 $table->string('category', 20)->comment('カテゴリー'); // 時間 $table->timestamps(); }); } database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php public function up() { Schema::create('registers', function (Blueprint $table) { // 符号なしBIGINTを使用した自動増分ID $table->bigIncrements('id')->comment('id'); // 255文字以内の文字列 $table->string('body', 255)->comment('本文'); // 1対多リレーション(整数しか入らないのでunsignedBigIntegerを使用) $table->unsignedBigInteger('user_id')->comment('ユーザーID'); // 時間 $table->timestamps(); // 外部キー制約 $table->foreign("user_id")->references("id")->on("users"); }); } 子 database/migrations/XXXX_XX_XX_XXXXXX_create_posts_categories_table.php public function up() { Schema::create('posts_categories', function (Blueprint $table) { $table->unsignedBigInteger('post_id')->comment('投稿ID'); $table->unsignedBigInteger('category_id')->comment('カテゴリーID'); // プライマリーキー制約(PK) $table->primary(["post_id", "category_id"]); }); } マイグレーションを実行する。 ターミナル php artisan migrate Model 親にhasMany、子にbelongsToを使います。 親 app/Http/Models/Post.php class Post extends Model { public function user() { return $this->belongsTo("App\Models\User"); } // belongsToMany('関係するモデル', '中間テーブルのテーブル名', '中間テーブル内で対応しているID名', '関係するモデルで対応しているID名'); public function categories() { return $this->belongsToMany("App\Models\Category", "posts_categories", "post_id", "category_id"); } } app/Http/Models/Category.php class Post extends Model { // belongsToMany('関係するモデル', '中間テーブルのテーブル名', '中間テーブル内で対応しているID名', '関係するモデルで対応しているID名'); public function posts() { return $this->belongsToMany("App\Models\Post", "posts_categories", "category_id", "post_id"); } } 子 モデルファイルは必要ありません。 Controller コントローラーでのアクセス方法についてです。 app/Http/Controllers/XXXX.php // model読み込み use App\Models\Post; use App\Models\Category; public function index() { // 親から親 // 投稿のIDが1のカテゴリーすべて Post::find(1)->categories; // 親から親 // 投稿のIDが1のカテゴリー名 Post::find(1)->categories->category; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インフラエンジニアのlaravel学習(AWSリソース作成)

はじめに 本記事はインフラエンジニアとしてLaravelを学習した際の備忘録(予定)の記事になります。 今までアプリ担当から言われたとおりミドルウェアを導入してアプリに連携していたのですが、 最近はインフラエンジニアとしてもっと上のレイアーも自分で把握しなければならないと感じたため、 PHPの学習を行っています。 今回はPHP7.3、Apache2.4を使用してLaravelの実行環境をEC2に構築します。 ※次のステップではECSを使ってコンテナ化したいですが、まずは慣れ親しんだEC2で始めてみます 今回の頭の中の構想 ざっくりと以下のようなものを作りたいと思ってます。 これらを一人で作ることで上から下までを一気通貫で対応できるようになると信じてます笑 AWSリソースはTerraformで管理し、専用のgithubリポジトリを作成 サーバの設定(ミドルウェア等)は本来Ansibleなどで構成管理したいところですが、ここはめんどいので、どこにも残さない想定(サーバが消えたらオワリ笑、やっぱコンテナって偉大・・・) コードはコード用のgithubリポジトリを作成して管理 最初は手作業でデプロイ(git pul想定)、最終的にはCircleCIもしくはGithubActionsを使ってデプロイしたい 作るWEBアプリケーションの中身は適当、今回はログイン画面と問い合わせフォーム的なのができればよいかな 前提条件 本記事を見る上で以下を考慮してください。 執筆者はあくまでインフラエンジニアとしての経験を元に 本記事を記載しているので、自分がわかってしまう部分は意識せず割愛してしまうこともあります。 逆にプログラマ目線でみたら意味不明なコードを書いたりすするかもしれまsんので、ご容赦ください。 また、あくまで学習の備忘録なので間違っていることも多々ありますので、参考にされる方は注意してください。 自分が試験的に使えるAWSアカウントを所持していること 自分の場合は会社がエンジニア検証用に用意してくれたアカウントがあるので、それを使用 なるべく費用は抑えて(無料の範囲)作る予定ですが、個人持ちではないのであまり金銭的なことに気をかけてないです IAMはAdministator権限想定 実装者はインフラエンジニア目線 Linuxの知識はそこそこある PHP初学者(というよりもプログラム初学者笑) Terraformはある程度わかる人 CI/CDツールもほぼ初学者レベル目線 記事分割 多分上で記載した構想を1つの記事にはまとまらないと思うので、以下の単位で記事を分割すると思います。 (★マークが今回の記事) AWSリソース作成★ サーバ設定 コード作成 手動でデプロイ CI/CDツール導入して自動デプロイ AWSリソース作成 1.githubリポジトリ作成 まずはTerraform用のリポジトリを作成します。 ここでは仮に「laravel-terraform」とします。 2.コード作成 ざっくりと以下のコードで環境を作成 ※バージョンは「0.12.28」で作成 ※変数の中身は載せません 細かいことを書くときついので、ここは結構省略 ec2.tf resource "aws_instance" "bastion_ec2" { count = 1 ami = var.ami_id instance_type = var.instance_type key_name = var.key_pair subnet_id = lookup(var.public_subnets, count.index % 2) vpc_security_group_ids = ["${aws_security_group.bastion_security_group.id}"] tags = { Name = "${var.name}_${var.bastion_name}_${count.index + 1}" } } resource "aws_instance" "laravel_ec2" { count = 1 ami = var.ami_id associate_public_ip_address = "false" instance_type = var.instance_type key_name = var.key_pair subnet_id = lookup(var.private_subnets, count.index % 2) vpc_security_group_ids = ["${aws_security_group.internal.id}"] tags = { Name = "${var.name}_${var.name}_${count.index + 1}" } } network.tf resource "aws_vpc" "vpc" { cidr_block = var.vpc_cidr instance_tenancy = "default" enable_dns_support = true tags = { Name = "${var.name}-vpc" } } # EIP resource "aws_eip" "nat" { vpc = true } # NatGateway resource "aws_nat_gateway" "nat" { allocation_id = aws_eip.nat.id subnet_id = aws_subnet.public-a.id } # Subnet resource "aws_subnet" "public-a" { vpc_id = aws_vpc.vpc.id cidr_block = var.subnet_cidr["public-a"] map_public_ip_on_launch = true availability_zone = "ap-northeast-1a" tags = { Name = "public-subnet-1a" } } resource "aws_subnet" "public-c" { vpc_id = aws_vpc.vpc.id cidr_block = var.subnet_cidr["public-c"] map_public_ip_on_launch = true availability_zone = "ap-northeast-1c" tags = { Name = "public-subnet-1c" } } resource "aws_subnet" "private-a" { vpc_id = aws_vpc.vpc.id cidr_block = var.subnet_cidr["private-a"] availability_zone = "ap-northeast-1a" tags = { Name = "private-subunet-1a" } } resource "aws_subnet" "private-c" { vpc_id = aws_vpc.vpc.id cidr_block = var.subnet_cidr["private-c"] availability_zone = "ap-northeast-1c" tags = { Name = "private-subunet-1c" } } # db_subnet_group resource "aws_db_subnet_group" "db-subnet" { name = "db-subnet" description = "test db subnet" subnet_ids = ["${aws_subnet.private-a.id}", "${aws_subnet.private-c.id}"] } # Internet Gateway resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = { Name = "${var.name}-igw" } } # public Route Table resource "aws_route_table" "public" { vpc_id = aws_vpc.vpc.id tags = { Name = "${var.name}-rt" } } # public Route resource "aws_route" "public_route" { gateway_id = aws_internet_gateway.igw.id route_table_id = aws_route_table.public.id destination_cidr_block = "0.0.0.0/0" } # パブリックルートテーブルとpublic-a関連付け resource "aws_route_table_association" "public_a" { subnet_id = aws_subnet.public-a.id route_table_id = aws_route_table.public.id } # パブリックルートテーブルとpublic-c関連付け resource "aws_route_table_association" "public_c" { subnet_id = aws_subnet.public-c.id route_table_id = aws_route_table.public.id } # private Route Table resource "aws_route_table" "private" { vpc_id = aws_vpc.vpc.id } # private Route resource "aws_route" "private_route" { nat_gateway_id = aws_nat_gateway.nat.id route_table_id = aws_route_table.private.id destination_cidr_block = "0.0.0.0/0" } # パブリックルートテーブルとprivate-a関連付け resource "aws_route_table_association" "private-a" { subnet_id = aws_subnet.private-a.id route_table_id = aws_route_table.private.id } # パブリックルートテーブルとprivate-c関連付け resource "aws_route_table_association" "private-c" { subnet_id = aws_subnet.private-c.id route_table_id = aws_route_table.private.id } output "vpc_id" { value = aws_vpc.vpc.id } output "subnet_public_a_id" { value = aws_subnet.public-a.id } output "subnet_public_c_id" { value = aws_subnet.public-c.id } output "subnet_private_a_id" { value = aws_subnet.private-a.id } output "subnet_private_c_id" { value = aws_subnet.private-c.id } sg.tf # RDSに設定するセキュリティグループ resource "aws_security_group" "rds_security_group" { name = "${var.name}-rds-sg" vpc_id = aws_vpc.vpc.id ingress { from_port = "3306" to_port = "3306" protocol = "tcp" cidr_blocks = ["10.1.0.0/16"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.name}-rds-sg" } } resource "aws_security_group" "alb" { name = "${var.name}-alb-sg" vpc_id = aws_vpc.vpc.id ingress { from_port = "443" to_port = "443" protocol = "tcp" cidr_block = var.office_ip } ingress { from_port = "80" to_port = "80" protocol = "tcp" cidr_block = var.office_ip } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.name}-alb-sg" } } # bastionに設定するセキュリティグループ resource "aws_security_group" "bastion_security_group" { name = "$var.name}-${var.bastion_name}-sg" vpc_id = aws_vpc.vpc.id ingress { from_port = "22" to_port = "22" protocol = "tcp" cidr_block = var.office_ip } egress { from_port = "0" to_port = "0" protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.name}-${var.bastion_name}-sg" } } output "bastion_sg_id" { value = aws_security_group.bastion_security_group.id } # インスタンスに設定するセキュリティグループ resource "aws_security_group" "internal" { name = "${var.name}-internal-sg" vpc_id = aws_vpc.vpc.id ingress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["10.1.0.0/16"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.name}-internal-sg" } } output "internal_sg_id" { value = aws_security_group.internal.id } alb # albを作成 resource "aws_alb" "alb" { name = "${var.name}-alb" security_groups = ["${aws_security_group.alb.id}"] subnets = [ "${aws_subnet.public-a.id}", "${aws_subnet.public-c.id}", ] internal = false enable_deletion_protection = false access_logs { bucket = "${var.name}-alb-logs-01" } } # albのターゲットグループ resource "aws_alb_target_group" "alb" { name = "${var.name}-tg" port = 80 protocol = "HTTP" vpc_id = aws_vpc.vpc.id target_type = "ip" health_check { interval = 60 path = "/" // NOTE: defaultはtraffic-port //port = 80 protocol = "HTTP" timeout = 20 unhealthy_threshold = 4 matcher = 302 } } # 443ポートの設定は一旦後回し # resource "aws_alb_listener" "alb_443" { # load_balancer_arn = "${aws_alb.alb.arn}" # port = "443" # protocol = "HTTPS" # ssl_policy = "ELBSecurityPolicy-2015-05" # certificate_arn = "${aws_acm_certificate.cert.arn}" # default_action { # target_group_arn = "${aws_alb_target_group.alb.arn}" # type = "forward" # } # } #listener作成 resource "aws_alb_listener" "alb" { load_balancer_arn = "${aws_alb.alb.arn}" port = "80" protocol = "HTTP" default_action { target_group_arn = "${aws_alb_target_group.alb.arn}" type = "forward" } } output "alb" { value = { dns_name = "${aws_alb.alb.dns_name}" arn = "${aws_alb.alb.arn}" target_group_arn = "${aws_alb_target_group.alb.arn}" } } 3.Terraform Apply 自分のmac上から実施 意図した差分であることを確認後、「yes」で環境作成 $ terrarorm apply 4.サーバにログインできるように設定 踏み台(bastion)サーバとLarabel用のインスタンスが出来ているはずなので、 踏み台からLaravel用のインスタンスにログインできるように設定を行う。 ※ここも省略 おわりに 本記事はここまでです。 一旦AWSのリソースができて踏み台からLaraveサーバにログインできれば本記事はオワリ。 次はサーバにPHPやフレームワークの設定を仕込んでいきます。 ※いつごろ執筆できるかはやる気次第笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む