20200911のlaravelに関する記事は9件です。

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処理が必要不可欠である。

次回は実際に作りながら学んでいきたい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のセキュリティ設定

下記のサイトを参考にGoogleアカウントの2段階認証、またはアプリパスワードの設定を行う。
【laravel】メール(gmail)がエラーで送信出来ない問題 | 旅行好き・WEBエンジニアのブログ

自分は2段階認証ではエラーが解消しなかったが、アプリパスワードの設定によって解消できた。

アプリパスワードが発行されたら、.envMAIL_PASSWORD=に入力する。

キャッシュの削除

今のままだとLaravelの変更前の設定ファイルの記録などが残っているため、$ php artisan config:cacheをターミナルで実行しそれらを削除する。

以上の作業が終わったら、自分が操作したいメール送信機能を試してみる。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.php
Route::get('foo', function () {
    return 'Hello World';
});

こんな感じで書けばブラウザから /foo にアクセスすると処理が実行されてHello Worldと表示されるはずです。
ここで
resolve()などのヘルパーを使って使いたいクラスをインスタンス化して、必要な処理を呼び出すことができます。

また、このケースでは文字列をreturnしてますが、標準出力した文字もブラウザ上に表示されるます。

以上、参考になりましたら幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelの配列操作ラッパー、Collectionを単品で使用してみる

概要

Laravelのcollectionが便利なので単品で使いたい
コレクション -Laravelドキュメント

install

% composer require illuminate/support

code

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:
      - ./.env

Dockerfile

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 unzip

build & 起動 & 実行

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

まだ全然しょぼい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 Procfile

ProfileではなくProcfileですのでご注意。touchというのはターミナルの超基本的なコマンドで、ファイルを作ってという命令文です。ディレクトリ(フォルダ)を作る場合はmkdirですよね。(mkfileにして欲しかったな)
作成できたら中身を編集します。VS codeなどのテキストエディタで開いてもいいし、ターミナルなら$ vi Procfileで開き、iで書き込みモードになり(下にINSERTと表示される)、編集して、escの後:wqでもOKです。何を書き込むかというと、こちら↓です。

Procfile
release: 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:ignite

DBの環境設定をする

下記コマンドで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 required

We 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/cleardb

HerokuにLaravel+MySQL環境をデプロイする
https://shkn.hatenablog.com/entry/2019/06/23/163502

【Heroku】LaravelとMySQLでデプロイする
https://qiita.com/yukibe/items/5c1910e259ff4e6498db

Laravel アプリをHerokuにデプロイする方法
https://kimulog.com/2020/01/03/laravel-heroku-deploy/

Laravelのアプリケーションをherokuへデプロイしてみた
https://qiita.com/KZ-taran/items/6fd4fc6617953fdbc7db

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Exmentでメール送信の設定をする(Amazon SESがおすすめだよ)

Exmentをインストールしたら、早めにやっておきたい設定があります。

それは、メールの送信設定

Exmentには、レコードを追加したら通知をメールで飛ばす、というようなメール通知の仕組みがあります。ま、そのためにも必要ですが、そもそもメールが送信できないと、パスワード忘れた時に、再発行メールを飛ばせない→詰むということになります。

ですので、できるだけ早めに設定しておいたほうが良いです。

Exmentのメール送信設定箇所

デフォルトでは、左カラムのメニューから、「管理者設定→システム設定」で開いた画面の右上「システム設定」ボタンをクリックし、開いたモーダルの中にある「詳細設定」をクリックします。開いた画面の下のほうに、「システムメール設定」があります。

2020-09-11_10h20_35.png

2020-09-11_10h21_24.png

2020-09-11_10h21_54.png

インストール直後のデフォルトでは、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を使う方法

基本的には、

  1. SESのセットアップ
  2. Exmentに設定

で完了します。

SESのセットアップはちょっと手間で、不正なメール送信を行わせないようにするため、審査があります。この審査をクリアして、晴れて正式に大量のメール送信ができます。(審査通過前は大量送信ができません)

詳しくは下記記事が詳しいので参考にしてください。

以降は、送信制限緩和が終わった前提で、Exmentへの設定方法を記します。

SESのSMTP情報を確認する

Amazon SESのコンソールにログインし、左メニューから「SMTP Settings」をクリックします。

2020-09-11_11h05_17.png
下図の画面が開くので、

  • ServerName:
  • Port:
  • Use Transport Layer Security

をメモっておきましょう。
2020-09-11_11h06_32.png

次に、「Create My SMTP Credentials」をクリックします。

2020-09-11_11h09_22.png

IAMユーザーの作成画面になります。適当なユーザー名をつけましょう。ここでは、ses-exment-smtp-userとしました。

右下の「作成」ボタンを押して次に進みます。

2020-09-11_11h11_02.png

すると、SMTPユーザーアカウントが発行されるので、ユーザー名とパスワードを控えておきます。右下の「認証情報のダウンロード」でもOKです。

次に、Exment側に取得した情報を設定します。

先ほどのメール設定画面を開き、以下の内容で設定します。

設定項目 設定内容
ホスト SESコンソールで控えた内容をペースト
ポート 25,465または587
暗号化形式 tls
ユーザー名 作成して控えたユーザー名
パスワード 作成して控えたパスワード
送信元アドレス 管理者のメールアドレスか、no-reply@~

以上の設定を済ませたら、保存ボタンを押して確定しましょう。

Exmentからテストメールを送信してみよう

Exmentには、きちんとメールが送信されるかテストメールを送信する機能があります。

メール送信設定画面と同じ画面の最下部にあります。

2020-09-11_11h20_43.png

ここに自分のメールアドレスを入力して、きちんと届くかテストしてみましょう。

2020-09-11_11h22_49.png

お、きちんと届いておりますな。Gmailの重要フラグもついて、迷惑メール扱いにもなってません。

まとめ

  1. Exmentでは、インストール後なるはやでメール送信の設定をしておこう
  2. そうしないとパスワード再発行時に詰む
  3. レンタルサーバーのメール送信サーバーでは、ユーザー数や送信タイミングによっては送信数上限に達する場合がある
  4. そういう場合は、Amazon SESやSendGridの利用がおすすめ
  5. Amazon SES利用には審査がある

こんなところですかね。これでExmentでもそこそこ人数のいる会社でも通知メールばんばん飛ばせるようになりますね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ: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.conf
server {
    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;
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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まで待つという方針で良いかもしれませんね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 問題が起こることがあります。
withload は 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;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む