- 投稿日:2021-06-23T21:50:28+09:00
Laravelのリソースコントローラを使ってCRUDを1行でルーティング【実例付】
緒言 Laravelにはリソースコントローラという機能があります。このリソースコントローラを使用すると、CRUD一式がすんなりと作成できます。 利点としては、 ルーティングを1行で済ませることができる。 コントローラで必要なアクションが予め列挙されているため、必要十分な記述ができる。 アクションに対するルート名も決まっている。 などが挙げられます。 Laravelドキュメントのリソースコントローラにより処理されるアクションという項目では、「photo」というリソースコントローラの機能は、以下の表のようにまとめられています。 動詞 URI アクション ルート名 GET /photos index photos.index GET /photos/create create photos.create POST /photos store photos.store GET /photos/{photo} show photos.show GET /photos/{photo}/edit edit photos.edit PUT/PATCH /photos/{photo} update photos.update DELETE /photos/{photo} destroy photos.destroy アクションを簡単に説明すると、indexは全体のリスト表示、createは新規入力、storeはcreateで入力したデータを新規作成、showは詳細表示、editは既存の項目の更新入力、updateはeditで入力したデータを更新、destroyは項目の削除、といったところでしょうか。URI欄の{photo}のところにはidが入ります。 以下、この表から、どのように実装していくかを、実例を挙げて説明します。 今回は、「ひとこと日記」という個人用の日記帳を作成しながら解説をします。 この記事では、Laravel 8.47.0を使用しています。7以下のバージョンと記述方法に違いがあることがあるので御注意ください。また、記事中でSQLiteのデータベースを作成するので、SQLiteのバイナリか、DB Browser for SQLiteなどの操作環境を用意してください。あとは、最低限PHP(7.4以上)とcomposerを入れておいてください。 Windows10とUbuntu20.04.2LTSで動作確認していますが、ディレクトリ区切り文字は/にしてあります。Windows環境では、適宜/を\に読み替えてください。 第1章 リソースコントローラの作成 Laravelをインストールしていなければインストールして、「hitokoto」というプロジェクトを作成します。 composer create-project laravel/laravel hitokoto hitokotoというサブディレクトリ(フォルダ)ができ、その中に各種ファイルが入っています。今後はこのhitokotoの中で作業を行います。VS Codeでは、このフォルダを開くと作業しやすいでしょう。 まず、DiaryControllerというリソースコントローラを作成します。今回はモデルも一緒に指定するので、--model=Diaryをつけます。 php artisan make:controller DiaryController --resource --model=Diary モデルは以下のようなメッセージで同時に作成してくれるので、予め作っておかなくとも構いません。 A App\Models\Diary model does not exist. Do you want to generate it? (yes/no) [yes]: 以下のようなメッセージが出てきたら完了です。 Model created successfully. Controller created successfully. 第2章 データベースとその他の設定 データベースはSQLiteを使う想定で作成します。SQLiteを使用する場合は、.envファイルにDB_CONNECTION=sqliteと指定し、データベースに関するその他の項目はコメントアウトします。 .env DB_CONNECTION=sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 # DB_DATABASE=laravel # DB_USERNAME=root # DB_PASSWORD= 他のRDBMSを使うのであれば、コメントアウトせずに該当する項目に適宜記述してください。 モデルに使うテーブルを作成します。テーブルの列には、日付、題名、内容を用意します。その他に、Laravelが必要とするid、updated_at、created_atも付け加えます。 SQLiteの場合は、指定された場所にデータベースファイルを置いて、その中にテーブルを作成します。ここで注意すべきは、データベースのテーブル名は、モデル名の複数形小文字でなければならないという点です。モデル名でDiaryを指定したので、ここではdiariesになります。 SQLをコマンドラインから入力する場合は、 sqlite3 database/database.sqlite CREATE TABLE "diaries" ( "id" INTEGER, "date" TEXT, "title" TEXT, "content" TEXT, "updated_at" INTEGER, "created_at" INTEGER, PRIMARY KEY("id" AUTOINCREMENT) ); .quit 念を押したいのであれば、.quit の前にselect count(*) from diaries;でテープルの存在を確認しておくと良いでしょう。0件が返ってくるはずです。更に念を押したい場合は、.schemaで定義が見られます。 DB Browser for SQLiteであれば、database/database.sqlを新規作成し、以下のとおり入力します。 app/Modelsに、このテーブルに対応するDiary.phpというモデルが作成されています。これはそのままで使えますので、作成されたことを確認しておけば良いでしょう。 次に、地域設定を行います。resources/langにjaディレクトリを作成しenの内容をコピーします。この中には各種メッセージの文章が入っています。いまは英語版をコピーしただけなので、中身も英語ですが、これを適当に日本語に翻訳して記述すれば、日本語のメッセージが出てくるようになります。とはいえ、全部のメッセージを翻訳するのは容易ではありません。今回は使用する分だけ、validation.phpの'required'を':attribute を入力してください。'と置き換えるにとどめておきます。 これで、一ヶ所しか変わっていないとはいえ、日本語の環境ができました。設定ファイルを修正します。 config/app.php 'timezone' => 'Asia/Tokyo', 'locale' => 'ja', 2ヶ所修正しました。一つはタイムゾーンです。これを設定しないと時刻がUTCになってしまいます。今回は日付を手入力しますが、自動的に日時を取り扱う場合には修正が必須の項目になります。 もう一つが、ロケールを先ほど作成した日本語環境に変更しました。 第3章 ルーティング ルーティングを設定します。ここがリソースコントローラの一番の見どころなのですが、内容はごく簡単です。routes/web.phpを修正します。このファイルを開くと、以下のように出てきます。 web.php <?php use Illuminate\Support\Facades\Route; (中略) Route::get('/', function () { return view('welcome'); }); これに2行加えます。use Illuminate\Support\Facades\Route;のあとにuse App\Http\Controllers\DiaryController;を、Route::get('/', function () {(中略)});のあとにRoute::resource('diary', DiaryController::class);を入れる、これだけです。 web.php(修正後) <?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\DiaryController; //この行を挿入 (中略) Route::get('/', function () { return view('welcome'); }); Route::resource('diary', DiaryController::class); //この行を挿入 これだけです。上の行はLaravel8のお約束みたいなものなので、実質、最後の1行だけで、上にあげた表の7つのルート名のルーティングを行うことができます。 第4章 index,create,store,showの実装 ルートができたので、いままで放置していたコントローラとビューの実装に移ります。app/Http/Controllers/DiaryController.phpを開いてみてください。コメントを除くと以下のような構成になっています。 DiaryController.php <?php namespace App\Http\Controllers; use App\Models\Diary; use Illuminate\Http\Request; class DiaryController extends Controller { public function index() { } public function create() { } public function store(Request $request) { } public function show(Diary $diary) { } public function edit(Diary $diary) { } public function update(Request $request, Diary $diary) { } public function destroy(Diary $diary) { } } ここを埋めていけば、コントローラが完成するという訳です。 まずは、全体のリストであるindexから手をつけます。 DiaryController.php(一部) public function index() { $diary = new Diary; $list = $diary->paginate(5); $count = $diary->count(); return view('diary.index',[ 'list' => $list, 'count' => $count, ]); } 引数はありません。まず必要なのは日記のリストなので、Diaryのインスタンスを作ります。5行ずつに改ページして$listに入れます。また、全件数を表示させたいので、$countに件数を入れて返します。上の表を参考にすると、返すビューはdiary.indexであることが分かりますので、そのように返します。 これに対するビューは、resources/views/diary/index.blade.phpとなり、以下のとおり作成します。views直下ではなく、diaryというサブディレクトリを作ってその中に作成する点に注意してください。 index.blade.php <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ひとこと日記一覧</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body> <div class="p-4"> <h1 class="text-3xl">ひとこと日記一覧</h1> <p class="text-xl p-4">件数:{{$count}}</p> <p class="text-xl p-4"><a href="/diary/create" class="underline">あたらしい日記を書く</a></p> <table class="table-auto border-collapse"> <thead> <tr> <th class="px-4 py-2">ID</th> <th class="px-4 py-2">日付</th> <th class="px-4 py-2">題名</th> <th class="px-4 py-2">編集</th> <th class="px-4 py-2">削除</th> </tr> </thead> <tbody> @foreach($list as $diary) <tr> <td class="border px-4 py-2">{{$diary->id}}</td> <td class="border px-4 py-2">{{$diary->date}}</td> <td class="border px-4 py-2"><a href="/diary/{{$diary->id}}" class="underline">{{$diary->title}}</a></td> <td class="border px-4 py-2"> <form action="/diary/{{$diary->id}}/edit" method="GET"> @csrf <button type="submit" class="border-2">編集</button> </form> </td> <td class="border px-4 py-2"> <form action="/diary/{{$diary->id}}" method="POST"> @method('delete') @csrf <button type="submit" class="border-2" onclick="return confirm('「{{$diary->title}}」を削除してよろしいですか?')">削除</button> </form> </td> </tr> @endforeach </tbody> </table> <div>{{$list->links()}}</div> </div> </body> </html> ペジネーションのリンクを使用するために、Tailwind CSSをCDN経由で使用しています。内容は単純で、件数を表示したあと、見出しを表示し、$listで届いたリストをforeachで繰り返しているだけです。題名は詳細画面へのリンクとなっています。また、編集と削除用のボタンを用意しました。これらについては、後ほど説明します。新規作成へのリンクも必要です。新規作成ページのメソッドはGETでルート名はcreateなので、通常のリンクを作成しておきます。@csrfはPOSTメソッドでのクロスサイトリクエストフォージェリを防ぐためのトークンを埋め込んでいます(公式解説)。 DiaryController.phpに戻って、createの中身を記述します。新規記事の入力画面ですので、特にデータを渡す必要もありません。 DiaryController.php(一部) public function create() { return view('diary.create'); } createに対するビューresources/views/diary/create.blade.phpは、新規入力のためのフォームが必要となります。 create.blade.php <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>あたらしい日記を書く</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body> <div class="p-4"> <h1 class="text-3xl">あたらしい日記を書く</h1> <form action="/diary" method="POST"> @csrf <div class="p-4"><label>日付: </label><input type="date" name="date" id=date class="border-2" value = "{{ old('date') }}"></div> <div class="p-4"><label>題名: </label><input type="text" name="title" id="title" class="border-2" value = "{{ old('title') }}"></div> <div class="p-4"><label class="inline-block align-top">本文: </label><textarea name="content" id="content" class="border-2" rows="5" cols="30">{{old('content')}}</textarea></div> <div class="p-4"><button type="submit" class="border-2">書き込む</button></div> </form> @error('date') <div class="p-2">{{$message}}</div> @enderror @error('title') <div class="p-2">{{$message}}</div> @enderror @error('content') <div class="p-2">{{$message}}</div> @enderror <hr /> <div class="p-4"><a href="/diary">日記一覧へ</a></div> </div> </body> </html> storeがPOSTで受けるので、フォームのメソッドもPOSTにします。また、各項目についてstoreでバリデーションを行います。エラーがある場合はこのビューに帰ってくるので、そのときのエラーを表示する欄を用意するのと、入力済みのデータを消さないよう、<input>と<textarea>にold関数を使用して入力データを保持しています。 続いて、createを受けるstoreをコントローラに記述します。引数にリクエストが含まれています。言うまでもなく、createで投げたフォームの中身が入っています。 DiaryController.php(一部) public function store(Request $request) { $diary = new Diary; $request->validate([ 'date' => 'required|date', //日付型か否か 'title' => 'required|max:50', //最大50文字 'content' => 'required|max:1000', //最大1,000文字 ]); //各データを格納 $diary->date = $request->date; $diary->title = $request->title; $diary->content = $request->content; //新規データとして保存 $diary->save(); //index用データ $list = $diary->paginate(5); $count = $diary->count(); //indexページに戻る return view('diary.index',[ 'list' => $list, 'count' => $count, ]); } まず、フォームから入ってきたデータをバリデーションを行います。dateは日付型、titleは50文字以内、contentは1,000文字以内で、いずれも必須項目となっています。もし、これらが不適合なら前のページに戻って入力し直しとなります。 適合している場合は、$diaryの各項目に代入してsave()すれば新規作成は完了します。 完了後はindexビューを表示するので、前回と同様に$listと$countの内容を返します。 ここまでで、一覧と新規作成ができるようになりました。プロジェクトのルートからphp artisan serveでサーバを起動し、「http://127.0.0.1:8000/diary」を開いてみてください。この時点ではデータは何も入っていないので、件数は0件で項目名だけが出てくるはずです。 データの新規入力はできるようになったので、早速入力してみましょう。「あたらしい日記を書く」というリンクから新規入力画面を開けます。 試しに、日付を入力せずに書き込んでみてください。先には進まず、「dateを入力してください。」と表示が出てきます。モデルの列名の関係で「日付」ではなく「date」という文字が出てくるのが少し残念ですが、ちゃんと日本語で警告が出てきました。 6件以上入力すると、下図のようにリストが複数ページにわたっています。indexとstoreで->paginate(5)と記述しておいたからです。 一覧は表示できましたが、この一覧では内容までは見られません。そもそも、内容は最大1,000文字まで許容しているので、一覧に表示させるのは現実的ではありません。そこで、詳細画面を作成して、1件ずつ見られるようにします。コントローラのshowの部分に記述を行います。 DiaryController.php(一部) public function show(Diary $diary) { $count = $diary->count(); return view('diary.show',[ 'date' => $diary->date, 'title' => $diary->title, 'content' => $diary->content, ]); } 引数にDiaryモデルである$diaryが用意されています。このshowへのリンクは、indexのタイトルの部分に用意しておきました。idを指定してあるので、当該idのデータを持ってきています。なので、そのままdate,title,contentをビューに渡せば完了です。 show.blade.php <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ひとこと日記詳細</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body> <div class="p-4"> <h1 class="text-xl">ひとこと日記詳細</h1> <div class="p-4"><span class="text-2xl">{{$title}}</span> {{$date}}</div> <p class="p-2">{{$content}}</p> <hr /> <div class="p-4"><a href="/diary">日記一覧へ</a></div> </div> </body> </html> これで、一覧の題名をクリックすると、詳細画面が出てくるようになりました。 第5章 edit,update,destroyの実装 続いて、コントローラのeditの作成に移ります。createと同様の画面を作成しますが、こちらは既存のデータの表示をしなければなりません。そのため、引数に$diaryが入っています。言うまでもなく、URIで指定されたidのデータになっています。 DiaryController.php(一部) public function edit(Diary $diary) { return view('diary.edit',[ 'id' => $diary->id, 'date' => $diary->date, 'title' => $diary->title, 'content' => $diary->content, ]); } createと異なるのは、updateのURIに入れるために、idを返している点です。 editに対するビューも作成します。 edit.blade.php <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ひとこと日記編集</title> <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> </head> <body> <div class="p-4"> <h1 class="text-3xl">ひとこと日記編集</h1> <form action="/diary/{{$id}}" method="POST"> @method('PUT') @csrf <div class="p-4"><label>日付: </label><input type="date" name="date" id=date class="border-2" value="{{$date}}"></div> <div class="p-4"><label>題名: </label><input type="text" name="title" id="title" class="border-2" value="{{$title}}"></div> <div class="p-4"><label class="inline-block align-top">本文: </label><textarea name="content" id="content" class="border-2" rows="5" cols="30">{{$content}}</textarea></div> <div class="p-4"><button type="submit" class="border-2">修正する</button></div> </form> @error('date') <div class="p-2">{{$message}}</div> @enderror @error('title') <div class="p-2">{{$message}}</div> @enderror @error('content') <div class="p-2">{{$message}}</div> @enderror <hr /> <div class="p-4"><a href="/diary">日記一覧へ</a></div> </div> </body> </html> こちらもほとんどcreateと同じです。違いは、フォームの初期データがold(xxx)ではなく、保存済みのデータであること、飛ばし先のメソッドをPUTと指定し直している点です。HTMLではGETとPOSTのメソッドしか投げられないので、ここでメソッドを書き換えている形になります。詳細は公式ドキュメントのMethodフィールドの項を参照してください。 editを受けて上書き処理をするのがupdateアクションです。コントローラーにはほとんど同じ記述となりますが、こちらは引数に$diaryがあり、当該データが用意されている点が異なります。ですので、$diary = new Diaryは必要ありません。 DiaryController.php(一部) public function update(Request $request, Diary $diary) { $request->validate([ 'date' => 'required|date', //日付型か否か 'title' => 'required|max:50', //最大50文字 'content' => 'required|max:1000', //最大1,000文字 ]); //各データを格納 $diary->date = $request->date; $diary->title = $request->title; $diary->content = $request->content; //データ保存 $diary->save(); //index用データ $list = $diary->paginate(5); $count = $diary->count(); //indexページに戻る return view('diary.index',[ 'list' => $list, 'count' => $count, ]); } アップデート後にはindexを返すようにしてあります。 最後にdestroyです。index.blade.phpの削除ボタンに割り付けてありますが、ボタンを押しただけで削除されてしまうのは乱暴なので、onclick="return confirm('「{{$diary->title}}」を削除してよろしいですか?')"とワンクッション入れておきました。また、DELETEメソッドもHTMLでは使えないので、@methodを使っている点も確認してください。 コントローラには、以下のように記述します。 DiaryController.php(一部) public function destroy(Diary $diary) { $id = $diary->id; $diary->where('id',$id)->delete(); //指定したIDのデータをdeleteする。 return redirect('/diary'); } 削除したらindexにリダイレクトしています。 結語 これで、DiaryController.phpの記述が全部埋まりました。ビューは4つ作成されています。ルーティングは最初に書いた1行ですべてをカバーしています。モデルには手を触れてもいませんが、ちゃんと動いています。呆気ないくらい簡単に出来てしまいました。php artisan serveでサーバを起動し、操作してみてください。 今回は日記の体を取りましたが、同様にして、データベースに入るものなら何でも、電話帳でも住所録でも、蔵書目録でも汎用メモでも作成することが出来ます。是非お試しください。 なお、これはあくまで個人用と言うことで、誰でも読み書きできるようになっていますが、実際に公開サーバに置くときは必要なところに認証なりをかける必要があろうかと思われます。お気をつけください。 この記事を書くにあたっては、以下のサイト及び書籍を参考にいたしました。ありがとうございます。 Laravel公式及びドキュメント LaravelでHTTPメソッド(CRUD)を使う 徒然草 卜部兼好 枕草子 清少納言 更級日記 藤原孝標女 土左日記 紀貫之 紫式部日記 紫式部 断腸亭日乗 永井荷風
- 投稿日:2021-06-23T19:57:11+09:00
PHP Discord OAuth2認証
PHP Discord OAuth2認証 PHP Laravel環境でDiscord OAuth2認証を行ったので備忘録 環境 ・Larave 5.7 ・PHP 7.2.15 前提 ・Discord Developerで「OAUTH2_CLIENT_ID」「OAUTH2_CLIENT_SECRET」を取得 ・Discord DeveloperでCallback URLの設定 ログイン時 public function login(Request $request) { define('OAUTH2_CLIENT_ID', '<OAUTH2_CLIENT_ID>'); define('OAUTH2_CLIENT_SECRET', '<OAUTH2_CLIENT_SECRET>'); $authorizeURL = 'https://discord.com/api/oauth2/authorize'; $tokenURL = 'https://discord.com/api/oauth2/token'; $apiURLBase = 'https://discord.com/api/users/@me'; $params = array( 'client_id' => OAUTH2_CLIENT_ID, 'redirect_uri' => '<Callback URL>', 'response_type' => 'code', 'scope' => 'identify guilds' ); // Redirect the user to Discord's authorization page header('Location: https://discord.com/api/oauth2/authorize' . '?' . http_build_query($params)); die(); } Callbackで呼び出す関数 public function message_login_callback_discord(Request $request) { define('OAUTH2_CLIENT_ID', '<OAUTH2_CLIENT_ID>'); define('OAUTH2_CLIENT_SECRET', '<OAUTH2_CLIENT_SECRET>'); $authorizeURL = 'https://discord.com/api/oauth2/authorize'; $tokenURL = 'https://discord.com/api/oauth2/token'; $apiURLBase = 'https://discord.com/api/users/@me'; $apiURLGuild = 'https://discord.com/api/users/@me/guilds'; if($request->input('code')) { // 初回のCallback $token = $this->apiRequest($tokenURL, array( "grant_type" => "authorization_code", 'client_id' => OAUTH2_CLIENT_ID, 'client_secret' => OAUTH2_CLIENT_SECRET, 'redirect_uri' => '<Callback URL>', 'code' => $request->input('code') )); // sessionにaccess_tokenを格納 session(['access_token' => $token->access_token]); return redirect('/message_login_callback_discord'); } //上記処理後のCallback if(session('access_token')) { $user = $this->apiRequest($apiURLBase); $guilds = $this->apiRequest($apiURLGuild); return $user; } return redirect('/login'); } その他「API Request 関数」 public function apiRequest($url, $post=FALSE, $headers=array()) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); $response = curl_exec($ch); if($post) curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post)); $headers[] = 'Accept: application/json'; if(session('access_token')) $headers[] = 'Authorization: Bearer ' . session('access_token'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); return json_decode($response); } おまけ「特定のDiscordサーバーに参加しているか確認」 $user = $this->apiRequest($apiURLBase); $guilds = $this->apiRequest($apiURLGuild); $check_suiseicord_join = false; foreach ($guilds as $guild) { if ($guild->id=="<確認したいサーバー(guild)のID>") { $check_suiseicord_join = true; } }
- 投稿日:2021-06-23T18:38:21+09:00
3秒で終わるLaravel開発環境構築
はじめに DockerでLaravelの開発環境構築するか〜と思ってネット上をサーフィンしていたところ、2020年末にLaravelが公式にLaravel Sailというめちゃくちゃ便利なDocker環境をリリースしていたのでまとめました。 Dockerの知識がない方でも簡単に始められます。Laravel Sail初見の方はぜひ騙されたと思ってやってみてください! 環境 macOS Big Sur Docker Desktop 3.3.3 Laravel Sailを使うためにはDocker Desktopのインストールが必要です。 新規Laravelアプリの作成とSailのインストール terminal $ curl -s https://laravel.build/<アプリ名> | bash アプリの作成とsailのインストールは同時に行われます。 これでmysql・redis・meilisearch・selenium・mailhogがデフォルトで利用できるようになりますが、withクエリを使って mysql pgsql mariadb redis memcached meilisearch selenium mailhog から使いたいサービスを個別指定することも可能です。 mysqlとredisを指定する例 $ curl -s "https://laravel.build/example-app?with=mysql,redis" | bash 既存のLaravelアプリにSailをインストールする場合 terminal $ composer require laravel/sail --dev $ php artisan sail:install アプリの起動 terminal $ cd <Laravelアプリ> $ ./vendor/bin/sail up -d http://localhost/にアクセスするとLaravelの初期画面が、http://localhost:8025にアクセスするとMailHogが表示されます。Sailをバックグラウンドで起動するにはデタッチの-dオプションを追加します。 エイリアス設定 呼び出し時に毎回./vendor/bin/sailと入力するのが面倒なので、エイリアスを設定します。こちらの記事を参考にさせて頂きました。 bashシェルの場合 $ alias sail='./vendor/bin/sail' $ source ~/.bash_profile zshシェルの場合 $ alias sail='./vendor/bin/sail' $ source ~/.zshrc コマンド実行 artisanコマンド $ sail artisan make:controller コントローラ名 phpコマンド $ sail php --version composerコマンド $ sail composer require ベンダー名/パッケージ名 nodeコマンド $ sail node --version npmコマンド $ sail npm run dev phpunitコマンド $ sail test 例:Vueのセットアップ コンテナ起動、停止以外は普段通りです。どれだけ普段通りか示すためにVueとの連携手順を載せておきます。 terminal laravel/uiのダウンロード $ sail composer require --dev laravel/ui #この時点でresources/js/components以下にvueコンポーネントが設置される vueの認証スカフォールドの生成 $ sail artisan ui vue --auth #認証機能をつけたい場合(ちなみにlaravelはjetstreamを推奨してます) パッケージのインストール $ sail npm install ビルド $ sail npm run dev #または sail npm run watchで自動コンパイル アプリ(コンテナ)の停止 terminal $ sail down #または'Ctr + C' おわりに いかがでしたか?Docker Desktopがあらかじめインストールされていた方は terminal $ curl -s https://laravel.build/<アプリ名> | bash $ ./vendor/bin/sail up の2行、時間にして約3秒くらいで開発環境構築が完了しましたね!(適当) 自分でDockerfile, docker-composeファイルを作る手間なくMySQL, mailサーバ等開発における必要最低限の機能が用意されるのでめちゃくちゃ便利です。 まだ試してない方はぜひ使ってみてください! 参考
- 投稿日:2021-06-23T16:41:35+09:00
バリデーションでリファラー(URL)とパラメーターが適切であるときにサイトを表示させる処理
要件 ここで満たしたい要件は以下の3つ。 ・特定のサイトから遷移してきた人限定でサイトを表示する。 (直接URLを叩いたら404が返される) ・リファラーについているパラメーターの値と本サイトのパラメーターが同じ値ならサイトを表示する ・①の条件を一度でも満たした人がURLを直接叩いたらサイトが表示されるようにする *Middleweaerの作成が必要なので作成方法がわからない方はこちら (Laravel素人の俺が書いたから間違ってる可能性有りなので他のサイトを参考にした方がいい気はする) https://qiita.com/yoshiakidayo/items/06c90a995c4761e648e3 リファラーとは、アクセスログに記載されている情報のひとつで、当該ファイルを取得する(ブラウザで表示する)直前に閲覧していたページのURLを内容とする情報らしい。 参考サイト https://www.mitsue.co.jp/case/glossary/l_011.html まずリファラーを取得する //url()->previous()でリファラーのURLが取得できる。 $previousUrl = url()->previous(); 参考サイト https://blog.capilano-fw.com/?p=2537 次に適切であるかを判断したいので三項演算子を使用する。 // リファラー(直前のサイト)のURLが適切であればtrue $getUrl = $previousUrl == 'リファラーのURL' ? true : false; 次にパラメーターを取得する //パラメーターが?parameter=testの場合 $params = $request->parameter; 一度訪れたユーザーは閲覧できるようにしたいためセッションを保持させるのでセッションを取得するコードを書く // sessionに保存せされている値を取得 $flag = session()->get('flag'); 条件分を書いていく。 if($getUrl == true){ // リファラー(直前のサイト)のURLが正しければifを実行 if($params == config('app.get_params')){ // 取得したパラメーターが適切であればsessionにflag_truを保存 session()->put('flag', 'flag_true'); }else{ abort(404); } }elseif(!$flag) { // $flagの値が入っていなければ404を返す abort(404); } 完成形 Middlewere public function handle($request, Closure $next) { // リファラー(直前のサイト)のURL $previousUrl = url()->previous(); // リファラー(直前のサイト)のURLが適切であればtrue $getUrl = $previousUrl == 'リファラーのURL' ? true : false; // パラメーターを受け取る $params = $request->parameter; // sessionに保存せされている値を取得 $flag = session()->get('flag'); if($getUrl == true){ // リファラー(直前のサイト)のURLが正しければifを実行 if($params == config('app.get_params')){ // 取得したパラメーターが適切であればsessionにflag_truを保存 session()->put('flag', 'flag_true'); }else{ abort(404); } }elseif(!$flag) { // $flagの値が入っていなければ404を返す abort(404); } return $next($request); } あとはMiddlieweareに登録して上記の処理をTOPページのみに適用させる web.php Route::get('/', 'TopController@index')->middleware('VerifyUrl'); その他のページには新しくMiddleweaerを作成し下記の処理を適用 public function handle($request, Closure $next) { // $getUrl = url()->previous(); // dd($getUrl); $flag = session()->get('flag'); if(!$flag){ abort(404); } return $next($request); } web.php Route::get('shops', 'ShopController@index')->middleware('VerifySession'); Route::view('/faq', 'static/faq')->middleware('VerifySession'); //などなど
- 投稿日:2021-06-23T16:39:22+09:00
Middleweaer作成方法 Laravel
Middlewareの作成方法 ターミナル php artisan make:middleware hoge Http/Middlewareファイルに作成される Middleweareの登録 Kernel.php protected $routeMiddleware = [ ~ 'Hoge' => \App\Http\Middleware\hoge::class, ]; web.php Route::get('/', 'TopController@index')->middleware('Hoge'); これで使えた。 間違ってたらメンゴ
- 投稿日:2021-06-23T16:26:20+09:00
Laravel Mix で video.js
Laravelで動画ストリーミング。 忘れないための落書き。 作業手順 video.js をインストール npm install --save video.js package.json は以下のような感じになる(抜粋) package.json "dependencies": { "video.js": "^7.12.3" } webpack.mix.js で node_modules 配下のファイルを public 配下にコピー webpack.mix.js mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/style.scss', 'public/css') .copy('node_modules/video.js/dist/video.min.js', 'public/js') // videojs用 .copy('node_modules/video.js/dist/video-js.min.css', 'public/css'); // videojs用 画面にプレイヤーを表示 video.php <html> <head> <link href="{{ asset('css/video-js.min.css') }}" rel="stylesheet"> <script src="/js/video.min.js"></script> </head> <body> <video-js id=video with=600 height=300 class="vjs-default-skin" controls> <source src="/path/to/video.m3u8" type="application/x-mpegURL"> </video-js> <script> var player = videojs('video') </script> </body> </html> 動画の保存場所が S3 で CloudFront から読み込む場合 CORS問題を解決しないとローカル環境等の別ドメインで動画を再生できない。 S3のアクセス許可の Cross-Origin Resource Sharing (CORS) を定義する。 [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "HEAD" ], "AllowedOrigins": [ "*" ], "MaxAgeSeconds": 3000 } ] CORS関連に必要なヘッダーが CloudFront を経由してS3に届くようにする。 CloudFront の Behavior の Origin Request Policy に Managed-CORS-S3Origin を追加する。
- 投稿日:2021-06-23T15:03:39+09:00
[Laravel] パフォーマンス改善策 メモ
はじめに この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。 今回は、Laravelに関するパフォーマンス改善策について調べたので、まとめておきたいと思います。(新しい情報を見つけ次第、随時更新します) 間違いなどがございましたら、ご指摘のほどよろしくお願い致します。 パフォーマンス改善策について Laravelというフレームワークは、様々なインターフェース等を利用することで比較的簡単に自分が実装したい機能を実現できてしまいます。この比較的簡単に実装できてしまう点に落とし穴があり、開発者がパフォーマンス面を意識して開発しなければパフォーマンスが落ちてしまいます。 基本的には、公式ドキュメントに沿ってコードを記述すべきですが、ドキュメントの各項目に書かれているコードにどのような役割があり、なぜそのような機能があるのかを理解した上でコードを書くことが重要になってくると思われます。 PHPアプリケーションでは、4つの段階に分けてパフォーマンスを改善できるという情報を目にしました。 4つの段階とは ・言語レベル ・フレームワークレベル ・インフラレベル ・ハードウエアレベル 今回は、PHPレベルの改善策(opcache)とLaravelフレームワークレベルの改善策についてまとめたいと思います。 PHPレベルの改善策(OPcache) OPcacheとは OPcache はコンパイル済みのバイトコードを共有メモリに保存し、PHP がリクエストのたびにスクリプトを読み込み、パースする手間を省くことでパフォーマンスを向上させます。 引用:PHPマニュアル OPcache 本番環境時にphp.iniでOPcacheを有効にします。 ソースコードを変更してもサーバーを再起動しないと変更が反映されなくなるため開発環境時は使用しない方が良いと思います。 一般的なphp.iniの設定 php.ini [opcache] opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 8 opcache.max_accelerated_files = 4000 opcache.validate_timestamps = 0 opcache.huge_code_pages = 0 opcache.preload_user = rootユーザー以外を指定する その他の設定はPHPマニュアル OPcache 実行時設定を参照してください。 Laravelの初期起動を改善する Laravelの初期起動を改善するには、DIとServiceProviderに関する理解が必要となります。 私自身がすぐに復習できるよう簡単にまとめておきます。 DIとは 「Dependency Injection」の略語であり、クラス間およびオブジェクト間の依存関係を外側からコントロールする仕組みのことです。 Laravelを使う場合は、基本的にはDIを利用すべきだと思います。 参考記事 DI・DIコンテナ、ちゃんと理解出来てる・・? 最高にわかるDIコンテナ(特にPHPにフォーカスした Laravelの依存性の注入(DI)を具体的にどう使うのか ServiceProviderとは サービスプロバイダは、Laravelアプリケーション全体の起動処理における、初めの心臓部です。皆さんのアプリケーションと同じく、Laravelのコアサービス全部もサービスプロバイダを利用し、初期起動処理を行っています。 ほとんどのプロバイダは、すべてのリクエストで必ずロードされるとは限らず、そのプロバイダが提供するサービスが、実際に必要なときにのみロードされる「遅延」プロバイダです。 引用:laravel6.x サービスプロバイダ DIのために用いられるLaravelの機能で、依存解決の部分を実装するために用いられます。 DIの量が増えるとServiceProviderの仕事量が増える仕組みとなっています。 ServiceProviderの起動は、全ての処理実行前に実施されるので、ServiceProviderの仕事量が増加すると、その分処理速度が遅くなってしまうといった側面を持っています。 この解決策として、「遅延」プロバイダが存在し、必要になったときに遅延解決する仕組みになっています。 DBへの接続回数を最小限にする DBへの接続回数を最小限にする上で、重要となる知識がEloquentです。 Eloquentに関する知識はリンク先を参照してください。 DBへの接続回数を最小限にするポイントは、どれだけselectの回数を最小限にすることができるかだと思います。 下記にて、selectの回数を最小限に抑える解決策として比較的簡単に取り入れることができるN+1問題の解決策をまとめておきます。 N+1問題とは リレーションを保持しているテーブルのデータを取得する際、 対象となるテーブルのデータを全件取得するのにSQLを1回 取得したデータに紐付く情報(リレーション先のデータ)を取得するのにSQLをN回 といったように、1 + N 回のSQLを発行する必要性が出てきます。 このように、対象となるテーブルのデータ取得件数が増加するほど、紐付く側のデータを取得するSQLの発行回数が増えてしまうことを「N+1問題」といいます。 N+1問題のコード例 public function index() { $users = User::limit(4)->get(); return view('user.index',compact('users')); } //実行結果 //usersのデータに紐づいているpostsデータを取得するSQLがusersの取得件数分だけ発行される //このようにuserデータ4件を取得するSQLを1回発行して、追加でuserデータに紐付くデータを取得するSQLがuserデータ分発行されるの(例だと4回発行する)で、結果として5回発行されることになる(1+4) select * from users limit 4 select * from posts where posts.user_id = 1 select * from posts where posts.user_id = 2 select * from posts where posts.user_id = 3 select * from posts where posts.user_id = 4 解決策 1回のSQLで、データに紐付く情報(リレーション先のデータ)を全て取得することができれば、パフォーマンスの改善が期待できると思います。 LaravelにおけるN+1問題に対する解決策として、withメソッドとloadメソッドが存在します。 loadメソッドとは すでに親のモデルを取得した後に、リレーションをEagerロードする必要がある場合もあるでしょう。たとえば、どの関連しているモデルをロードするかを動的に決める場合に便利です。 引用:laravel6.x リレーション 遅延Eagerロード loadメソッドは、遅延Eagerロード(Lazy Eager Loading)と呼ばれます。 EagerLoadingという機能で発行するSQLを少なくして効率的にDBからデータを取得するメソッドです。 withメソッドとは withメソッドは、loadメソッドと同じでEagerLoadingという機能で発行するSQLを少なくして効率的にDBからデータを取得するメソッドです。 参考文献:laravel リレーション Eagerロード withメソッドを使用した解決策例 public function index() { $users = User::with('posts')->limit(4)->get(); return view('user.index',compact('users')); } //実行結果 //下記のように1つのSQLで複数の紐づいたデータを取得できるようになる select * from users limit 4 select * from posts where posts.user_id in (1, 2, 3, 4) 未使用のAutoloaded Service Providersを削除する Laravelは起動時に大量のサービスをオートロードします。これらはconfig/app.phpファイルの'providers'配列キーの一部として様々なサービスが存在しています。 下記のように、数多くのService Providersがデフォルトで設定されています。 config/app.php /* |-------------------------------------------------------------------------- | Autoloaded Service Providers |-------------------------------------------------------------------------- | | The service providers listed here will be automatically loaded on the | request to your application. Feel free to add your own services to | this array to grant expanded functionality to your applications. | */ 'providers' => [ /* * Laravel Framework Service Providers... */ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, Illuminate\Filesystem\FilesystemServiceProvider::class, Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, /* * Package Service Providers... */ /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, ], 注意点 未使用のService Providersを削除することは好ましいことではあるが、やみくもにコメントアウトして、本番にプッシュしてはいけない。 すべてのテストを実行し、手動でチェックすることでエラーが発生しないことを確認してから本番にプッシュすべきです。 可能な限りキャッシュを使用する キャッシュを使うとDBへの負荷を削減する事が可能になります。 データを、キャッシュから取得する事によって、DBへのリクエストを減らし、結果的に負荷が削減できるようになります。 また、複雑なクエリなどが多い場合は、その結果をキャッシュさせる事によってデータの読み込みが高速化され、リクエスト処理も高速化できるようになります。 Laravelは読み書きしやすい、多くのキャッシュシステムに対する統一したAPIを提供します。キャッシュの設定は、config/cache.phpで指定します。アプリケーション全体のデフォルトとして使用するキャッシュドライバをこのファイルの中で指定します。MemcachedやRedisなど、人気のあるキャッシュシステムをLaravelは最初からサポートしています。 引用:laravel6.x キャッシュ 詳細はリンク先を確認する。 php artisan config:cache php artisan config:cacheとは アプリケーションをスピードアップさせるために、全設定ファイルを一つのファイルへまとめる、config:cache Artisanコマンドを使ってください。これによりアプリケーションの全設定ファイルのオプションが、単一のファイルに結合され、フレームワークが素早くロードできるようになります。 引用:laravel6.x 設定 設定キャッシュ アプリケーションをプロダクションへデプロイする場合、デプロイプロセスの中で、確実にconfig:cache Artisanコマンドを実行してください。 このコマンドは、Laravelの全設定ファイルをキャッシュされる一つのファイルへまとめるため、設定値をロードする場合に、フレームワークがファイルシステムを数多くアクセスする手間を大いに減らします。 引用:laravel6.x デプロイ 使用する際の注意点 ・ローカルでの開発中にこのコマンドを実行する場合は要注意 ・可能な限り本番環境でのみ行う ・実行後、何か問題が発生した場合は、php artisan cache:clearで設定を元に戻す 開発中にconfig:cacheコマンドを実行する場合 開発過程の一環としてconfig:cacheコマンド実行を採用する場合は、必ずenv関数を設定ファイルの中だけで使用してください。設定ファイルがキャッシュされると、.envファイルはロードされなくなり、env関数の呼び出しはすべてnullを返します。 引用:laravel6.x 設定 設定キャッシュ 開発中にphp artisan config:cacheを使うと[.env]が読み込めず内容がnullになってしまう可能性があることを覚えておく必要がありそうです。 また、configファイルが変更されるたびにコマンドを実行することを忘れないでください。 キャッシュをクリアするには、下記のコマンドを使用します。 $ php artisan config:clear php artisan route:cache アプリケーションの設定と同様に、ルートは時間の経過とともにあまり変化しないので、キャッシュの理想的な候補となります。 routesファイルが変更されるたびにコマンドを実行することを忘れないでください。 キャッシュをクリアするには、下記のコマンドを使用します。 $ php artisan route:clear 注意点 この機能はPHPのシリアライゼーション機能を使用するため、アプリケーションの全ルートをキャッシュするには、コントローラベースのルート定義だけを使用してください。PHPはクロージャをシリアライズできません。 引用:laravel6.x デプロイ 画像を扱う場合の最適化 画像は帯域幅を最も消費するものであり、アプリケーションやウェブサイトが遅くなる最大の原因の1つでもあります。アップロードされた画像を単純にサーバに保存し、HTTPレスポンスで送り返すだけでは、パフォーマンスの最適化に反してしまいます。 AWS S3へ画像をアップロードするなど、別の形で画像を扱うべきです。 queuesを使いパフォーマンスを向上させる Laravelのキューサービスは、Beanstalk、Amazon SQS、Redis、さらにはリレーショナル・データベースなどさまざまなキューバックエンドに対し共通のAPIを提供しています。キューによりメール送信のような時間を費やす処理を遅らせることが可能です。時間のかかるタスクを遅らせることで、よりアプリケーションのリクエストをドラマチックにスピードアップできます。 キューの設定ファイルはconfig/queue.phpです。このファイルにはフレームワークに含まれているそれぞれのドライバーへの接続設定が含まれています。それにはデータベース、Beanstalkd、Amazon SQS、Redis、ジョブが即時に実行される同期(ローカル用途)ドライバーが含まれています。 nullキュードライバはキューされたジョブが実行されないように、破棄します。 引用:laravel6.x キュー 例 キューを設定せずに、メール機能を実装(同期処理)すると、メールの「送信する」ボタンを押して次の画面が表示されるまで数秒の待ち時間が発生します。 このようなパフォーマンスを改善する策として、Laravel側でのメール送信を「キュー」と呼ばれる仕組みを使った非同期処理が存在します。キューに関する詳細はリンク先を確認してください。 おわりに 今回は、パフォーマンスの改善策について調べた情報が溜まっていたので、1つの記事としてまとめてみました。記事が完成したので、内容を参考にして実際にパフォーマンスの改善につながるかどうか試してみたいと思います。エラー等が発生した場合、別の記事でまとめたいと思います...。 新しい情報を見つけ次第、随時更新していきます。 参考文献 PHPマニュアル OPcache laravel6.x キュー laravel6.x デプロイ laravel6.x リレーション 遅延Eagerロード laravel リレーション Eagerロード How to Optimize PHP Laravel Web Application for High Performance? 12 Tips for Laravel Performance Optimization in 2020 サイトを37倍に高速化した7つの手法 Laravel を高速化というか最適化する
- 投稿日:2021-06-23T13:36:05+09:00
laravelのModelでテーブル名は明示したほうが良さそうという話
laravelのリレーションの話 リレーションでuserに関連するuser_informationsを取得したい リレーションを貼る public function user_informations() { return $this->hasOne(UserInformation::class); } withメソッドを使おうと思っていたらこんなエラーがでた。 SQLSTATE[42S02]: Base table or view not found: 1146 Table 'laravel_local.user_information' doesn't exist (SQL: select * from `user_information` where `user_information`.`user_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)) テーブルが見つからないらしい。 user_informationを探しているが目的のテーブル名はuser_informationsだ。 その目的のテーブルのもmodelがこれ↓ <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class UserInformation extends Model { use HasFactory; } laravelはテーブル名が指定されてない場合model名をスネークケースにして複数形にしたものをテーブル名として取得するが どうやらまれに複数系を認識できなかったりするらしい。 (informationが普通は不可算名詞でinformationsにはならないから?) なのでこうする <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class UserInformation extends Model { use HasFactory; protected $table = 'user_informations'; } protected $table =とすることでテーブル名を明示できる。 これでエラーは解決。 今日の教訓 たまにmodelがテーブルを認識できないことがあるのでテーブル名は明示したほうがいいのかも。
- 投稿日:2021-06-23T13:21:59+09:00
Laravel / Guzzle (HTTP Client) で画像ファイルを送信したい
Laravel + Guzzle を使用し、テキストや画像を混在させた状態のデータをHTTP Client経由で送りたいときの備忘録です。検証はしてませんがPDFやテキストファイルなど、その他のファイル形式でもいけるはずです。 APIでの画像アップロード(ファイルアップロード)のお供にどうぞ。 検証環境 Laravel 6.20.12 Guzzle 7.2 PHP 7.4 前提条件: base64 と multipart/form-data ファイルの送信にはよく base64 へのエンコードが用いられますが、Guzzleで画像やその他ファイルをエンコードせずに送るためには multipart/form-data という形式で送る必要があるようです。 base64 はざっくり言えば、64進数を意味しており、データを64種類の文字 (アルファベット[ a - z ], [ A - Z ], 数字[ 0 - 9 ]、一部の記号[ +, / ] )で表すエンコード方式です。 Base64 出典: フリー百科事典『ウィキペディア(Wikipedia)』 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 - Base64 base64ってなんぞ??理解のために実装してみた multipart/form-data は Content-Type(メディアの種別を表すもの)の一種で複合データ型を意味し、HTMLの送信フォームでファイルをアップロードしたいときなどによく見かけます。 下記のフォームでは enctype として multipart/form-data を設定しています。この設定がないと画像やPDFなど諸々ファイルアップロードができないので、設定を忘れてフォームが動かなかった、という経験のある方も多いのではないでしょうか。 <form action="/" method="post" enctype="multipart/form-data"> <input type="text" name="foo" value="foobar"> <input type="file" name="fooFile"> <button type="submit">送信</button> </form> HTML フォームにおける Content-Type - MDN - Mozilla 【HTTP】multipart/form-data の boundary って何ぞや? Multipart/form-dataの仕様メモ [フロントエンド] multipart/form-dataを理解してみよう SuikaWiki > Wiki > multipart/form-data (MIME) Guzzle公式ドキュメントより まずはコチラの公式ドキュメントを見てみます。英語だし端的にしか書かれていないので色々と不安になりますが、一度書いて動かしてしまえば大丈夫です。 Guzzle Docs: Request Options - multipart multipart のセクションで、以下のように書いてあります。 Sets the body of the request to a multipart/form-data form. (中略) The value of multipart is an array of associative arrays, each containing the following key value pairs: name: (string, required) the form field name contents: (StreamInterface/resource/string, required) The data to use in the form element. headers: (array) Optional associative array of custom headers to use with the form element. filename: (string) Optional string to send as the filename in the part. リクエストのボディを multipart/form-data 形式で設定します。 (中略) multipartの値は連想配列の配列であり、それぞれに次のキーと値のペアが含まれています。 name:(string, 必須)フォームのフィールド名 contents:(StreamInterface/resource/string, 必須)form要素で使用するデータ。 headers:(array)フォーム要素で使用するカスタムヘッダーのオプションの連想配列。 filename:(string)パーツのファイル名として送信するオプションの文字列。 オプションという表現も、慣れていないと「つまりどういうことだってばよ」という気持ちになったりしますが、元の英文の headers と filename に Optional と書いてあり、そういうときは大体、必須では無い、あってもなくても良い、必要に応じて入れる、という意味になります。 なので、今回の実装で必須なのは name と contents だということになります。 実装 Laravel 7 以上のバージョンでは、デフォルトでGuzzleのパッケージを含むそうで、使い方は以下のドキュメントに書かれています。 Laravel 7.x HTTPクライアント 今回はLaravel 6 以下での話になるので、Laravel 6 以下のバージョンは composer コマンドで guzzlehttp/guzzle パッケージのインストールを行います。 composer require guzzlehttp/guzzle 簡単に書くと、Guzzleを使ったリクエスト(画像ファイル送信)はこんな感じです。 一度Storageに保存した画像をGuzzleで読み込みさせるように実装しました。 use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; use GuzzleHttp\Client; use GuzzleHttp\Psr7; public function postApi($data) { $method = 'POST'; $api_url = 'https://example.com/v1/upload'; $params = [ 'name' => 'token', 'contents' => Auth::user()->token ], [ 'name' => 'product_id', 'contents' => $data['product_id'] ], [ 'name' => 'image_file', 'contents' => Psr7\Utils::tryFopen('../' . Storage::url($data['file_path'])) ]; $client = new Client(); $response = $client->request($method, $api_url, ['multipart' => $params, 'http_errors' => false]); return $response; } headers/Content-Typeについて Guzzle のリクエストタイプには query や form_params を使用することの方が多いかもしれませんが、画像をそのままファイルとして送信したいときは multipart を使用します。 Stack Overflow など見ていると http における headers の内容を配列で別途指定している方もいるようですが、Guzzle ではリクエスト方式に multipart を選んだ時点で Content-Type が multipart/form-data になるため、あえて headers の Content-Type を手動で再設定する必要はありません。 認証などで headers をカスタマイズしなければいけないときのみ手動で設定してください。またその際は、Content-Type を誤った形式で上書きしないように注意してください。 パラメータについて name にはAPIのパラメータ名(フィールド名)、contents にはパラメータとして送りたい内容を入れます。 multipart のときは、ただのテキスト情報も、画像などのファイルも、一緒に送れます。 filename には任意でファイル名を入れられますが、なくても大丈夫です。 ファイルパスについて Guzzleが app ディレクトリと同じ並びの vendor ディレクトリにあるせいなのか、 Storage::url() だけだとパスエラーになったため、パスの先頭に ../ を付けています。(他にもっと良い書き方をご存知の方がいたら教えてくださいmm) その他のパラメータなど ほかに解説記事が豊富にあってここで説明するほどでもないかなと思った項目は飛ばしていますが、「この設定が知りたい」といったリクエストが万が一あれば(素人の解説でよければ)追記します。 という訳で今日はいったんここまで。 不備不足にお気付きの方はご指摘ください。 参考URL Guzzle Docs: Request Options - multipart POST - MDN - Mozilla PHP GuzzleHttp. How to make a post request with params? Multiple files uploaded via Guzzle multipart/form-data request are not recognized by Symfony Sample POST request with Guzzle 【PHP】GuzzleでPOSTリクエストができなかったお話し PHP Http ClientのGuzzleパッケージを使う 今時のPHP HTTPクライアントのGuzzleを使ってみた Guzzleを使って外部APIへPOST送信
- 投稿日:2021-06-23T13:08:53+09:00
Laravelスケジュールの実行ログを出力する
概要 Laravelでスケジュール機能を使って定期実行する際、実行ログを出力したいといったことがあったので調べたことをメモしておく。 ファイル構成 こんな感じでdockerのcron環境を整備する。 ※ ソースコード: Github-reflet/laravel5.6 ├ docker // ← docker関連のフォルダ │ └ cron │ ├ cron.root │ ├ Dockerfile // ← cron実行するコンテナ (PHP-CLI) │ └ php.ini │ ├ src // ← Laravel関連のフォルダ │ ├ app │ │ ├ Console │ │ │ ├ Commands │ │ │ │ ├ EmailSendCommand.php // ← メールをテスト送信するコマンド │ │ │ │ └ Schedule.php // ← schedule:runコマンド を上書きする │ │ │ └ Kernel.php // ← スケジュール設定 │ │ ... │ │ ├ resources │ │ └ views │ │ └ emails │ │ └ test.blade.php // ← テストメール文面 │ │ ... │ │ └ yarn.lock │ └ docker-compose.yml コマンド作成 テストメールを送信するプログラムを追加する。 $ docker-compose exec php php artisan make:mail TestEMail ./src/app/Mail/TestEMail.php protected $body; public function __construct(string $body = '') { $this->body = $body; } public function build(): TestEMail { return $this ->from('hoge@example.com') ->view('emails.test') ->with([ 'body' => $this->body ]); } 定期実行するテストメール送信コマンドを追加する。 $ docker-compose exec php php artisan make:command EmailSendCommand ./src/app/Console/Commands/EmailSendCommand.php <?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; use App\Mail\TestEMail; class EmailSendCommand extends Command { protected $signature = 'email:test'; ... public function handle() { $this->info(sprintf( '[%s] email:test command start.', Carbon::now()->format('Y-m-d H:i:s') )); $message = 'バッチ実行だよ。'; Mail::to('hoge@example.com') ->send(new TestEmail($message)); $this->info(sprintf( '[%s] email:test command complete.', Carbon::now()->format('Y-m-d H:i:s') )); } } スケジュール設定 Kernel.phpに設定を追加する。 ./src/app/Console/Kernel.php protected function schedule(Schedule $schedule) { // ↓ 1時間ごとに定期実行する $schedule ->command('email:test') // 実行するコマンド ->everyMinute() // 実行間隔: 毎分 ->environments(['local']) // ローカル環境でのみ実行してみる ; } logging設定 ログにscheduleチャンネルを新規に追加する。 ./src/config/logging.php 'channels' => [ ... // ↓ これの設定を追加する 'schedule' => [ 'driver' => 'daily', 'path' => storage_path('logs/schedule.log'), 'level' => 'info', 'permission' => 0666, 'days' => 7, ], ... ] schedule:runコマンド Laravelの $ php artisan schedule:run コマンドを上書きする。 ./src/app/Console/Commands/Schedule.php <?php namespace App\Console\Commands; use Illuminate\Console\Scheduling\ScheduleRunCommand; use Illuminate\Console\Scheduling\Event; use Illuminate\Support\Facades\Log; class Schedule extends ScheduleRunCommand { public function __construct(\Illuminate\Console\Scheduling\Schedule $schedule) { parent::__construct($schedule); } protected function runEvent($event) { // ↓ スケジュール実行ログを出力する Log::channel('schedule') ->info(sprintf('Running scheduled command: %s', $event->getSummaryForDisplay() )); // ↓ スケジュール実行結果ログを出力する $handlers = Log::channel('schedule')->getLogger()->getHandlers(); foreach ($handlers as $handler) { // dailyのログローテートしているので、そのハンドラーから情報取得する if ($handler instanceof \Monolog\Handler\RotatingFileHandler) { // ログのパスを取得する $path = $handler->getUrl(); $event->appendOutputTo($path); // 実行結果をログ出力する } } parent::runEvent($event); } } 動作確認 cronの実行状況を確認してみる。 $ docker-compose logs cron cron_1 | Running scheduled command: '/usr/local/bin/php' 'artisan' email:test >> '/var/www/www.example.com/storage/logs/schedule-2021-06-23.log' 2>&1 ログの出力状況を確認してみる。 ./src/storage/logs/schedule-2021-06-23.log [2021-06-23 11:22:01] local.INFO: Running scheduled command: '/usr/local/bin/php' 'artisan' email:test >> '/var/www/www.example.com/storage/logs/schedule-2021-06-23.log' 2>&1 [2021-06-23 11:22:02] email:test command start. [2021-06-23 11:22:02] email:test command complete. 問題ないようです。 参考サイト Laravel 5.6 タスクスケジュール Laravel 5.6 Artisanコンソール Laravel 5.6 メール 以上
- 投稿日:2021-06-23T10:55:47+09:00
laravel 任意のフィールドの値が指定したものと一致していない時に除外するバリデーションルールを記載する方法
目的 laravelで任意のフィールドの値が指定したものと一致していない時に当該フィールドのバリデートをスキップする方法をまとめる 方法 フィールドcheckの値に「true」という文字列以外が格納されたとき以外に、フィールドinput_strの値のバリデーションをスキップしたい時は下記のように記載する。 input_strの値のバリデーションルールは「必須」と「文字列であること」とする。 下記はリクエストファイルのrules()メソッドの内容のみ抜粋して記載する。 HogeRequest.php /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'check' => 'required', 'input_str' => 'exclude_unless:check,true|required|string', ]; } 任意のフィールドの値が指定したものと一致していない時のみ除外してほしいバリデーションルールの頭にexclude_unless:フィールド名,指定された値を記載する。 参考文献 https://readouble.com/laravel/6.x/ja/validation.html
- 投稿日:2021-06-23T03:01:31+09:00
ララベルで久々にプロジェクトをクローンしたのでつまづきポインの備忘録(forMac)
自分のプロジェクトをクローンしてデータベースのモックアップ(テスト環境)をローカルに作成しようと思ったのですが、前にプロジェクトをクローンした時と同じ轍を踏んでいる気がしたのでいい加減覚えようと戒めの意味も込めて備忘録を作成します。 1.プロジェクトのクローン これは簡単ですねサクッとローカルにクローンします。 simple_db_mockup % git clone https://github.com/mar-gitacount/simple.git うまくいったぽいです。 Cloning into 'simple'... remote: Enumerating objects: 554, done. remote: Counting objects: 100% (554/554), done. remote: Compressing objects: 100% (307/307), done. remote: Total 554 (delta 304), reused 463 (delta 213), pack-reused 0 Receiving objects: 100% (554/554), 1.06 MiB | 4.62 MiB/s, done. Resolving deltas: 100% (304/304), done. 中身もチェックします。プロジェクトはsimple_db_mockup/simple直下にあるので、 移動して中身をチェックします。 % cd simple % ls README.md dist server.php app docker-compose.yml server.txt artisan flowmemo.txt serverlog.txt bootstrap package-lock.json src composer.json package.json storage composer.lock phpunit.xml tests composer.phar public config resources webpack.mix.js database routes とりあえずは大丈夫そうです。 2.MAMPの設定 ローカ環境ではMAMPを利用しており、ここでは割愛しますが、htdocs直下にプロジェクトをクローンしています。 ちなみに自分の環境ですと以下になります。 /Applications/MAMP/htdocs/s_p/simple_db_mockup/simple MAMPは大変便利でバーチャルホストの設定をすることでプロジェクトのホスト名を任意で設定できます。MAMPではNginxとApacheが提供されていますが、Apacheを利用を前提に説明します。 2-1バーチャルホストにプロジェクトの追加 バーチャルホストにhtdocs以下のパスとホスト名を設定します。 vi /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf パスが/Applications/MAMP/htdocs/s_p/simple_db_mockup/simple/public で、エイリアスをdbmockとします。 portはMAMPの初期設定の80ポートを利用します。 ~ <VirtualHost *:80> DocumentRoot "/Applications/MAMP/htdocs/s_p/simple_db_mockup/simple/public" ServerName dbmock </VirtualHost> ~ hostsファイルを書き換える。 ホストファイルを書き換えてIPアドレスとホスト名を紐づけます。これはMAMPではなくMac側の設定です。ファイルの場所はprivate/etc/hostsとなります。 他の方はわかりませんが、おそらくデフォルトでは管理者権限出ないとファイル編集ができないのではと思います。 sudo vi /private/etc/hosts 以下のようなファイルが表示されるかと思います。 ~ 127.0.0.1 localhost 127.0.0.1 s_p 127.0.0.1 js_study 127.0.0.1 neomedia 127.0.0.1 liqid_hp 127.0.0.1 dbmock ←追加 ~ 3..envファイルを書き換える。 とりあえずアクセスしてみます。 ホスト名をdbmockとしましたのでdbmockとアドレスバーに入力します。 しかし接続しても画面が真っ白になってしまい、表示されません インターネッツで調べるとどうやらcomoseをインストールしないといけないみたいです。 %composer install Laravel Framework 8.18.1 Usage: command [options] [arguments] Options: -h, --help Display help for the given command. When no command is given display help for the list command -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question --env[=ENV] The environment the command should run under -v|vv|vvv, --verbose Increase t ~ とりあえず正常に行きました。 今度こそはと思い、dbmockに移動しますが、500servererrorとなります。 先ほどとは内容が変わりました。 またもやインターネッツの旅に出かけます。 どうやら.envファイルが存在しないのが問題のようです。前も同じことしたような、、 その上でキーを作成しないといけないようです。 プロジェクトファイル内に.env.exampleファイルがありますのでこれをコピーして.env ファイルを作成します。 cp .env.example .env % php artisan key:generate Application key set successfully. でもう一回接続しますが今度はデータベース関係のエラーが吐かれています。 自分的にはこの辺は何度もやっていたので予測はしていました。 4.データベース設定 今まで利用していたデータベースとは違う場所で作成し、実験的に作業したかったので, 新しく作業したかったので作成します。 4-1データベース作成 DB名はdbmockとします。コマンドラインでやるのは面倒だったので、phpmyAdminを利用して作成します。 4-2.設定ファイルの修正 .envファイルとconfig/database.phpを修正します。 .envファイル(修正前) DDB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=mysql DB_USERNAME=root DB_PASSWORD=root DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock .envファイル(修正後) DDB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=dbmock ←編集 DB_USERNAME=root DB_PASSWORD=root DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock 修正後マイグレーションします。 % php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (51.64ms) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (50.18ms) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (53.27ms) Migrating: 2020_12_12_192229_create_votings_table Migrated: 2020_12_12_192229_create_votings_table (26.06ms) Migrating: 2020_12_23_191814_create_articles_table Migrated: 2020_12_23_191814_create_articles_table (62.01ms) Migrating: 2020_12_29_161423_remove_article_title_from_articles_table Migrated: 2020_12_29_161423_remove_article_title_from_articles_table (34.97ms) Migrating: 2021_01_20_140834_change_aritcle_articles Migrated: 2021_01_20_140834_change_aritcle_articles (61.61ms) Migrating: 2021_04_16_130836_add_column_to_user_prf_table Migrated: 2021_04_16_130836_add_column_to_user_prf_table (30.66ms) Migrating: 2021_06_02_094739_create_sessions_table Migrated: 2021_06_02_094739_create_sessions_table (102.78ms) Migrating: 2021_06_15_130739_add_column_to_gunle_table Migrated: 2021_06_15_130739_add_column_to_gunle_table (28.16ms) テーブルもできています!! dbmodkにアクセスしてみます。大丈夫そうです。 以上です。