- 投稿日:2020-09-11T23:39:38+09:00
PHP 、Laravel学習 1
これから学習したことをQiitaに投稿していきます。
非常に中途半端なところからのスタートになりますがご容赦下さい。
●Laravel学習
・MVCモデル
①ブラウザから送られてきた指令(リクエスト)をまずroutingが受け取る
②routingがどこにその指令を飛ばすべきかリクエストの種類を判別する
③controllerで受けとった場合、然るべきアクションを行う
④viewで受けとった場合そのままviewを返す(レスポンス)
⑤ブラウザに反映される※多様なパターンがあり、一概に↑の順とは限らない
routing・・・リクエストを判別し、然るべき場所に信号を送る
controller・・・routingから送られてきたリクエストに大して然るべきアクションをする
view・・・表示するべきUI部分。
Model・・・データベースの前に立ち、操作しやすくするもの(?)Model、view、controller
3つの要素の頭文字をとってMVCモデル・データベース基礎
情報を保存しておくところ
Ex.ユーザーの名前、年齢、性別等まず、データベースを操作する言語(データベース言語)について触れておきます
SQLといい、何種類かある
①MySQL
②PostgreSQL
③SQLite
存在するデータベースにより、どの言語が使われているかは異なるが
LaravelのModelはそれを全て統一して管理してくれるという便利機能付きこの言語を使い、データベースの雛形を作ったり、データを入力したりする
・CRUD処理
①Create・・・作成(タスクを作成したり、ツイートを作成したり)
②Read・・・読み込み(作成したタスクを読み込んだり、ツイートを読み込んだり)
③Update・・・更新(タスクを編集したり、追加する)
④Delete・・・削除(一度作ったデータを削除する)WebアプリケーションではこのCRUD処理が必要不可欠である。
次回は実際に作りながら学んでいきたい
- 投稿日:2020-09-11T18:41:18+09:00
LaravelでSwift_TransportExceptionが発生した時の対処法(Gmail編)
問題
Laravel
でメール送信しようとしたらSwift_TransportException
というエラーが発生した。Authenticator LOGIN returned Expected response code 235 but got code "534"
が発生した。自分は上記の両方を認証機能のパスワード再発行の際に発生した。
それらを解決できたので、その方法を記載する。解決策
今回は
Gmail
を利用した解決策を書いていく。.env
まずは
env
ファイルの設定を確かめる。MAIL_DRIVER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=465 MAIL_ENCRYPTION=ssl MAIL_FROM_ADDRESS=laravel@admin.com(適当なメールアドレス) MAIL_FROM_NAME=Admin(適当な名前) MAIL_USERNAME=(自分が利用しているGmailアドレス) MAIL_PASSWORD= MAIL_PRETEND=false
MAIL_PASSWORD
の所に何も記入されていないが後で記入する。config/mail.php
config/mail.php
を次のように書き換える。
既に記入されていたコードはコメントアウトなどで保存。config/mail.php// Mail Driver 'driver' => env('MAIL_DRIVER', 'smtp'), // SMTP Host Address 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), // SMTP Host Port 'port' => env('MAIL_PORT', 587), // Global "From" Address 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', null), 'name' => env('MAIL_FROM_NAME', null) ], // E-Mail Encryption Protocol 'encryption' => env('MAIL_ENCRYPTION', null), // SMTP Server Username 'username' => env('MAIL_USERNAME', null), // SMTP Server Password 'password' => env('MAIL_PASSWORD', null), // Sendmail System Path 'sendmail' => '/usr/sbin/sendmail -bs', // Mail "Pretend" 'pretend' => env('MAIL_PRETEND', false),Googleのセキュリティ設定
下記のサイトを参考に
【laravel】メール(gmail)がエラーで送信出来ない問題 | 旅行好き・WEBエンジニアのブログ自分は2段階認証ではエラーが解消しなかったが、アプリパスワードの設定によって解消できた。
アプリパスワードが発行されたら、
.env
のMAIL_PASSWORD=
に入力する。キャッシュの削除
今のままだと
Laravel
の変更前の設定ファイルの記録などが残っているため、$ php artisan config:cache
をターミナルで実行しそれらを削除する。以上の作業が終わったら、自分が操作したいメール送信機能を試してみる。
参考資料
- 投稿日:2020-09-11T18:23:53+09:00
ElastickBeanstalkにSSHしてLaravelのコマンドを動かそうと思ったらなぜかsegment faultになるので
これはなに
ElastickbeanstalkにSSHしてPHPのコードを動かそうと思ったら
なぜかsegment faultになったけど
ルーティングファイルに直接処理を書いたら落ちずに実行できたよという話です。背景
ElastickBeanstalkのインスンタンスにSSHでログインして
Laravelのコマンドを実行したいのですが、実行後1,2秒するとsegment faultが発生してしまう状況にハマってました。
local環境だと動くし、メモリも不足してなさそうな感じで、原因はまったく不明です。
tinker
から処理を呼び出してもダメでした。
不思議な力によってプロセスがキルされてるのかもしれません。AWSのセキュリティ機構みたいななにかが。対処方法
SSHしてコマンド実行するのは諦めました。
そこでひらめいたのが、Webからアクセスしたら処理が呼び出されるようにする方法です。
Laravelのルーティングが記載されたファイルを編集します。
私は、SSHしたままvim
で直接コードを変えました。 一時的なコマンド実行なので大目に見て欲しい。routes/web.phpRoute::get('foo', function () { return 'Hello World'; });こんな感じで書けばブラウザから
/foo
にアクセスすると処理が実行されてHello World
と表示されるはずです。
ここで
resolve()
などのヘルパーを使って使いたいクラスをインスタンス化して、必要な処理を呼び出すことができます。また、このケースでは文字列をreturnしてますが、標準出力した文字もブラウザ上に表示されるます。
以上、参考になりましたら幸いです。
- 投稿日:2020-09-11T17:23:24+09:00
Laravelの配列操作ラッパー、Collectionを単品で使用してみる
概要
Laravelのcollectionが便利なので単品で使いたい
コレクション -Laravelドキュメントinstall
% composer require illuminate/supportcode
sample.php
<?php require_once "vendor/autoload.php"; $array = ['key' => 'value']; $collection = collect($array); var_dump($collection);結果
% php sample.php object(Illuminate\Support\Collection)#3 (1) { ["items":protected]=> array(1) { ["key"]=> string(5) "value" } }できた!
参考
おまけ
docker-compose.yml
version: '3' services: php: container_name: php-collection build: context: . dockerfile: docker/php/Dockerfile volumes: - ./php:/var/www tty: true working_dir: /var/www env_file: - ./.envDockerfile
FROM php:7.4 COPY --from=composer:latest /usr/bin/composer /usr/bin/composer RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \ && apt-get update \ && apt-get install -y zip unzipbuild & 起動 & 実行
docker-compose build php docker-compose up -d docker exec -it php-collection /bin/bash composer require illuminate/support vi ./sample.php php ./sample.php
- 投稿日:2020-09-11T14:19:25+09:00
まだ全然しょぼいLaravelアプリをHerokuにデプロイしてみた
はじめに
まだ作り始めの段階でしかないLaravelのしょぼいアプリを早々に、GitHubのmasterブランチからHerokuにデプロイする。GitHubのこのデプロイ話もネットにゴロゴロしていますが、どれも微妙に違ったりして、迷ったので、僕はこのやり方でやりましたというのを参考に記録しておきたいと思います。
なぜしょぼいのにデプロイするの?
確かにHeroku デプロイで検索してざっと眺めてみた感じ、良い感じにアプリが出来上がってきてからデプロイする人が多いようです。てか僕もそう思っていました。エンジニアYouTuberのおさないさんのアドバイスをもらうまでは...。
おさないさん曰く、開発の初期の段階で本番に近い環境で制作していないと、アプリが仕上がった段階でデプロイしてしまって、その環境では動かないことが判明したときに修正するのが大変だからだそうです。どうせ一般公開しても誰も見に来れないと。
環境
Laravel 7, Mac, GitHub, Heroku, ClearDB MySQL
やるべきことをまとめとくと
チマチマとやることがあるので、やること一覧を俯瞰して把握できるように整理しておきたいと思います。
- Herokuでアカウント登録をして、新規アプリを作る
- Heroku CLI(Herokuのアプリ)をPCにインストールする
- ClearDBをアドオンする
- ProcfileをLaravelプロジェクトディレクトリ直下に作成する
- APP_KEYを設定する
- HerokuとGitHubを連携する
では、詳しく見ていきましょう。
やってみよう
Herokuでアカウント登録する
これはそんな難しくないと思いますので割愛。迷ったことと言えば地域がUnited StatesかEuropeしか選べなかったことくらいでしょうか。United Statesにしておきました。
Heroku CLIをPCにインストールする
Heroku CLIとはHeroku Command Line Interfaceの略で、コマンドライン(ターミナルとか)でHerokuを操作できるようになる、
heroku xxx
というコマンドを使えるようにするやつです。
プロジェクトのディレクトリに行って、下記コマンドを打ちます。$ brew tap heroku/brew && brew install heroku結構時間がかかったと記憶しています。
ちなみにbrewってのは、Homebrewです。HomebrewってのはMacOS用のパッケージ管理システムで、ソフトウェアのインストールを簡単にしてくれるシステムみたいです。npmってのもこの仲間だそうです。Herokuにログイン
言わなくてもわかるかと思いますが、$がついているのが、人間が打つコマンド、付いてない下段のメッセージはコンピュータからの応答です。(地味に初心者の僕らってこういうとこで混乱したりしますよね)
$ heroku login heroku: Press any key to open up the browser to login or q to exit:「ブラウザでHeroku開けるならなんかキーボード押して。終了するならqな。」と仰っておられます。
↓こんな感じのメッセージが出てきたら、ログインできています。Logging in... done Logged in as XXXXXX@gmail.com
ProcfileをLaravelプロジェクトディレクトリ直下に作成する
Procfileとは何かについてはこのQiita記事が詳しいです。要はHeroku用の設定ファイルみたいです。
$ touch ProcfileProfileではなくProcfileですのでご注意。
touch
というのはターミナルの超基本的なコマンドで、ファイルを作って
という命令文です。ディレクトリ(フォルダ)を作る場合はmkdir
ですよね。(mkfile
にして欲しかったな)
作成できたら中身を編集します。VS codeなどのテキストエディタで開いてもいいし、ターミナルなら$ vi Procfile
で開き、i
で書き込みモードになり(下にINSERTと表示される)、編集して、esc
の後:wq
でもOKです。何を書き込むかというと、こちら↓です。Procfilerelease: php artisan migrate:fresh web: vendor/bin/heroku-php-apache2 public/
これ、他のサイトを参考にしたんですが、apacheサーバーを使うという前提ですね。他のサーバー(nignxとか)を使用するなら書き方が変わってくるかと思います。(曖昧ですみません)
Procfileをリモートディレクトリ(GitHub)にプッシュします。
$ git add . $ git commit -m "コミットコメント" $ git push heroku master$ git push origin masterではないのでご注意。
で、私はここでエラーに直面しました↓。(うまく行っている人は読み飛ばしてください)fatal: 'heroku' does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
herokuなんてリポジトリはないよ、と仰っているので、設定してあげます。こちらのコマンドで.git/configを開いて
$ vi .git/config下記追加します。urlにはHeroku git URLを。HerokuサイトのSettingタブで見つけられます。
.git/config[remote "heroku"] url = https://git.heroku.com/jazz-review.git fetch = +refs/heads/*:refs/remotes/heroku/*
最初
fatal: bad config line 30 in file .git/config
とエラーが出ました。単なるスペルミスだったので、同じように躓いた方は見直してみてください。どこのブランチでやるのかってことが意外とどの解説にもないのですが(当然すぎるから??)、HerokuとリンクさせるGitHubブランチでやりましょう(今回はmaster)。
ClearDB MySQLをアドオンする
Herokuのページ上で、
Heroku > あなたのアプリ名 > Resources > Add-ons
で追加できます。また下記コマンドでも作成できます。(igniteとは無料プラン名前のことです)$ heroku addons:create cleardb:igniteDBの環境設定をする
下記コマンドでDBの情報を取得します。
$ heroku config === プロジェクト名 Config Vars CLEARDB_DATABASE_URL: mysql://ユーザーネーム:パスワード@ホスト名/データベース名?reconnect=trueそこで出てきたURLを元に、下記のように
heroku config:set XXX
でターミナルで各種設定をしていきます。ごちゃごちゃしてますが、$の行のコマンドを入力していくだけです。$ heroku config:set DATABASE_URL='mysql://ユーザーネーム:パスワード@ホスト名/データベース名?reconnect=true' Setting DATABASE_URL and restarting ⬢ プロジェクト名... done, v6 DATABASE_URL: mysql://ユーザーネーム:パスワード@ホスト名/データベース名?reconnect=true $ heroku config:set DB_DATABASE=heroku_何たらかんたら Setting DB_DATABASE and restarting ⬢ プロジェクト名... done, v7 DB_DATABASE: heroku_何たらかんたら $ heroku config:set DB_HOST=何たらかんたら.cleardb.com Setting DB_HOST and restarting ⬢ プロジェクト名... done, v8 DB_HOST: 何たらかんたら.cleardb.com $ heroku config:set DB_USERNAME=ユーザーネーム Setting DB_USERNAME and restarting ⬢ プロジェクト名... done, v9 DB_USERNAME: ユーザーネーム $ heroku config:set DB_PASSWORD=パスワード Setting DB_PASSWORD and restarting ⬢ プロジェクト名... done, v10 DB_PASSWORD: パスワードこれが済むとMySQLでログインできるそうですが、これをやった頃私はMySQLがMAMPのMySQLとは別物であることを知らなくてインストールしておらずできませんでした...。私と同じ状態の方は、詳しくはこちらの記事で。
APP_KEYを設定する
heroku config:set APP_KEY=$(php artisan key:generate --show)巷に転がるHerokuデプロイ記事では、
$ php artisan key:generate --show
で出た値をメモしておいて(もしくはHeroku > 該当のアプリ > Settings > Config Var
でも確認して)、$ heroku config:set APP_KEY=
に続けてAPP_KEYを直接インプットするというやり方もありましたが、上記のように式を代入するみたいなやり方でもうまく行きました。楽でいいですね。HerokuとGitHubを連携する
Heroku > 該当アプリ > Deployタブにて、Deploy methodでGitHubを選択し、Connect to GitHubで該当レポジトリを選択し、Connectするだけです。
僕はここで下記のエラーメッセージが出ました。
Item could not be created:
Admin access to repository requiredWe are unable to access to this connected repository on GitHub
Either the repository is empty, the authorization key is not valid, or the account used to connect to GitHub doesn't have access to the repository. Try disconnecting the repository above and then reconnect.GitHub側でのAdminアクセス権限が必要だぞーと言われているので、リポジトリの管理者に問い合わせましょう。
また、当初、連携はできたもののローカルと違う表示になってしまっていました。この問題は、一旦マニュアルでデプロイすることで最新のmasterブランチの情報をアップデートしてくれて、解決しました。
最後に
もし抜けていることがあったら、後で補足します!ご指摘ありましたら、よろしくお願い申し上げます。
参考
ClearDB MySQL
https://devcenter.heroku.com/articles/cleardbHerokuにLaravel+MySQL環境をデプロイする
https://shkn.hatenablog.com/entry/2019/06/23/163502【Heroku】LaravelとMySQLでデプロイする
https://qiita.com/yukibe/items/5c1910e259ff4e6498dbLaravel アプリをHerokuにデプロイする方法
https://kimulog.com/2020/01/03/laravel-heroku-deploy/Laravelのアプリケーションをherokuへデプロイしてみた
https://qiita.com/KZ-taran/items/6fd4fc6617953fdbc7db
- 投稿日:2020-09-11T11:32:26+09:00
Exmentでメール送信の設定をする(Amazon SESがおすすめだよ)
Exmentをインストールしたら、早めにやっておきたい設定があります。
それは、メールの送信設定。
Exmentには、レコードを追加したら通知をメールで飛ばす、というようなメール通知の仕組みがあります。ま、そのためにも必要ですが、そもそもメールが送信できないと、パスワード忘れた時に、再発行メールを飛ばせない→詰むということになります。
ですので、できるだけ早めに設定しておいたほうが良いです。
Exmentのメール送信設定箇所
デフォルトでは、左カラムのメニューから、「管理者設定→システム設定」で開いた画面の右上「システム設定」ボタンをクリックし、開いたモーダルの中にある「詳細設定」をクリックします。開いた画面の下のほうに、「システムメール設定」があります。
インストール直後のデフォルトでは、smtp.mailtrap.ioというSMTPサーバーが設定されていると思います。
mailtrap.ioは、開発用のメール送信サービスなので、本番運用では使えないと思います(試してませんが。)
そこで、然るべきメール送信サービスを設定します。
レンタルサーバーで運用している場合、そのレンタルサーバーのメール送信サーバーで良い?
Exmentのウリのひとつとして、さくらインターネットやエックスサーバーといった一般的なレンタルサーバーでも運用できる、というのがあります。
一応、Exmentの公式から、各レンタルサーバーのメールサーバーでの設定ページへのリンクも張られています。
が、一度ここで考えてみましょう。
仮に100人のユーザーがExmentに登録されていて、ある時、5分間に誰かが合計3回レコードを修正したら、通知のメールは一体5分間に何通飛ぶのか? 簡単な算数の問題ですよね。300通です。
一方、レンタルサーバーのメール送信サーバーは、どこまで送信数を許容してくれるのでしょうか?
さくらインターネット公式によると、
3.3. メール送信件数の上限
ライト スタンダード プレミアム ビジネス ビジネスプロ マネージドサーバ 15分毎に約100通程度 (400通/時間 9,600通/日 換算、ただし15分に100通程度を超えない範囲) 15分毎に約250通程度 (1,000通/時間 24,000通/日 換算、ただし15分に250通程度を超えない範囲) 15分毎に約600通程度 (2,400通/時間 57,600通/日 換算、ただし15分に600通程度を超えない範囲) また、エックスサーバー公式では、このようになっています。
メール送信数の目安
1,500通/時間
15,000通/日エックスサーバーはまだ余裕がありますが、結局は
- どれくらいの数のユーザーが
- どれくらいの頻度でレコードを更新するか(あるいはその他のタイミングでメールを送信するイベントが発生するか)
によって、この送信制限数を超えてしまう可能性は否定できません…
そこで、Amazon SESですよ
Amazon SESって何? って方向けに、公式から引用しておきます。
ユースケース
取引 E メール
購入の確認やパスワードのリセットなどのトリガーベースの連絡事項を、お客様のアプリケーションから顧客に即時に送信します。
マーケティング E メール
カスタマイズされたコンテンツと E メールテンプレートを使用して、特別オファーやニュースレターなどの製品やサービスを宣伝します。
Eメールの一括送信
通知やアナウンスを含む一括での連絡を大規模なコミュニティに送信し、設定セットを使用して結果を追跡します。まさに、ExmentのようなWebシステムからの大量メール通知に使ってくださいね、ってことですね。
気になる料金ですが、
お客様の利用状況 お客様の支払い額 追加料金 Amazon EC2 でホストされているアプリケーションからの E メール送信 毎月送信する E メールのうち最初の 62,000 通につき 0 USD、それ以降 1,000 通ごとに 0.10USD。 添付ファイル 1 GB につき 0.12USD。EC2 を使用する場合は追加の料金。 E メールクライアントやその他のソフトウェアパッケージからの E メール送信 送信する各 1,000 通の E メールにつき、0.10USD。 添付ファイル 1 GB につき 0.12USD。 E メール受信 受信する最初の 1,000 通の E メールにつき 0 USD、それ以降 1,000 通ごとに 0.10USD。 受信する E メールチャンク 1,000 通につき、0.09USD (詳細については、料金の詳細を参照してください)。 レンタルサーバー+SESだと、2行目の価格設定に相当するので、1000通ごとに0.1ドル、つまり10円ぐらいです。
100名のユーザーだと10回分になりますが、仮に月1,000回ぐらいメールを100通飛ばすことになるのであっても、月に1,000円の費用負担です。まあまあ安いんじゃないでしょうか。
もし可能であれば、Amazon EC2またはLightsailからの運用だと、1行目の価格設定に相当するので、62,000通は無料になります。これは大きいですね。(LightsailからもSESの無料枠を使えるというのは、非公式ですがこちらがソースです。)
(LightsailでExmentを運用したい方は、こちらの記事もご参照ください。)
あと、SESでない選択肢としてはSendGridになってくるかと思いますが、こちらも無料で月12,000通までは無料だそうです。(私は使ったことがないので、使用感などについてはなんとも言えませんが…)
ExmentでAmazon SESを使う方法
基本的には、
- SESのセットアップ
- Exmentに設定
で完了します。
SESのセットアップはちょっと手間で、不正なメール送信を行わせないようにするため、審査があります。この審査をクリアして、晴れて正式に大量のメール送信ができます。(審査通過前は大量送信ができません)
詳しくは下記記事が詳しいので参考にしてください。
以降は、送信制限緩和が終わった前提で、Exmentへの設定方法を記します。
SESのSMTP情報を確認する
Amazon SESのコンソールにログインし、左メニューから「SMTP Settings」をクリックします。
- ServerName:
- Port:
- Use Transport Layer Security
次に、「Create My SMTP Credentials」をクリックします。
IAMユーザーの作成画面になります。適当なユーザー名をつけましょう。ここでは、ses-exment-smtp-userとしました。
右下の「作成」ボタンを押して次に進みます。
すると、SMTPユーザーアカウントが発行されるので、ユーザー名とパスワードを控えておきます。右下の「認証情報のダウンロード」でもOKです。
次に、Exment側に取得した情報を設定します。
先ほどのメール設定画面を開き、以下の内容で設定します。
設定項目 設定内容 ホスト SESコンソールで控えた内容をペースト ポート 25,465または587 暗号化形式 tls ユーザー名 作成して控えたユーザー名 パスワード 作成して控えたパスワード 送信元アドレス 管理者のメールアドレスか、no-reply@~ 以上の設定を済ませたら、保存ボタンを押して確定しましょう。
Exmentからテストメールを送信してみよう
Exmentには、きちんとメールが送信されるかテストメールを送信する機能があります。
メール送信設定画面と同じ画面の最下部にあります。
ここに自分のメールアドレスを入力して、きちんと届くかテストしてみましょう。
お、きちんと届いておりますな。Gmailの重要フラグもついて、迷惑メール扱いにもなってません。
まとめ
- Exmentでは、インストール後なるはやでメール送信の設定をしておこう
- そうしないとパスワード再発行時に詰む
- レンタルサーバーのメール送信サーバーでは、ユーザー数や送信タイミングによっては送信数上限に達する場合がある
- そういう場合は、Amazon SESやSendGridの利用がおすすめ
- Amazon SES利用には審査がある
こんなところですかね。これでExmentでもそこそこ人数のいる会社でも通知メールばんばん飛ばせるようになりますね!
- 投稿日:2020-09-11T10:13:54+09:00
メモ:Exment on Lightsail + Docker(nginx)環境で、CORSするためのnginx.conf設定
先日、Lightsail + Dockerという組み合わせで、ExmentというOSS WebDBシステムを構築したという話を書きました。
んで、その後にもExmentとMFクラウド請求書をAPIで組み合わせる記事も書きました。
で、両者の記事で大事なことを書き忘れていたんですね。
ExmentのAPIが通らない
Postmanでは通るんだけど、Node.jsで通らない。なぜだ… そうか、CORSか…
ということで、Exmentを運用しているDocker-composeはこちらなのですが、Webサーバーはnginxで動いています。
というわけで、nginx用のCORS設定を追記しました。
nginx.confserver { listen 8080; server_name _; root /var/www/exment/public; index index.php index.html; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin '*'; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE'; add_header Access-Control-Allow-Headers 'Origin, Authorization, Accept, Content-Type'; add_header Access-Control-Max-Age 3600; add_header Content-Type 'text/plain charset=UTF-8'; add_header Content-Length 0; return 204; } try_files $uri /index.php?$query_string; } location ~ \.php$ { add_header Access-Control-Allow-Origin '*' always; fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }
- 投稿日:2020-09-11T09:23:06+09:00
Laravelのバージョンアップを正しく理解する ~Laravel8へのバージョンアップは別に早くない~
はじめに
2020年9月。Laravel8がリリースされました。このことについて「もう8なの!?」って驚きの声をいくつか見かけましたが、Laravelのバージョンアップは元々半年ごとに行なわれていたので特にそれ自体驚くことではないです。なので、おそらくそういった感想を漏らす人というのは、Laravel6以前の感覚でバージョンアップを眺めてしまっているのではないかと思います。
実は6以前のバージョン管理はいけてなかった
Laravelのバージョンの変遷を振り返ってみます。私は半年に1回バージョンアップしていると書きましたが、果たして本当でしょうか?
バージョン リリース日 LTS? 5.0 2015/02 No 5.1 2015/09 Yes 5.2 2015/12 No 5.3 2016/08 No 5.4 2017/01 No 5.5 2017/08 Yes 5.6 2018/02 No 5.7 2018/09 No 5.8 2019/02 No 6.0 2019/09 Yes 7.0 2020/02 No 8.0 2020/09 No 少なくとも2015年以降、概ね半年に1回のバージョンアップを繰り返していることが分かりますね。しかし、バージョン6以降、それまでとバージョンの上がり方が違っているのに気づくでしょう。
そもそも、それまでバージョンは
5.1、5.2…
と半年ごとに0.1
ずつしかあがっていませんでした。マイナーバージョンアップなら、その表記で良いでしょう。しかし、古参のLaravel使いはご存知の通り、これらのバージョンアップは後方互換性の無いメジャーバージョンアップです。特に5.8
の変更なんて……。セマンティックバージョンニング
では、なぜ上のような認識の違いが生まれていたのかというと、単純にバージョン管理の仕様がプロジェクトごとに自由に策定されていたからです。しかし、それでは不便だと気づいたLaravelのコントリビューターの方々は、
6
にした際に、「セマンティックバージョンニング」という、バージョン管理の標準仕様を取り入れたのです。このことについては、Laravel6のリリースノートで説明されています。
Laravel and its other first-party packages follow Semantic Versioning. Major framework releases are released every six months (February and August), while minor and patch releases may be released as often as every week. Minor and patch releases should never contain breaking changes.
When referencing the Laravel framework or its components from your application or package, you should always use a version constraint such as ^6.0, since major releases of Laravel do include breaking changes. However, we strive to always ensure you may update to a new major release in one day or less.
https://laravel.com/docs/6.x/releases#versioning-scheme
さいごに
以上、Laravelのバージョン管理について説明しました。ちなみにバージョン6からセマンティックバージョニングを取り入れたことで、次のLTSはバージョン10(2021/08リリース見込み)というキリの良い数値となります。Laravel8はDXに考慮した非常に進歩的な仕様変更がいくつも取り入れられているので、興味深いバージョンです。しかしLTSではないので、商用利用でのバージョンアップはバージョン10まで待つという方針で良いかもしれませんね。
- 投稿日:2020-09-11T00:26:29+09:00
Laravel における with の使い所 / 使うべきでない所
下記のようなコードがあると仮定します。
$tweet = Tweet::findOrFail($tweet_id); foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; }
モデルの実装例
class User extends Model { protected $fillable = ['name']; function tweets() { return $this->hasMany(Tweet::class); } } class Tweet extends Model { protected $fillable = ['content', 'user_id', 'parent_id']; function user() { return $this->belongsTo(User::class); } function replies() { return $this->hasMany(Tweet::class, 'parent_id'); } function parent() { return $this->belongsTo(Tweet::class, 'parent_id'); } }上記のコード例で発行される SQL は下記のようになります。
SELECT * FROM tweets WHERE id IN (?) LIMIT 1; -- Tweet::findOrFail($tweet_id) SELECT * FROM tweets WHERE parent_id IN (?); -- $tweet->replies -- replies が 6 件だった場合… SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->userおおっと!
replies が 6 件だったので良かったですが、これが 1000 件だったら?N + 1 問題
replies を全件取得したあと( SQL のクエリ1回 )
SELECT * FROM tweets WHERE parent_id IN (?); -- $tweet->replies
foreach
の中で関連する user の取得が replies の数だけ発生する…( SQL のクエリ N 回 )。-- replies が 6 件だった場合… SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->user SELECT * FROM users WHERE id IN (?); -- $reply->userこのようにクエリがたくさん発行される問題を N + 1 問題 と呼びます。
Eager ローディングを使う
ここで
with
を使ってみます。$tweet = Tweet::with('replies.user')->findOrFail($tweet_id); foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; }SELECT * FROM tweets WHERE id IN (?) LIMIT 1; -- findOrFail($tweet_id) SELECT * FROM tweets WHERE parent_id IN (?); -- with('replies -- replies が 6 件だった場合… SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?, ?); -- with('replies.user')
with
を使うことでクエリ数が減りました。
with
を使うべきでない場合すべての場合において
with
を使うべきでしょうか?$tweet = Tweet::with('user', 'replies.user')->findOrFail($tweet_id); if ($tweet->replies->count() <= 5) { echo $tweet->user->name, "\n"; foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; } } else { echo "too many replies!\n"; }SELECT * FROM tweets WHERE id IN (?) LIMIT 1; -- findOrFail($tweet_id) SELECT * FROM users WHERE id IN (?); -- with('user') SELECT * FROM tweets WHERE parent_id IN (?); -- with('replies -- replies が 6 件だった場合… SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?, ?); -- with('replies.user')さて今回の例において replies が 6 件だった場合
下記はfalse
になるので…if ($tweet->replies->count() <= 5) {…下記の2つのクエリが 無駄に実行 されています。
SELECT * FROM users WHERE id IN (?); -- with('user') SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?, ?); -- with('replies.user')Lazy ローディング / Lazy Eager ローディングを使う
これを修正すると下記のようになります。
$tweet = Tweet::findOrFail($tweet_id); if ($tweet->replies->count() <= 5) { echo $tweet->user->name, "\n"; // Lazy ローディング $tweet->loadMissing('replies.user'); // Lazy Eager ローディング foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; } } else { echo "too many replies!\n"; }replies が 6 件だった場合のクエリは下記のようになり…
SELECT * FROM tweets WHERE id = ? LIMIT 1; SELECT * FROM tweets WHERE parent_id = ?;replies が 5 件だった場合のクエリは下記のようになります。
SELECT * FROM tweets WHERE id = ? LIMIT 1; SELECT * FROM tweets WHERE parent_id = ?; -- 下記の2つのクエリは必要なときだけ実行される SELECT * FROM users WHERE id = ? LIMIT 1; SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?);まとめ
Laravel のリレーションは必要になるまで取得されません。
そのため N + 1 問題が起こることがあります。
with
やload
は N + 1 問題 を防ぐために使いましょう。不要な場合はできるだけ使うのを避けた方が良いです。
必要ないのに取得するため無駄にクエリが発行されるかもしれません。参考
https://laravel.com/docs/8.x/eloquent-relationships#eager-loading
https://laravel.com/docs/8.x/eloquent-relationships#lazy-eager-loading補足1
Laravel でない ORM の場合は
必要そうなリレーションに片っ端から with を指定する利点があるかもしれません。Laravel で
Tweet::with('user')->findOrFail($tweet_id)を実行すると下記の SQL が発行されますが…
SELECT * FROM tweets WHERE id = ? LIMIT 1; SELECT * FROM users WHERE id IN (?);たとえば Sequelize で
Tweet.findOne({ where: { id: tweetId }, include: [Tweet.associations.user] })を実行すると下記の SQL が発行されます。
SELECT `tweet`.`id`, `tweet`.`content`, `tweet`.`created_at` AS `createdAt`, `tweet`.`updated_at` AS `updatedAt`, `tweet`.`user_id` AS `userId`, `tweet`.`parent_id` AS `parentId`, `user`.`id` AS `user.id`, `user`.`name` AS `user.name`, `user`.`created_at` AS `user.createdAt`, `user`.`updated_at` AS `user.updatedAt` FROM `tweets` AS `tweet` LEFT OUTER JOIN `users` AS `user` ON `tweet`.`user_id` = `user`.`id` WHERE `tweet`.`id` = ?;補足2
今回の例の場合は、より適切な実装があります。
$tweet = Tweet::with(['replies' => fn($q) => $q->take(6)])->findOrFail($tweet_id); if ($tweet->replies->count() <= 5) { $tweet->loadMissing('replies.user'); echo $tweet->user->name, "\n"; foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; } } else { echo "too many replies!\n"; }
php 7.3 以前の場合
$tweet = Tweet::with(['replies' => function($q) { $q->take(6); }])->findOrFail($tweet_id); if ($tweet->replies->count() <= 5) { $tweet->loadMissing('replies.user'); echo $tweet->user->name, "\n"; foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; } } else { echo "too many replies!\n"; }SELECT * FROM tweets WHERE id = ? LIMIT 1; SELECT * FROM tweets WHERE parent_id IN (?) LIMIT 6; -- replies が 5 件以下だった場合 下記の2つのクエリも実行される SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?); SELECT * FROM users WHERE id = ? LIMIT 1;あるいは(クエリの実行数は増えますが)下記の方が良いかもしれません。
$tweet = Tweet::findOrFail($tweet_id); if ($tweet->replies()->count() <= 5) { // replies が replies() になってます $tweet->loadMissing('replies.user'); echo $tweet->user->name, "\n"; foreach ($tweet->replies as $reply) { echo $reply->user->name, "\n"; } } else { echo "too many replies!\n"; }SELECT * FROM tweets WHERE id = ? LIMIT 1; SELECT COUNT(*) AS aggregate FROM tweets WHERE parent_id = ?; -- replies が 5 件以下だった場合 下記の3つのクエリも実行される SELECT * FROM tweets WHERE parent_id IN (?); SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?); SELECT * FROM users WHERE id = ? LIMIT 1;