20200624のlaravelに関する記事は10件です。

独学4ヶ月の初心者がポートフォリオ(SPA)を作ってみた Vue Router + Vuex + Laravel

はじめに

皆さんいつもお世話になり、ありがとうございます:blush:
今回、独学4ヶ月初心者の私がアウトプットのために、フロントにVue.js、バックにLaravelを使用して、オリジナルのSPA(シングルページアプリケーション)を作成してみました:yum:
学習の記録として投稿させていただきますので、どうぞよろしくお願い申し上げます:grin:

アプリの概要

LARA_LABEL.png
■ アプリ名 LARA_LABEL 高機能メモアプリ
1枚のラベル(ポストイット的な存在のやつ)に、メモ機能、ブックマーク機能、カラーリング機能、スニペット機能、を全て詰め込んだ、学習効率化アプリです。ラベルは、フォルダ分けして管理することができ、キーワード及びカラー検索機能で呼び出し、コピーボタンをクリックすることで、登録したコマンドやスニペットをコピペすることができます:v_tone1:
レスポンシブにつきましては、現在学習中のため非対応です...:sweat:

■ アプリ作成の背景
プログラミング学習において、最も多くの時間を費やすのがググる時間だと思います。私も学習において、分からないことがある度に、ググってブックマークしたり、Qiitaで諸先輩方のありがたい教えをストックしてを繰り返しております。しかし、いざ学んだ知識を使いたいと思った時に、どのサイトに情報があったか分からなくなったり、Qiitaにストックした記事を見つけるのに時間がかかったりと、無駄な時間を使ってしまった経験があります:sweat_smile:そこで、コマンドやスニペットをストックし、即時呼び出しを可能にすれば、時間を短縮できると考えました。さらに、その情報に紐づけてQiitaの記事やWebサイトを登録することで、効率よく管理ができるようになると思い、本アプリを作成いたしました:grin:

バージョン

■フロント
Vue.js 2.6.11
Vue Router 3.1.6
Vuex 3.3.0
Vuetify 2.2.27
Vue Meta 2.3.3

■バック
PHP 7.3.17
Laravel 7.10.3

■データベース
MySQL 5.7

■Webサーバー
Nginx 1.17.10
php-fpm 7.3.17

■環境
Docker (Laradock)
AWS EC2 (Ubuntu 18.04)

機能一覧

■ユーザー情報関連
・新規ユーザー登録
・ログイン・ログアウト
・ソーシャルログイン (Google, GitHub, Facebook)
・簡単ログイン
・アカウント削除

■メイン機能
・フォルダ 一覧表示、作成、編集、削除
・ラベル 一覧表示、作成、編集、削除
・キーワード検索
・カラー検索
・ページネーション
・ドラックアンドドロップによる並び替え
・背景画像変更

アプリ紹介

■ユーザー登録 / ログイン
Login.png
Vue Routerを使用して、ログインページコンポーネントにルーティングを行いました。
ユーザー認証情報はaxiosでLaravelと非同期通信を行い、Vuexのストアで管理しております。
クッキーからトークンを取り出し、HTTPヘッダーに含めてリクエスト送信することでCSRF対策を行っております。
Socialiteを使って、SNSログインを実装しました。右のラベルイメージをクリックすることで、ゲストユーザーとして簡単ログインが可能です:sunglasses:

■フォルダ
Folder.png
ユーザーはフォルダを作成、編集、削除することができます。上部の検索フォームにて検索を行うことができます。フォルダのドラッグアンドドロップによる並び替えを実装したかったのですが、現在の実力では実現できませんでしたので、今後の課題としております:frowning2:

■ラベル作成
create-label.png
上部ナビバーのCreate Labelボタンをクリックするとモーダルが開きます。Title、Text、Colorは必須で、URLはnull可です。Textはコピーエリアなので、頻出のコマンド等をすることで、即時呼び出しが可能になります:ok_hand:

■ラベル 一覧表示
index-label.png
ラベルはターミナルを切り取ってポストイットにしました風なデザインです...:sweat_smile:コピーボタンをクリックすることで、Text部分がクリップボードにコピーされます。私はDockerのコマンドやEC2のsshログインコマンドをよく忘れるので、登録してすぐ呼び出せるようにしております。ナビバーのSearch Labelエリアでキーワード検索、Search by colorエリアで色検索ができます。background-imageエリアで背景画像を変更できます。

■スニペット作成
snippet-label.png
ラベルのスニペットアイコンから登録可能です。上記の例では本アプリで使用したログインコンポーネントをスニペットとして登録しております。Copyボタンクリックでクリップボードにコピーされます。Vue.jsのコンポーネントや、お気に入りのCSSをスニペットに登録して、すぐに使い回すことが可能です:yum:

■タイトル&テキスト編集
edit-title.png
タイトル及びテキストは直接編集し、エンターキーを押すことで登録が可能です:raised_hands:

■カラー編集
edit-color.png
Edit Color ボタンで色を変更できます:raised_hands:

■URL追加及び編集
edit-url.png
URLは後から追加できます。あらかじめ登録してある場合は編集モードに切り替わります:raised_hands:

学んだこと

・SPAの基本的な開発方法及び実装の難しさ
・Vue Router Vuexの活用方法
・クッキー認証とCSRF対策
・ソーシャルログイン機能の実装
・axiosによる非同期通信
・ローカル開発から本番環境デプロイまでの基本的な流れ

おわりに

今回は、初めてオリジナルのアプリをゼロから作成し、デプロイまでやってみました。1つ機能を実装する度にエラーに遭遇し、何度も心が折れそうになりながら、とりあえず形にすることができました:sweat_smile:実際にアプリを作ってみて、自分の実力の無さを改めて実感しました:sob:今後もVue.jsとLaravelを使ってどんどんアプリを作り、成長できるよう努力を続けていきたいです:smile:
引き続きどうぞよろしくお願い申し上げます:bow:

Mahalo
takunosuke

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

独学4ヶ月の初心者がSPA(シングルページアプリケーション)を作ってみた Vue Router + Vuex + Laravel

はじめに

皆さんいつもお世話になり、ありがとうございます:blush:
今回、独学4ヶ月初心者の私がアウトプットのために、フロントにVue.js、バックにLaravelを使用して、オリジナルのSPA(シングルページアプリケーション)を作成してみました:yum:
学習の記録として投稿させていただきますので、どうぞよろしくお願い申し上げます:grin:

アプリの概要

LARA_LABEL.png
■ アプリ名 LARA_LABEL 高機能メモアプリ
1枚のラベル(ポストイット的な存在のやつ)に、メモ機能、ブックマーク機能、カラーリング機能、スニペット機能、を全て詰め込んだ、学習効率化アプリです。ラベルは、フォルダ分けして管理することができ、キーワード及びカラー検索機能で呼び出し、コピーボタンをクリックすることで、登録したコマンドやスニペットをコピペすることができます:v_tone1:
レスポンシブにつきましては、現在学習中のため非対応です...:sweat:

■ アプリ作成の背景
プログラミング学習において、最も多くの時間を費やすのがググる時間だと思います。私も学習において、分からないことがある度に、ググってブックマークしたり、Qiitaで諸先輩方のありがたい教えをストックしてを繰り返しております。しかし、いざ学んだ知識を使いたいと思った時に、どのサイトに情報があったか分からなくなったり、Qiitaにストックした記事を見つけるのに時間がかかったりと、無駄な時間を使ってしまった経験があります:sweat_smile:そこで、コマンドやスニペットをストックし、即時呼び出しを可能にすれば、時間を短縮できると考えました。さらに、その情報に紐づけてQiitaの記事やWebサイトを登録することで、効率よく管理ができるようになると思い、本アプリを作成いたしました:grin:

バージョン

■フロント
Vue.js 2.6.11
Vue Router 3.1.6
Vuex 3.3.0
Vuetify 2.2.27
Vue Meta 2.3.3

■バック
PHP 7.3.17
Laravel 7.10.3

■データベース
MySQL 5.7

■Webサーバー
Nginx 1.17.10
php-fpm 7.3.17

■環境
Docker (Laradock)
AWS EC2 (Ubuntu 18.04)

機能一覧

■ユーザー情報関連
・新規ユーザー登録
・ログイン・ログアウト
・ソーシャルログイン (Google, GitHub, Facebook)
・簡単ログイン
・アカウント削除

■メイン機能
・フォルダ 一覧表示、作成、編集、削除
・ラベル 一覧表示、作成、編集、削除
・キーワード検索
・カラー検索
・ページネーション
・ドラックアンドドロップによる並び替え
・背景画像変更

アプリ紹介

■ユーザー登録 / ログイン
Login.png
Vue Routerを使用して、ログインページコンポーネントにルーティングを行いました。
ユーザー認証情報はaxiosでLaravelと非同期通信を行い、Vuexのストアで管理しております。
クッキーからトークンを取り出し、HTTPヘッダーに含めてリクエスト送信することでCSRF対策を行っております。
Socialiteを使って、SNSログインを実装しました。右のラベルイメージをクリックすることで、ゲストユーザーとして簡単ログインが可能です:sunglasses:

■フォルダ
Folder.png
ユーザーはフォルダを作成、編集、削除することができます。上部の検索フォームにて検索を行うことができます。フォルダのドラッグアンドドロップによる並び替えを実装したかったのですが、現在の実力では実現できませんでしたので、今後の課題としております:frowning2:

■ラベル作成
create-label.png
上部ナビバーのCreate Labelボタンをクリックするとモーダルが開きます。Title、Text、Colorは必須で、URLはnull可です。Textはコピーエリアなので、頻出のコマンド等をすることで、即時呼び出しが可能になります:ok_hand:

■ラベル 一覧表示
index-label.png
ラベルはターミナルを切り取ってポストイットにしました風なデザインです...:sweat_smile:コピーボタンをクリックすることで、Text部分がクリップボードにコピーされます。私はDockerのコマンドやEC2のsshログインコマンドをよく忘れるので、登録してすぐ呼び出せるようにしております。ナビバーのSearch Labelエリアでキーワード検索、Search by colorエリアで色検索ができます。background-imageエリアで背景画像を変更できます。

■スニペット作成
snippet-label.png
ラベルのスニペットアイコンから登録可能です。上記の例では本アプリで使用したログインコンポーネントをスニペットとして登録しております。Copyボタンクリックでクリップボードにコピーされます。Vue.jsのコンポーネントや、お気に入りのCSSをスニペットに登録して、すぐに使い回すことが可能です:yum:

■タイトル&テキスト編集
edit-title.png
タイトル及びテキストは直接編集し、エンターキーを押すことで登録が可能です:raised_hands:

■カラー編集
edit-color.png
Edit Color ボタンで色を変更できます:raised_hands:

■URL追加及び編集
edit-url.png
URLは後から追加できます。あらかじめ登録してある場合は編集モードに切り替わります:raised_hands:

学んだこと

・SPAの基本的な開発方法及び実装の難しさ
・Vue Router Vuexの活用方法
・クッキー認証とCSRF対策
・ソーシャルログイン機能の実装
・axiosによる非同期通信
・ローカル開発から本番環境デプロイまでの基本的な流れ

おわりに

今回は、初めてオリジナルのアプリをゼロから作成し、デプロイまでやってみました。1つ機能を実装する度にエラーに遭遇し、何度も心が折れそうになりながら、とりあえず形にすることができました:sweat_smile:実際にアプリを作ってみて、自分の実力の無さを改めて実感しました:sob:今後もVue.jsとLaravelを使ってどんどんアプリを作り、成長できるよう努力を続けていきたいです:smile:
引き続きどうぞよろしくお願い申し上げます:bow:

Mahalo
takunosuke

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

独学4ヶ月の初心者がシングルページアプリケーションを作ってみた Vue.js + Vue Router + Vuex + Laravel

はじめに

皆さんいつもお世話になり、ありがとうございます:blush:
今回、アウトプットのために、フロントにVue.js、バックにLaravelを使用して、オリジナルのシングルページアプリケーションを作成してみました:yum:
学習の記録として投稿させていただきますので、どうぞよろしくお願い申し上げます:grin:

アプリの概要

LARA_LABEL.png
■ アプリ名 LARA_LABEL 高機能メモアプリ
1枚のラベル(ポストイット的な存在のやつ)に、メモ機能、ブックマーク機能、カラーリング機能、スニペット機能、を全て詰め込んだ、学習効率化アプリです。ラベルは、フォルダ分けして管理することができ、キーワード及びカラー検索機能で呼び出し、コピーボタンをクリックすることで、登録したコマンドやスニペットをコピペすることができます:v_tone1:
レスポンシブにつきましては、現在学習中のため非対応です...:sweat:

■ アプリ作成の背景
プログラミング学習において、最も多くの時間を費やすのがググる時間だと思います。私も学習において、分からないことがある度に、ググってブックマークしたり、Qiitaで諸先輩方のありがたい教えをストックしてを繰り返しております。しかし、いざ学んだ知識を使いたいと思った時に、どのサイトに情報があったか分からなくなったり、Qiitaにストックした記事を見つけるのに時間がかかったりと、無駄な時間を使ってしまった経験があります:sweat_smile:そこで、コマンドやスニペットをストックし、即時呼び出しを可能にすれば、時間を短縮できると考えました。さらに、その情報に紐づけてQiitaの記事やWebサイトを登録することで、効率よく管理ができるようになると思い、本アプリを作成いたしました:grin:

バージョン

■フロント
Vue.js 2.6.11
Vue Router 3.1.6
Vuex 3.3.0
Vuetify 2.2.27
Vue Meta 2.3.3

■バック
PHP 7.3.17
Laravel 7.10.3

■データベース
MySQL 5.7

■Webサーバー
Nginx 1.17.10
php-fpm 7.3.17

■環境
Docker (Laradock)
AWS EC2 (Ubuntu 18.04)

機能一覧

■ユーザー情報関連
・新規ユーザー登録
・ログイン・ログアウト
・ソーシャルログイン (Google, GitHub, Facebook)
・簡単ログイン
・アカウント削除

■メイン機能
・フォルダ 一覧表示、作成、編集、削除
・ラベル 一覧表示、作成、編集、削除
・キーワード検索
・カラー検索
・ページネーション
・ドラックアンドドロップによる並び替え
・背景画像変更

アプリ紹介

■ユーザー登録 / ログイン
Login.png
Vue Routerを使用して、ログインページコンポーネントにルーティングを行いました。
ユーザー認証情報はaxiosでLaravelと非同期通信を行い、Vuexのストアで管理しております。
クッキーからトークンを取り出し、HTTPヘッダーに含めてリクエスト送信することでCSRF対策を行っております。
Socialiteを使って、SNSログインを実装しました。右のラベルイメージをクリックすることで、ゲストユーザーとして簡単ログインが可能です:sunglasses:

■フォルダ
Folder.png
ユーザーはフォルダを作成、編集、削除することができます。上部の検索フォームにて検索を行うことができます。フォルダのドラッグアンドドロップによる並び替えを実装したかったのですが、現在の実力では実現できませんでしたので、今後の課題としております:frowning2:

■ラベル作成
create-label.png
上部ナビバーのCreate Labelボタンをクリックするとモーダルが開きます。Title、Text、Colorは必須で、URLはnull可です。Textはコピーエリアなので、頻出のコマンド等をすることで、即時呼び出しが可能になります:ok_hand:

■ラベル 一覧表示
index-label.png
ラベルはターミナルを切り取ってポストイットにしました風なデザインです...:sweat_smile:コピーボタンをクリックすることで、Text部分がクリップボードにコピーされます。私はDockerのコマンドやEC2のsshログインコマンドをよく忘れるので、登録してすぐ呼び出せるようにしております。ナビバーのSearch Labelエリアでキーワード検索、Search by colorエリアで色検索ができます。background-imageエリアで背景画像を変更できます。

■スニペット作成
snippet-label.png
ラベルのスニペットアイコンから登録可能です。上記の例では本アプリで使用したログインコンポーネントをスニペットとして登録しております。Copyボタンクリックでクリップボードにコピーされます。Vue.jsのコンポーネントや、お気に入りのCSSをスニペットに登録して、すぐに使い回すことが可能です:yum:

■タイトル&テキスト編集
edit-title.png
タイトル及びテキストは直接編集し、エンターキーを押すことで登録が可能です:raised_hands:

■カラー編集
edit-color.png
Edit Color ボタンで色を変更できます:raised_hands:

■URL追加及び編集
edit-url.png
URLは後から追加できます。あらかじめ登録してある場合は編集モードに切り替わります:raised_hands:

学んだこと

・SPAの基本的な開発方法及び実装の難しさ
・Vue Router Vuexの活用方法
・クッキー認証とCSRF対策
・ソーシャルログイン機能の実装
・axiosによる非同期通信
・ローカル開発から本番環境デプロイまでの基本的な流れ

おわりに

今回は、初めてオリジナルのアプリをゼロから作成し、デプロイまでやってみました。1つ機能を実装する度にエラーに遭遇し、何度も心が折れそうになりながら、とりあえず形にすることができました:sweat_smile:実際にアプリを作ってみて、自分の実力の無さを改めて実感しました:sob:今後もVue.jsとLaravelを使ってどんどんアプリを作り、成長できるよう努力を続けていきたいです:smile:
引き続きどうぞよろしくお願い申し上げます:bow:

Mahalo
takunosuke

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

独学4ヶ月の初心者がSPA(シングルページアプリケーション)を作ってみた Vue.js + Vue Router + Vuex + Laravel

はじめに

皆さんいつもお世話になり、ありがとうございます:blush:
今回、独学4ヶ月初心者の私がアウトプットのために、フロントにVue.js、バックにLaravelを使用して、オリジナルのSPA(シングルページアプリケーション)を作成してみました:yum:
学習の記録として投稿させていただきますので、どうぞよろしくお願い申し上げます:grin:

アプリの概要

LARA_LABEL.png
■ アプリ名 LARA_LABEL 高機能メモアプリ
1枚のラベル(ポストイット的な存在のやつ)に、メモ機能、ブックマーク機能、カラーリング機能、スニペット機能、を全て詰め込んだ、学習効率化アプリです。ラベルは、フォルダ分けして管理することができ、キーワード及びカラー検索機能で呼び出し、コピーボタンをクリックすることで、登録したコマンドやスニペットをコピペすることができます:v_tone1:
レスポンシブにつきましては、現在学習中のため非対応です...:sweat:

■ アプリ作成の背景
プログラミング学習において、最も多くの時間を費やすのがググる時間だと思います。私も学習において、分からないことがある度に、ググってブックマークしたり、Qiitaで諸先輩方のありがたい教えをストックしてを繰り返しております。しかし、いざ学んだ知識を使いたいと思った時に、どのサイトに情報があったか分からなくなったり、Qiitaにストックした記事を見つけるのに時間がかかったりと、無駄な時間を使ってしまった経験があります:sweat_smile:そこで、コマンドやスニペットをストックし、即時呼び出しを可能にすれば、時間を短縮できると考えました。さらに、その情報に紐づけてQiitaの記事やWebサイトを登録することで、効率よく管理ができるようになると思い、本アプリを作成いたしました:grin:

バージョン

■フロント
Vue.js 2.6.11
Vue Router 3.1.6
Vuex 3.3.0
Vuetify 2.2.27
Vue Meta 2.3.3

■バック
PHP 7.3.17
Laravel 7.10.3

■データベース
MySQL 5.7

■Webサーバー
Nginx 1.17.10
php-fpm 7.3.17

■環境
Docker (Laradock)
AWS EC2 (Ubuntu 18.04)

機能一覧

■ユーザー情報関連
・新規ユーザー登録
・ログイン・ログアウト
・ソーシャルログイン (Google, GitHub, Facebook)
・簡単ログイン
・アカウント削除

■メイン機能
・フォルダ 一覧表示、作成、編集、削除
・ラベル 一覧表示、作成、編集、削除
・キーワード検索
・カラー検索
・ページネーション
・ドラックアンドドロップによる並び替え
・背景画像変更

アプリ紹介

■ユーザー登録 / ログイン
Login.png
Vue Routerを使用して、ログインページコンポーネントにルーティングを行いました。
ユーザー認証情報はaxiosでLaravelと非同期通信を行い、Vuexのストアで管理しております。
クッキーからトークンを取り出し、HTTPヘッダーに含めてリクエスト送信することでCSRF対策を行っております。
Socialiteを使って、SNSログインを実装しました。右のラベルイメージをクリックすることで、ゲストユーザーとして簡単ログインが可能です:sunglasses:

■フォルダ
Folder.png
ユーザーはフォルダを作成、編集、削除することができます。上部の検索フォームにて検索を行うことができます。フォルダのドラッグアンドドロップによる並び替えを実装したかったのですが、現在の実力では実現できませんでしたので、今後の課題としております:frowning2:

■ラベル作成
create-label.png
上部ナビバーのCreate Labelボタンをクリックするとモーダルが開きます。Title、Text、Colorは必須で、URLはnull可です。Textはコピーエリアなので、頻出のコマンド等をすることで、即時呼び出しが可能になります:ok_hand:

■ラベル 一覧表示
index-label.png
ラベルはターミナルを切り取ってポストイットにしました風なデザインです...:sweat_smile:コピーボタンをクリックすることで、Text部分がクリップボードにコピーされます。私はDockerのコマンドやEC2のsshログインコマンドをよく忘れるので、登録してすぐ呼び出せるようにしております。ナビバーのSearch Labelエリアでキーワード検索、Search by colorエリアで色検索ができます。background-imageエリアで背景画像を変更できます。

■スニペット作成
snippet-label.png
ラベルのスニペットアイコンから登録可能です。上記の例では本アプリで使用したログインコンポーネントをスニペットとして登録しております。Copyボタンクリックでクリップボードにコピーされます。Vue.jsのコンポーネントや、お気に入りのCSSをスニペットに登録して、すぐに使い回すことが可能です:yum:

■タイトル&テキスト編集
edit-title.png
タイトル及びテキストは直接編集し、エンターキーを押すことで登録が可能です:raised_hands:

■カラー編集
edit-color.png
Edit Color ボタンで色を変更できます:raised_hands:

■URL追加及び編集
edit-url.png
URLは後から追加できます。あらかじめ登録してある場合は編集モードに切り替わります:raised_hands:

学んだこと

・SPAの基本的な開発方法及び実装の難しさ
・Vue Router Vuexの活用方法
・クッキー認証とCSRF対策
・ソーシャルログイン機能の実装
・axiosによる非同期通信
・ローカル開発から本番環境デプロイまでの基本的な流れ

おわりに

今回は、初めてオリジナルのアプリをゼロから作成し、デプロイまでやってみました。1つ機能を実装する度にエラーに遭遇し、何度も心が折れそうになりながら、とりあえず形にすることができました:sweat_smile:実際にアプリを作ってみて、自分の実力の無さを改めて実感しました:sob:今後もVue.jsとLaravelを使ってどんどんアプリを作り、成長できるよう努力を続けていきたいです:smile:
引き続きどうぞよろしくお願い申し上げます:bow:

Mahalo
takunosuke

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

LaravelでのファイルアップロードAPIとUnitTest

  • Windows 10
  • PHP 7.4.5
  • Laravel Framework 7.9.2

アップロードAPI

app/Http/Controllers/ImagesController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class ImagesController extends Controller
{
    public function upload(Request $request)
    {
        try {
            $path = $request->file('file')->store('images');
            return response()->json([
                'path' => $path
            ]);
        } catch (Exception $e) {
            return response($e->getMessage(), 500);
        }
    }
}

テストコード

tests/Feature/ImageFeatureTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class ImageFeatureTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @test
     */
    public function upload_success()
    {
        // Data
        $file = UploadedFile::fake()->image('dummy.jpg', 800, 800);

        // Test
        Storage::fake('local');
        $response = $this->json('POST', '/image/upload', [
            'file' => $file
        ]);
        $response->assertOk()->assertJsonStructure(['path']);
        Storage::assertExists($response->decodeResponseJson('path'));
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでレートリミット制限をかけて制限時の429エラーレスポンスをjsonで返す

Dos攻撃対策のレート制限を実装

apiの外部公開をするにあたり、Dos攻撃対策としてレートリミット機能を実装しました。

venderにあるライブラリ標準機能では、アクセス制限がかかった際は429エラーページの表示がされるようになっています。

それを次のように変更しました。

・jsonでエラーメッセージを返すようにする
・レートリミット制限の設定を環境変数から制御する

今回行った実装内容を簡単にご紹介します。

参考リンク

公式のレートリミットの解説
https://readouble.com/laravel/6.x/ja/routing.html

ルーティングの設定についての解説(今回は触りませんでした)
https://laravel.com/docs/7.x/routing#rate-limiting

分かりやすく解説されているブログの記事一覧
https://blog.pinkumohikan.com/entry/laravel-rate-limit-middleware
https://access-jp.co.jp/blogs/development/203
https://www.suzu6.net/posts/170-laravel-throttle/

middlewareにレートリミット用のファイルを作成する

app/Http/Middleware/RateLimitting/ThrottleRequests.php

middlewareにディレクトリを作り、その中にファイルを作成します

最初はvenderディレクトリから継承したThrottleRequestsExceptionを改良してレスポンスを返せないか試行錯誤していたのですが、どうも上手くいかず、、

方法を考え参考になりそうな記事を探しまくっていた所、stackoverflowにちょうどドンピシャな内容がありました。


こちらの一番最後にあるソースコードを元に、一部変更をして実装します。
https://stackoverflow.com/questions/40246741/laravel-rate-limit-to-return-a-json-payload

Create a new file ApiThrottleRequests.php in app/Http/Middleware/ and paste the code below:

変更した部分の解説を記載します。

ヘルパー関数のconfig()を使って環境変数の値からレートリミットを設定する

$maxAttempts = (int) config('app.maxAttempts');

Note:
環境変数の呼び出しでenvではなくconfigを使ったのは、 本番環境でconfig:cacheコマンドを実行すると、その後env()はnullしか返さないからです。

https://qiita.com/kawax/items/deed7e7c7c26085da01f

↓ 公式でも言及されています

https://readouble.com/laravel/6.x/ja/configuration.html

レスポンスをjsonに変更

    /**
     * Create a 'too many attempts' response.
     *
     * @param  string $key
     * @param  int $maxAttempts
     * @return \Illuminate\Http\Response
     */
    protected function buildResponse($key, $maxAttempts)
    {
        $errorResponse = json_encode([
                'status_code' => '429',
                'error' => 'Too Many Requests',
                'error_description' => 'アクセス数が上限に達しました。',
        ]);

        $response = new Response($errorResponse, 429);

        $retryAfter = $this->limiter->availableIn($key);

        return $this->addHeaders(
            $response, $maxAttempts,
            $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
            $retryAfter
        );
    }

引数の値を変えて、~分間の制限に指定(ここでは1分としています)

// $this->limiter->hit($key, $decayMinutes);
$this->limiter->hit($key, 60);

kernel.phpにパスを指定する

kernel.phpに追加したファイルのパスを指定します

※環境変数からレートリミットを制御させたいので、数値指定を削除しました

'api' => [
// 'throttle:60,1', 環境変数から制御する為に元々あった数値設定を削除
'throttle',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \App\Http\Middleware\RateLimitting\ThrottleRequests::class, //ミドルウェアのファイルパスを指定
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];

app.phpに環境変数を設定

config\app.php

環境変数の設定数値を読み込むようにします。
※説明文はオリジナルです

    /*
    |--------------------------------------------------------------------------
    | Rate Limit Access
    |--------------------------------------------------------------------------
    | Includes a middleware to rate limit access to your api.
    | The throttle middleware accepts parameters that determine the maximum
    | number of requests that can be made in one minutes. 
    | Thanks you for reading my 4th article on Qiita.
    */

    'maxAttempts' => env('MAX_ATTEMPTS_PER_MINUTE', '100'),

環境変数にアクセス上限数を設定する

.envに設定値を記載します

#レートリミット制限値の設定:設定した数値のアクセス数を超えると制限がかかる
MAX_ATTEMPTS_PER_MINUTE=100

テストしてみる

postmanを使ってレートリミット制限がかかっているかどうか、設定したエラー内容がjsonで返ってくるか、確かめてみます。

実際の構成では、AWSのALB(Application Load Balancer)を経由して接続させています。X-Forwarded-ForにIPアドレスが格納され、この値ごとに制限がかかるようにしています。(※本記事では関連するコードの明記を省略しています)

スクリーンショット 2020-06-24 12.07.45.png

制限(X-RateLimit-Limit)を5回にしてテストしています。一番下のX-RateLimit-Remainingが残りの試行回数です。

スクリーンショット 2020-06-24 12.08.26.png

5回以上試行すると、X-RateLimit-Remainingはゼロとなり、リミット制限が戻る時間を示すRetry-Afterが表示されるようになりました。

表示例では、残り16秒との事です。

スクリーンショット 2020-06-24 12.08.41.png

レスポンス結果は以下の通りです。Statusには429 too Many Request。Bodyもjsonで指定した通りのレスポンスが返ってくる事を確認できました。

スクリーンショット 2020-06-24 12.13.41.png

以上になります。少しでも実装の参考になれば幸いです。

HTTPのエラーページをカスタマイズ表示させたい場合は、
/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views

のbladeで編集できますので、目的に応じて対応しましょう。

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

【Laravel7でユーザー認証_7】会員登録時にメール認証を行う

はじめに

Laravelのユーザー認証機能を利用する際、会員登録時にメール認証を挟む手順をまとめます。

環境

XAMPP環境でLaravelが使えるように設定してあります。

  • Windows10 Pro 64bit
  • PHP 7.3.18
  • Laravel 7.12.0
  • MariaDB 10.1.32

また、Laravelプロジェクトは以下の手順で作業を進めており、ユーザーの情報を表示・変更する画面が表示できるまでできていること前提の手順となっています。
また、.env で SMTPサービスを設定して、メールも送信できるようにしています。

実装手順

Usersモデルの設定

Usersモデルについて、MustVerifyEmail を実装するように変更します。

app/User.php
- class User extends Authenticatable
+ class User extends Authenticatable implements MustVerifyEmail
  {

ルーティングの設定

Auth::routes にオプションを渡し、メール認証を有効にします。

routes/web.php
- Auth::routes();
+ Auth::routes(['verify' => true]);

このままだと、認証メールは届きますが、メールを無視してもログインができてしまう状態です。
メールアドレス確認済みのユーザーのみアクセスできるように、アクセスを制限したいルーティングについて ->middleware('verified') を追加します。

試しに、homeにだけつけてみます。

routes/web.php
- Route::get('/home', 'HomeController@index')->name('home');
+ Route::get('/home', 'HomeController@index')->name('home')->middleware('verified');

一旦確認

ユーザー登録をして、メールが届くかどうか、認証前にログインするとhomeの表示が変わるかどうか確認します。

▼届くメール

▼メール認証せずにログインすると表示される画面

日本語化

認証メールや表示される画面が一部日本語化されていないので、修正します。
※詳しくは 【Laravel7でユーザー認証_2】ユーザー認証を日本語化
ちなみに、認証メールの本文やSubjectはvender/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.php にかかれています。

resources/views/auth/verify.blade.php
                      {{ __('Before proceeding, please check your email for a verification link.') }}
-                     {{ __('If you did not receive the email') }},
+                     {{ __('If you did not receive the email,') }}
                      <form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
                          @csrf
-                         <button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>
+                         <button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another.') }}</button>
                      </form>
resources/ja.json
+     "Verify Email Address": "メールアドレス認証",
+     "Please click the button below to verify your email address.": "下のボタンをクリックして認証してください。",
+     "If you did not create an account, no further action is required.": "このメールにお心当たりがない方は、恐れ入りますが、このメールを削除してください。"
  }

ルーティングを修正

認証前ユーザーは、/settingで自分の登録情報を参照することだけはできるけれど、書き換えには認証が必要という状態になるように修正します。

HTTPプロトコル URI ゲスト 認証前ユーザー 認証済ユーザー
GET /
GET /login
GET /home ×(/login)
GET /setting ×(/login)
GET,POST /setting/name ×(/login) ×(/email/verify)
GET,POST /setting/email ×(/login) ×(/email/verify)
GET,POST /setting/username ×(/login) ×(/email/verify)
GET,POST /setting/password ×(/login) ×(/email/verify)
GET,POST /setting/deactive ×(/login)

先ほどは、app/web.php->middleware('verified') を指定しましたが、各コントローラのコンストラクタに書くこともできます。
どちらも一長一短あるかと思いますが、今回はコンストラクタに記述することにします。

Settingコントローラでは、indexはメール認証無しでアクセス可能という仕様なので、->except('index'); をつけてverifiedから除外します。

app/Http/Controllers/SettingController.php
      public function __construct()
      {
          $this->middleware('auth');
+         $this->middleware('verified')->except('index');
      }
app/Http/Controllers/Auth/ChangePasswordController.php
      public function __construct()
      {
          $this->middleware('auth');
+         $this->middleware('verified');
      }
app/Http/Controllers/Auth/ChangePasswordController.php
      public function __construct()
      {
          $this->middleware('auth');
+         $this->middleware('verified');
      }
routes/web.php
- Route::get('/home', 'HomeController@index')->name('home')->middleware('verified');
+ Route::get('/home', 'HomeController@index')->name('home');

動作確認

/setting の画面は認証前ユーザーでも表示され、名前やメールアドレスの変更フォームは認証してください画面へリダイレクトする仕様が実現できました。

しかし、クリックしないと操作できるかできないかがわからないのでは不親切です。
viewを変更して、もう少し親切設計にしたいと思います。

viewの修正

未認証のユーザーの場合、/setting画面で「メール認証をしてください」のアラートを表示させ、ユーザー情報変更の画面へのリンクは消しておく、という仕様に変更します。

メール認証済ユーザーの場合、データベース上は email_verified_at のカラムに日付が入り、そうでないユーザーはNULLとなります。
これを利用して、ログインユーザーのemail_verified_at がNULLかどうかで処理を分けました。
(書き換える内容が多かったので、viewファイル自体を分けてしまった方がよかったのかもしれないですね…)

resources/views/setting/index.blade.php
                  <div class="card-body">
                      @if (session('status'))
                          <div class="alert alert-success" role="alert">
                              {{ session('status') }}
                          </div>
                      @endif
+  
+                     @if (!$auth->email_verified_at)
+                         <div class="alert alert-warning" role="alert">
+                           {{ __('Before proceeding, please check your email for a verification link.') }}
+                           {{ __('If you did not receive the email,') }}
+                           <form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
+                               @csrf
+                               <button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another.') }}</button>
+                           </form>
+                         </div>
+                     @endif

                      <div class="list-group mb-3" style="max-width:400px; margin:auto;">
-                       <a href="{{ route('name.form') }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
+                       <a href="{{ route('name.form') }}"
+                       @if ($auth->email_verified_at)
+                         class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
+                       @else
+                          class="list-group-item list-group-item-action d-flex justify-content-between align-items-center disabled" tabindex="-1" aria-disabled="true"
+                       @endif
+                       >
                          <dl class="mb-0">
                            <dt>{{ __('Name') }}</dt>
                            <dd class="mb-0">{{ $auth->name }}</dd>
                          </dl>
+                         @if ($auth->email_verified_at)
                          <div><i class="fas fa-chevron-right"></i></div>
+                         @endif
                        </a>

-                       <a href="{{ route('email.form') }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
+                       <a href="{{ route('email.form') }}"
+                       @if ($auth->email_verified_at)
+                         class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
+                       @else
+                          class="list-group-item list-group-item-action d-flex justify-content-between align-items-center disabled" tabindex="-1" aria-disabled="true"
+                       @endif
+                       >
                          <dl class="mb-0">
                            <dt>{{ __('E-Mail Address') }}</dt>
                            <dd class="mb-0">{{ $auth->email }}</dd>
                          </dl>
+                         @if ($auth->email_verified_at)
                          <div><i class="fas fa-chevron-right"></i></div>
+                         @endif
                        </a>

-                       <a href="{{ route('password.form') }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
+                       <a href="{{ route('password.form') }}"
+                       @if ($auth->email_verified_at)
+                         class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
+                       @else
+                          class="list-group-item list-group-item-action d-flex justify-content-between align-items-center disabled" tabindex="-1" aria-disabled="true"
+                       @endif
+                       >
                          <dl class="mb-0">
                            <dt>{{ __('Password') }}</dt>
                            <dd class="mb-0">********</dd>
                          </dl>
+                         @if ($auth->email_verified_at)
                          <div><i class="fas fa-chevron-right"></i></div>
+                         @endif
                        </a>
                  </div>

↓メール認証前

↓メール認証後

おわりに

これで会員登録時にメール認証機能をもたせることができました。
ただ、このままだと有効なメールアドレスで登録・メール認証した後、不正なメールアドレスに変更ができてしまいます。
メールアドレス変更時にもメール認証をするようにアップデートしなければならなそうです。
次回はその部分を調べながら実装したいと思います。

参考サイト

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

PHP,Laravel導入

php インストール

ターミナル
brew install php@7.1

pathを通す

ターミナル
  echo 'export PATH="/usr/local/opt/php@7.1/bin:$PATH"' >> ~/.zshrc
  echo 'export PATH="/usr/local/opt/php@7.1/sbin:$PATH"' >> ~/.zshrc

更新

ターミナル
source .zshrc

php バージョン切り替え

ターミナル
7.1から7.3に切り替えたい場合。
brew unlink php@7.1
brew link php@7.3

パスの変更

vi .zshrc

export PATH="/usr/local/opt/php@7.3/bin:$PATH"
export PATH="/usr/local/opt/php@7.3/sbin:$PATH"
source .zshrc

laravel

composerインストール

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

laravelインストール

composer global require laravel/installer

パスの設定

  • usernameの部分を適宜変更してください。
export PATH="$PATH:/Users/username/.config/composer/vendor/bin"
source .zshrc
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで便利なおすすめComposerパッケージ一覧

はじめに

Laravelでアプリを作るときに
使うことが多い便利なパッケージをまとめました。
全てComposerでインストール可能なパッケージです。

主に管理画面系のwebアプリ構築の経験が多いため、
パッケージの選定もそのようなアプリで利用するものが多いです。

他にも便利なおすすめパッケージがあれば
ぜひコメントよろしくお願いします。

必須

barryvdh/laravel-debugbar

https://github.com/barryvdh/laravel-debugbar
ブラウザ下部にデバッグバーを表示する。
その時にリクエストで発行されたSQL一覧や、今持っているセッション情報一覧など
デバッグに便利な情報がブラウザ上で確認できるようになる。

barryvdh/laravel-ide-helper

https://github.com/barryvdh/laravel-ide-helper
IDEを利用してコーディングする際に、
コード補完を強化する。
変数からアローを書いたときにメソッドやプロパティのサジェスチョンがたくさん表示されたり、
メソッド定義元へのジャンプできる範囲が増えたり。
 

laravelcollective/html

https://github.com/LaravelCollective/html
bladeファイルでフォームを書くときに便利なメソッドを提供する。
CSRFトークンを自動で埋め込んでくれたり、
モデルとフォームを紐づけて自動で初期値を入れてくれたり。
 

doctrine/dbal

https://github.com/doctrine/dbal
migrationでカラム定義変更をする場合にインストールしておく必要あり。
 

よく使う

wildside/userstamps

https://github.com/WildSideUK/Laravel-Userstamps
データを作成、更新した際に
created_by、updated_byのカラムを
ログイン中ユーザIDで自動更新してくれる。

guzzlehttp/guzzle

https://github.com/guzzle/guzzle
簡単にHTTPリクエストを送信するコードが書ける。
外部サービスのAPIにリクエストするときや
フロントエンドからajaxでAPIリクエストするときに利用。

laracasts/flash

https://github.com/laracasts/flash
フラッシュメッセージを簡単に表示できる。
データ登録完了時や削除完了時に
画面上部に「登録完了しました。」みたいなメッセージ表示をする。

kyslik/column-sortable

https://github.com/Kyslik/column-sortable
一覧系画面で簡単にソート機能を実装できる。
一覧テーブルのヘッダー行をクリックするだけで
昇順、降順ソートを切り替えられる。

league/flysystem-aws-s3-v3

https://github.com/thephpleague/flysystem-aws-s3-v3
S3にファイルアップロード、ダウンロードをする場合に利用。

aws/aws-sdk-php-laravel

https://github.com/aws/aws-sdk-php-laravel
その他AWSサービス利用時に必要。
SESでメール送信など。

アプリによっては使う

orangehill/iseed

https://github.com/orangehill/iseed
実際にDBに入っているデータからseederファイルを逆生成する。

Enum

marc-mabe/php-enum
myclabs/php-enum
bensampo/laravel-enum
LaravelでEnumを実装したいときに利用。
似たようなパッケージがいくつかあり、どれがベストかはまだわからない。

spatie/laravel-menu

https://github.com/spatie/laravel-menu
階層になっているメニューを生成できる。

spatie/laravel-permission

https://github.com/spatie/laravel-permission
ユーザ、ロール、権限
の制御を簡単にできる。
ユーザ、ロール、権限を
多対多対多で管理するようなアプリでは非常に便利。

ユーザへのロール・権限の付与、はく奪の処理や
ユーザのロール・権限によるアクセス制御などが簡単。

davejamesmiller/laravel-breadcrumbs

https://github.com/davejamesmiller/laravel-breadcrumbs
パンくずリストの表示や管理がしやすくなる。

league/csv

https://github.com/thephpleague/csv
CSVのインポート・エクスポート処理を簡単にしてくれる。

barryvdh/laravel-dompdf

https://github.com/barryvdh/laravel-dompdf
PDF出力処理を簡単にできる。

jenssegers/agent

https://github.com/jenssegers/agent
ユーザエージェントの取得、判定処理をできる。

公式パッケージ

Laravelドキュメント
「公式パッケージ」の欄にいろいろ載っています。

課金システムを作れるCachier
全文検索処理のためのScout
SNSなど外部システム認証を導入するためのSocialiteなど、
もしアプリ要件にあれば導入必須となるような強力なパッケージがいくつかあります。

laravel-awesome-project紹介パッケージ

laravel-awesome-projectのページの
「popular-packages」の欄に、
便利なパッケージがカテゴリ別で大量に紹介されています。
この一覧をざーっと確認して
自分のアプリに導入できるようなパッケージがないか確認するのはおすすめです。

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

【Laravel】 Cron タスクスケジューラの onOneServer() と withoutOverlapping() の違い

はじめに

Laravel には Cron を利用したタスクスケジューリング機能があります。

ところが素でこれをそのまま使うと,ワーカーとして複数インスタンスを立ててスケーリングしている場合などに,重複実行されてしまう問題があります。これを防ぐためには基本は onOneServer() を使っておけばいいのですが,なにやら withoutOverlapping() というものも存在することを知りました。微妙に用途が異なるみたいなので,ここで整理しておこうと思います。

現在時刻が「$H$ 時 $(i - 1)$ 分 $59$ 秒」であるとします。ここから1秒経過して「$H$ 時 $i$ 分 $0$ 秒」になった瞬間のことを考えます。

メソッド 目的
onOneServer() $command$H$ 時 $i$ 分 における実行開始 処理が既に別の場所で発生済みであるとき,ここでの実行をスキップする
withoutOverlapping() $command現在実行中 である場合, ここでの実行をスキップする

この 2 つのオプションは複合するため,以下のような 4 通りのパターンがあります。

$schedule->command($command)->everyMinute();
$schedule->command($command)->everyMinute()->onOneServer();
$schedule->command($command)->everyMinute()->withoutOverlapping();
$schedule->command($command)->everyMinute()->onOneServer()->withoutOverlapping();

図解

所要時間が 1分10秒 である $command というコマンドを考えます。これを 1 分ごと に実行します。またワーカーは 2 インスタンス 立っているとします。

それぞれ順番にタイムチャートで見てみましょう。多分,図を見れば一発で理解できると思います。

オプション無し

$schedule->command($command)->everyMinute();
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|   Time   | 0 | ... | 50 | 60 | 70 | ... | 110 | 120 | 130 | ... | 170 | 180 | 190 | ... |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 1 | *********************                 *******************************        |
|          |                 *****************************                                |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 2 | *********************                 *******************************        |
|          |                 *****************************                                |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+

何の工夫もしない場合です。これはおそらく意図されていない結果です。

onOneServer()

$schedule->command($command)->everyMinute()->onOneServer();
キャッシュドライバに Redis または Memcached を使っている場合
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|   Time   | 0 | ... | 50 | 60 | 70 | ... | 110 | 120 | 130 | ... | 170 | 180 | 190 | ... |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 1 | *********************                 *******************************        |
|          |                 *****************************                                |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 2 |                                                                              |
|          |                                                                              |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
(参考)キャッシュドライバにファイルを使っている場合
オプション無しと同じ

ほとんどの軽量な短周期バッチではこれを選択するとよいでしょう。ワーカーを水平分散したとき, 1 インスタンスだけでタスクが実行されて欲しいと考えるのは自然な思考です。

補足

すべてのインスタンスが共通のキャッシュを見る必要があるため,キャッシュドライバとして Redis または Memcached が必須 要件であることに注意してください。 構成によっては Database でも問題ないように思われますが,アトミックな読み書きが確実に保証されているのは Redis と Memcached のみです。

withoutOverlapping()

$schedule->command($command)->everyMinute()->withoutOverlapping();
キャッシュドライバにファイルを使っている場合
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|   Time   | 0 | ... | 50 | 60 | 70 | ... | 110 | 120 | 130 | ... | 170 | 180 | 190 | ... |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 1 | *********************                 *******************************        |
|          |                                                                              |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 2 | *********************                 *******************************        |
|          |                                                                              |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
(参考)キャッシュドライバに Redis または Memcached を使っている場合
onOneServer() + withoutOverlapping() と同じになる可能性が高いが保証はされていない

withoutOverlapping() は単体ではあまり意味のない選択肢です。これも意図されているケースは無いはずです。

補足

今回のコマンドの場合は Redis または Memcached を使っていると,後述の onOneServer() + withoutOverlapping() の動作と実質同じ動きをしてくれると思いますが,厳密には保証されていません。具体的には,以下のようなケースで失敗します。実行開始時刻にばらつきがあり,コマンドの実行時間がスケジュール周期に対して十分短い場合です。

sleep(mt_rand(0, 5));
$schedule->command('1秒で終わるコマンド')->everyMinute()->withoutOverlapping();

withoutOverlapping() は「コマンド実行中の状態が重複しないこと」しか見てくれません。例えば,Worker 2 で開始されるときに Worker 1 での実行が終了していると,重複して実行されてしまいます。その逆も然りです。

+----------+---+---+---+---+---+-----+----+----+----+----+----+
|   Time   | 0 | 1 | 2 | 3 | 4 | ... | 60 | 61 | 62 | 63 | 64 |
+----------+---+---+---+---+---+-----+----+----+----+----+----+
| Worker 1 |     *****                                 *****  |
|          |                                                  |
+----------+---+---+---+---+---+-----+----+----+----+----+----+
| Worker 2 |             *****          *****                 |
|          |                                                  |
+----------+---+---+---+---+---+-----+----+----+----+----+----+

onOneServer() + withoutOverlapping()

$schedule->command($command)->everyMinute()->onOneServer()->withoutOverlapping();
キャッシュドライバに Redis または Memcached を使っている場合
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|   Time   | 0 | ... | 50 | 60 | 70 | ... | 110 | 120 | 130 | ... | 170 | 180 | 190 | ... |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 1 | *********************                 *******************************        |
|          |                                                                              |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| Worker 2 |                                                                              |
|          |                                                                              |
+----------+---+-----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
キャッシュドライバにファイルを使っている場合
withoutOverlapping() と同じ

withoutOverlapping()onOneServer() と併用して,初めて意味のある選択肢になります。中程度の周期のバッチの実行時間が,突発的に肥大化したりする場合に向いている選択肢です。「タスクが同時実行されると都合が悪いかどうか」で考えるといいでしょう。

  • 「人が手作業で行ってもいい作業を,適当に設定したサイクルで回るように自動化している」 という大雑把なスケジュールの場合, onOneServer() + withoutOverlapping() が適当と言える可能性が高そうです。
  • 「毎回実行することに意味があり,たとえ前のタスクが長引いても毎回実行しなければならない」という正確なスケジュールを求めている場合, onOneServer() のみのほうが適当です。

補足

2 つを併用することで,先程問題になっていた,実行開始時刻にばらつきがあり,コマンドの実行時間がスケジュール周期に対して十分短い場合でも確実に対応することができます。

+----------+---+---+---+---+---+-----+----+----+----+----+----+
|   Time   | 0 | 1 | 2 | 3 | 4 | ... | 60 | 61 | 62 | 63 | 64 |
+----------+---+---+---+---+---+-----+----+----+----+----+----+
| Worker 1 |     *****                                 *****  |
|          |                                                  |
+----------+---+---+---+---+---+-----+----+----+----+----+----+
| Worker 2 |                                                  |
|          |                                                  |
+----------+---+---+---+---+---+-----+----+----+----+----+----+

まとめ

メソッド 目的
onOneServer() $command$H$ 時 $i$ 分 における実行開始 処理が既に別の場所で発生済みであるとき,ここでの実行をスキップする
withoutOverlapping() $command現在実行中 である場合, ここでの実行をスキップする
$schedule->command($command)->everyMinute()->onOneServer();
$schedule->command($command)->everyMinute()->onOneServer()->withoutOverlapping();
  • onOneServer() は,スケーリングを前提とするのであればすべてのコマンドに付けておくべき。また実効性を生むためには Redis か Memcached が必須。
  • withoutOverlapping() は,前のコマンドの実行が終わっていないときにスキップして欲しい場合は onOneServer() と組み合わせて付けておく。実質的には無くても問題ないケースが多い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む