20211125のlaravelに関する記事は6件です。

laravel クエリビルダ or条件をグループ化する

目的 クエリビルダでor条件をグループ化する方法をまとめる。 概要 usersテーブルにflagカラムがあったとする。 flagカラムにはintの0、intの1、nullいずれか格納されている。 where句を用いてflagカラムが1もしくはnullのレコードを取得したい。 一見where('flag', '!=', 0)とすれば良さそうだが、nullはwhereでは判定できない。 orWhereNull('flag')とwhere('flag', '=', 1)を用いれば良さそうだが、いずれこちらのクエリビルダが複雑になった時にorWhereNull('flag')があると絞り込み条件が「その他のwhere条件、もしくはflagカラムがnullのもの」となり、意図しない値が返される可能性がある。 方法 下記のように記載することで問題は解決する。(と思う。) $users = DB::table('users') ->where(function($query) { $query->where('flag', 1) ->orWhereNull('flag'); }) ->get(); 参考文献 https://readouble.com/laravel/8.x/ja/queries.html(OR WHERE句 部分) 関連記事 https://qiita.com/miriwo/items/2cbe413453eb0b35de0b
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

11月25日の学習

本日はテックアカデミーの最初のメンタリングがありました。 その中でQiitaというサービスをメンターさんから教えてもらい、始めました。 現在はレッスン9のチャプター2まで進んでいます。 これから毎日学習報告をさせていただければと思います。 気づいたことや感じたことを逐一書いていきます。初心者ですが、よろしくお願いします。 本日の進捗 テックアカデミー レッスン8の課題に取り組みクリア My SQLの学習に入る。 今日はレッスン8の課題に取り組みましたが、nl2brの関数に手こずったり、h3タグをtableに入れてしまうというミスがありました。もう一度bootstrapのカリキュラムを見直ししたりしないといけないなと感じました。 これからひとまず25分のポモドーロ・テクニックで集中力を切らさないように学習していきたいです。 明日からMySQLの学習に入ります。また新しいことですが、一生懸命頑張りたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel blade trying to get property 'id' of non-object () 

optional($item)->id
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Desktop + WSL2 + Laravel が遅いので高速化したい

はじめに 元々は Vagrant + Homestead 環境にて開発を行っていましたが Docker を使ってみたくなり環境を乗り換える事に Docker Desktop を導入し環境を構築するところまでは良かったのだが Homestead の時と比べて明らかに遅かった為、高速化するまでのお話 環境 Docker Desktop WSL 2 Laravel Vue3 想定読者 docker 導入したけど、なんか遅くない?と思っているピンポイントの人向け docker 導入については、私自身参考にさせて頂いた記事を残しておきます どんなシステム構成なのか フロントが Vue3 でバックエンドに Laravel を使用している、何の変哲もない構成 フロント及びバックエンドは、同一サーバ上に配置 Vagrant から Docker への乗り換え Windows10 Home でも Docker が使える事を知り、ノリで乗り換えを決意 詳しい手順等については以下を参考にさせて頂きました ありがとうございます 高速化に向けて ブラウザの開発者ツールにて確認したところ、画面表示完了まで 3 ~ 4 秒掛かっており明らかに遅くなっている 元々は 0.2 秒ほどだったため、10 倍以上遅くなっている なぜ遅いのか? 色々調べているうちに似たような悩みを抱えている人がおり、解決している様に見える そこでの原因として Windows(ホスト) と Linux の OS ファイルシステム間でのファイルの読み込みが遅い どういう事なのか Windows と Linux では HDD のフォーマット形式が違い、その違う環境同士でファイルのやり取りを行うと時間が掛かる という事からくりでした なんでそんなことになるのか Windows(ホスト)の C ドライブのファイルは WSL 上の「/mnt/c/」等にマウントされます なので、特に意識せずに C:\work この様な Path にて docker を起動すると /mnt/c/work のファイルを WSL が読みに行ってしまい、時間が掛かってしまいます どう解決するのか 色々な記事に記載されいる解決策として /mnt/以外に置く ¥¥wsl$Ubuntu¥home¥ に置く という解決策があるのですが、知識が浅い私は で、どうしたらいいの? と結局解決に至らずもやもや 高速化しよう ベストプラクティスなのかは分からないが早くはなりました Ubuntu 導入 Microsoft Store から「Ubuntu 20.04 LTS」をインストール ※多分 Ubuntu じゃなくても良いと思います コマンドでも導入する事が出来そうですが、今回は Store からインストールしました \\wsl$ エクスプローラーで表示すると、Ubuntu が追加されているかと思います ※私の場合は、「Ubuntu-20.04」でした プロジェクト配置場所の変更 今回は以下ディレクトリへ配置することにします \wsl$\Ubuntu-20.04\home 先ほど追加された、Ubuntu を開きプロジェクトを配置 いざ docker 起動 Ubuntu のターミナルを起動し docker compose build docker compose up -d ブラウザからアクセスし起動確認 ・・・早くなってるはず! docker コマンドがエラー ① The command 'docker' could not be found in this WSL 1 distro. We recommend to convert this distro to WSL 2 and activate the WSL integration in Docker Desktop setting WSL バージョンが 1 の為、バージョンアップしましょう $ wsl -l -v NAME STATE VERSION * Ubuntu-20.04 Running 1 $ wsl --set-version Ubuntu-20.04 2 docker コマンドがエラー ② The command 'docker' could not be found in this WSL 2 distro. We recommend to activate the WSL integration in Docker Desktop settings. Docker Desktop > 設定 > Resources > WSL INTEGRATION にて「Ubuntu-20.04」のトグルを有効化する 早くなったのはいいけど、別の問題が 編集、ビルド出来ない? 私は VSCode にて作業をしているのですが、wsl 上にプロジェクトを配置したため 何やら、ファイルの編集やビルドが出来ない。。。 VSCode の Extention を導入にて解消しました VSCode を起動する際は、ubuntu のターミナルにて開きたいディレクトリへ移動後 code . にて起動 ※VSCode 上からも起動可能な為、どちらでも良いです VSCode の拡張が消えた? 消えたわけではなく、Local と WSL 上では別管理の様です VSCode の Extentions タブを開くと WSL UBUNTU - INSTALLED が空っぽの為、Local と同じ Extention で良ければ、Install Local Extensions in 'Remote'... (雲のアイコン)にて、適用出来ました ビルドについて 以前までは、VSCode の terminal にて「npm run watch」でビルドしていましたが、コマンドが通りません ubuntu のターミナルにて「npm run watch」を実行しておき VSCode 上での更新を検知して build が走る事を確認 ブラウザを更新し、変更内容が適用されている事を確認して完了 おわりに もうちょっといいやり方あるんじゃないかと思っているが、現状方法が分からない為一旦これで進めようと思う 違うやり方や効率的な方法があれば教えてもらえると嬉しいです お世話になった記事 ありがとうございます! Docker 導入編 高速化編 ubuntu 開発環境
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モダンフロントとLaravelでCRUD処理をやってみる

はじめに 【マイスター・ギルド】本物の Advent Calendar 2021の4日目の記事です。 他にもROCKな記事ばっかりなので要チェック!!! 本記事は筆者が以下の2つの記事を自身の学習用にまとめたものです。 大変参考にさせていただきました。ありがとうございます。 『Laravel CRUD』 『LaravelでCRUD』 モダンフロントとLaravelの組み合わせを想定しているので、bladeは使いません。 フロント部分についてはコードは載せますが、詳細は触れません。 Laravel側へリクエスト投げていれば何でも大丈夫です。今回はReactとaxiosを用います。 執筆の経緯 LaravelでCRUDをやってみたいが、bladeを使っている記事ばかり。 モダンフロントとLaravelの記事が少ない・・・。 なかったら作れ!(ハック精神) 想定読者 前回の記事で環境構築された方。 記事の通りに環境構築された場合はバージョンは以下になっているはず。 PHP : 7.3.32 Composer : 2.0.14 Laravel : 6.20.41 Laravel初心者の方。 LaravelおよびMVCモデルの基礎について簡単に理解している前提で話を進めます。 今回のゴール 簡単なCRUD処理を実装する。 タスク管理アプリを作る。 実行環境 PC : MacBookAir(M1, 2020) OS : macOS Big Sur11.4 チップ : Apple M1 メモリ : 16GB DockerDesktop : 3.5.2 Docker : 20.10.7 目次 何を作るか Laravelの前準備 処理のイメージ Controllerを作成する。 web.phpを編集し、ルーティングを設定する。 .envを編集する Modelおよびmigrationを作成し、テーブルを作成する Seederを作成して実行し、サンプルレコードを作成する フロントエンドの完成形 【要件1の実装】データベースからレコードを読み込む(Read) 【要件2の実装】データベースへレコードを挿入する(Create) 【要件3の実装】データベースの情報を更新する(Update) 【要件4の実装】データベースの情報を削除する(Delete) リポジトリ 何を作るか 簡単なタスク管理アプリを作成します。簡単な要件は以下の通り。 【要件1】ページ遷移時にデータベースから既存のタスクがfetchされている。 【要件2】タスクを追加することができる。 【要件3】「変更する」ボタンを押下すると状況が「作業中」と「完了」のトグルで入れ替わる。 【要件4】「削除する」ボタンを押下するとタスクが削除される。もちろんDBからも消えている。 Laravelの前準備 Laravelの処理の簡単なイメージ とりあえずModel、View、Controller、Route、DBが分かっていればOK。小売店と卸問屋で例えてみましょう。 View ブラウザ株式会社という小売店のスタッフ。お客さんに画面という情報を提供している。 今回はここがbladeじゃなくてReact。 Route サーバー株式会社という卸問屋の電話番。 Controller サーバー株式会社の営業マン。 Model サーバー株式会社の在庫管理の部署。 DB サーバー株式会社の倉庫。 イメージ Viewから商品問い合わせの電話が入った。 Routeが電話を受けて、担当営業マンのControllerに繋ぐ。 ControllerがModelへ在庫を確認。 ModelはDBへ在庫を確認、Viewへ発送する。 ControllerがViewへ納期を伝える。 イメージ違ってたらご指摘いただけると幸いです。 Controllerを作成する。 Controllerを作成する 中身は後ほど実装するので、とりあえず作成だけしておく。 backendコンテナの中 php artisan make:controller TodoController web.phpを編集し、ルーティングを設定する。 以下の通り設定。 これでpublic/fetch_todosにgetメソッドでリクエストが来たら、TodoControllerのfetchTodosメソッドを実行するよう設定ができている。 前述の通りcontrollerの中身は後ほど実装します。 routes/web.php Route::get('fetch_todos', 'TodoController@fetchTodos'); .envファイルを編集する Laravelプロジェクト直下にある.envファイルを以下の通り修正。 dockerは各コンテナ間のネットワークも自動で作成をしてくれている。 そのためLaravel(backendコンテナ)からMySQL(dbコンテナ)へサービス名でアクセスできるようになっている。 つまり「DB_HOST」の値は「db」でOK。 .env(一部抜粋) DB_CONNECTION=mysql // dockerで環境構築した場合は、docker-compose.ymlデータベースのコンテナのサービス名を入力する。 DB_HOST=db DB_PORT=3306 // 任意のデータベース名。先にDBコンテナに入ってデータベースだけは作成しておくこと。 DB_DATABASE=tasuku // rootユーザーは初期設定で存在しているので、このままでOK。 DB_USERNAME=root // docker-compose.ymlで設定したパスワードを入力。 DB_PASSWORD=pass Modelおよびmigrationを作成し、テーブルを作成する modelファイルとmigrationファイルを作成する migrationファイルとは簡易に書け、なおかつ複数実行可能なSQL文の集まりのようなものです。 -mオプションをつけることで、modelと一緒にmigrationも作成されます。 今回はタスクを格納するtodosテーブルのみ作成します。 model名は単数形にすること。(migrationファイルは複数形名で自動生成されている) ってことはmodelはきっとテーブルの各レコードってことなんだろうか。 backendコンテナ内 php artisan make:model Models/Todo -m migrationファイルを編集 ->nullable(false)のような書き方をすることで、該当のカラムにオプションを付与します。 私はいつも頭の中でSQLを思い浮かべてからどう書くのか検索したりしてます。 database/migrations/(作成時の日時)_create_todos_table.php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTodosTable extends Migration { public function up() { Schema::create('todos', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name', 255)->nullable(false); $table->string('status', 255)->nullable(false); $table->timestamps(); }); } public function down() { Schema::dropIfExists('todos'); } } migrationを実行 事前にdbコンテナ内で.envで指定したデータベースを作成しておかないとエラーになるので注意。 実行したらdbコンテナ内でテーブルができているか確認。 SQLポチポチ叩かなくて良いので便利! backendコンテナ内 php artisan migrate Seederを作成して実行し、サンプルレコードを作成する Seederファイルを作成する Seederファイルとはサンプルのデータを作成できるファイル。 さっきのmigrationがcreate table文の簡易版とすると、seederはinsert文の簡易版。 backendコンテナ内 php artisan make:seeder TodosTableSeeder Seederファイルを編集 適当にファイルを作成する。 id,created_at,updated_atはSeederには入力不要。 初期だとDBがimportされていないので、追記すること。 database/seeds/TodosTableSeeder.php use Illuminate\Support\Facades\DB; use Illuminate\Database\Seeder; class TodosTableSeeder extends Seeder { public function run() { DB::table('todos')->insert( [ [ 'name' => '買い物', 'status' => '作業中' ], [ 'name' => '料理', 'status' => '作業中' ], [ 'name' => '洗濯', 'status' => '作業中' ] ] ); } } DatabaseSeederへの登録 後述のseedコマンドを実行すると、このファイルが実行される。 つまりこのファイルに先ほど作成したSeederを記載しないと意味なし。 database/seeds/DatabaseSeeder.php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(TodosTableSeeder::class); } } Seederファイルの実行 dbコンテナにデータが入っていることを確認。 backendコンテナ内 php artisan db:seed フロントエンドの完成形 一部抜粋です。完成系は本記事の終わりのgithub参照。何やっているか雰囲気だけ掴んでもらえれば。 Reactで書いてますが、画面表示時にLaravelへgetメソッド投げれたら何でも良いです。 Vue.jsの場合はcreated()にfetchTodosメソッド、それ以外のメソッドはv-onで制御すればOKかと思います。 バニラJSの場合はaddEventListener駆使してください。 Main.tsx import { useState, useEffect, ChangeEvent } from 'react' import { client, postMethod } from './lib/axios' import { createURLSearchParams } from './utils' interface TodoType { id: string name: string status: string } const WORK_IN_PROGRESS = '作業中' const DONE = '完了' export const Main = () => { const [todos, setTodos] = useState<TodoType[]>([]) const [todoName, setTodoName] = useState('') const bindTodoNameValue = (event: ChangeEvent<HTMLInputElement>) => { setTodoName(event.target.value) } // 要件1 const fetchTodos = async () => { const { data } = await client.get<TodoType[]>('/fetch_todos') setTodos(data) } useEffect(() => { fetchTodos() }, []) // 要件2 const pushTodo = () => { const params = createURLSearchParams<TodoType>([ ['name', todoName], ['status', WORK_IN_PROGRESS], ]) postMethod('push_todo', params).then((_response) => fetchTodos()) } // 要件3 const changeStatus = (id: string, status: string) => { const statusParam = status === WORK_IN_PROGRESS ? DONE : WORK_IN_PROGRESS const params = createURLSearchParams<TodoType>([ ['id', id], ['status', statusParam], ]) postMethod('change_status', params).then((_response) => fetchTodos()) } // 要件4 const deleteTodo = (id: string) => { const params = createURLSearchParams<TodoType>([['id', id]]) postMethod('delete_todo', params).then((_response) => fetchTodos()) } return ( <> <p>タスクを追加する</p> <input onChange={bindTodoNameValue} type="text" /> <button onClick={pushTodo}>追加する</button> <ul> {todos.length > 0 && todos.map(({ id, name, status }, index) => { return ( <li key={id}> <p>Index:{index + 1}</p> <p>タスク名:{name}</p> <p>状況:{status}</p> <button onClick={() => changeStatus(id, status)}> 変更する </button> <button onClick={() => deleteTodo(id)}>削除する</button> </li> ) })} </ul> </> ) } 【要件1の実装】データベースからレコードを読み込む(Read) 処理が簡単なので、まずはReadからやります。 TodoControllerを以下の通り実装。 header("Access-Control-Allow-Origin: *")を付けないとCSRFのエラーが出ます。 簡単に言うと「違うドメインから通信来ても受け入れるよ」という設定です。 本来はちゃんとした設定をしないといけないんですが、今回は省略します。 migrationの時と似たような書き方でselect文を書いているイメージです。 json形式に変換してレスポンスを返します。 Todo::select()のようにModelを利用してDBへアクセスしていますが、DBから直接取ってくる実装方法もあります。 役割を考えると、Modelを利用するべきではと考えています。 TodoController.php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class TodoController extends Controller { public function fetchTodos() { header("Access-Control-Allow-Origin: *"); $todos = Todo::select('id' ,'name', 'status')->get(); return json_decode($todos); } } 画面をリロードしてみて、きちんとfetchできているか確認。 Chromeのdeveloperツールでも確認。200番返ってきてますね。 【要件2の実装】データベースへレコードを挿入する(Create) web.phpを編集してルーティングを追加 クライアント側から情報を受け取る時は大事な情報が入っている場合もあるでしょうから、post通信にします。 ちなみにそのままaxiosでLaravelにPOSTすると、エラーコード419が返ってきます。 これはCSRFトークンが埋め込まれていないためのエラーです。 app/Http/Kernel.phpの$middlewareGroupsの\App\Http\Middleware\VerifyCsrfToken::classをコメントアウトすれば一旦解決。 根本解決するにはリクエスト内にCSRFトークンを埋め込む必要がある。(勉強中) bladeを使用する場合はとても簡単に解決可能ですが、Laravelでフロント作る気になれなかったので今回は割愛。 routes/web.php <?php Route::get('fetch_todos', 'TodoController@fetchTodos'); // 追加 Route::post('push_todo', 'TodoController@pushTodos'); TodoControllerを以下の通り実装 フロント側から受け取った情報は$request内に格納されている。 $todo = new Todo()でModelの新しい営業担当を呼び出したイメージ。 $requst->input('name')はバニラPHPで言えば、$_POST['name']というところ。 $todo->save()でDBに格納できる。 TodoController.php <?php namespace App\Http\Controllers; // 追加 use App\Models\Todo; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class TodoController extends Controller { public function fetchTodos() { header("Access-Control-Allow-Origin: *"); $todos = Todo::select('id' ,'name', 'status')->get(); return json_decode($todos); }   //追加 public function pushTodo(Request $request) { header("Access-Control-Allow-Origin: *"); $todo = new Todo(); $todo->name = $request->input('name'); $todo->status = $request->input('status'); $todo->save(); } } フロント側から適当にタスクを追加する。 200番が返ってきていることを確認。Laravel側で何も返していないので、dataは空でOK。 リロードしても今追加したタスクが消えないことを確認。 【要件3の実装】データベースの情報を更新する(Update) web.phpを編集してルーティングを追加 routes/web.php <?php Route::get('fetch_todos', 'TodoController@fetchTodos'); Route::post('push_todo', 'TodoController@pushTodo'); // 追加 Route::post('change_status', 'TodoController@changeStatus'); TodoControllerを以下の通り実装 $id = (int) $request->input('id')でフロント側からきているidはstring型なので、int型へ型キャスト。 $todo = Todo::find($id)でTodoModelへ「$idを満たすレコードを頂戴」と言っている。 今回はstatusだけを変更したいので、上書きして保存。 TodoController.php <?php namespace App\Http\Controllers; use App\Models\Todo; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class TodoController extends Controller { public function fetchTodos() { header("Access-Control-Allow-Origin: *"); $todos = Todo::select('id' ,'name', 'status')->get(); return json_decode($todos); } public function pushTodo(Request $request) { header("Access-Control-Allow-Origin: *"); $todo = new Todo(); $todo->name = $request->input('name'); $todo->status = $request->input('status'); $todo->save(); } // 追加 public function changeStatus(Request $request) { header("Access-Control-Allow-Origin: *"); $id = (int) $request->input('id'); $todo = Todo::find($id); $todo->status = $request->input('status'); $todo->save(); } } フロント側で「変更ボタン」を押下して、「状況」が変わることを確認。 200番が出ていることも確認。 リロードしても「状況」が「完了」のままだと確認。 【要件4の実装】データベースの情報を削除する(Delete) web.phpを編集してルーティングを追加 routes/web.php <?php Route::get('fetch_todos', 'TodoController@fetchTodos'); Route::post('push_todo', 'TodoController@pushTodo'); Route::post('change_status', 'TodoController@changeStatus'); // 追加 Route::post('delete_todo', 'TodoController@deleteTodo'); TodoControllerを以下の通り実装 delete()でレコード削除。 TodoController.php <?php namespace App\Http\Controllers; use App\Models\Todo; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; class TodoController extends Controller { public function fetchTodos() { header("Access-Control-Allow-Origin: *"); $todos = Todo::select('id' ,'name', 'status')->get(); return json_decode($todos); } public function pushTodo(Request $request) { header("Access-Control-Allow-Origin: *"); $todo = new Todo(); $todo->name = $request->input('name'); $todo->status = $request->input('status'); $todo->save(); } public function changeStatus(Request $request) { header("Access-Control-Allow-Origin: *"); $id = (int) $request->input('id'); $todo = Todo::find($id); $todo->status = $request->input('status'); $todo->save(); } // 追加 public function deleteTodo(Request $request) { header("Access-Control-Allow-Origin: *"); $id = (int) $request->input('id'); $todo = Todo::find($id); $todo->delete(); } } フロント側から適当にタスクを削除。 今回は「買い物」を削除してみた。 200番が返ってくることを確認。 リポジトリ https://github.com/chillout2san/laravel_crud パイセンがガッツリレビューしてくれてプルリクまで出してくれました。ギザ優しす。 そんなパイセンのアドカレ記事はこちらです→FastAPI + Reactでフルスタックアプリを作成する 終わりに 今までfirebaseしか触ったことがなかったので、バックエンド触るのは新鮮だった。 API組むの楽しすぎてハマりそう。 だがやっぱりbladeは好きになれなかった。 Laravelはドキュメントが非常に充実していてありがたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MVCモデルのViewにDTOを導入するとどうなるか

はじめに 株式会社じげんのアレクセイです プロパンガス会社の情報・見積もり比較サービス エネピの開発を担当しています。 今回は、Viewでの作業を楽にするためにDTOの導入について書いていきます。 コントローラーで取得したモデルをそのままViewへ渡すことが多いと思いますが、Viewでモデルのリレーションを呼び出したり、モデルのプロパティによって分岐処理を入れたりします。分岐が複雑になり、同じ分岐が増え、だんだんViewのソースが読みづらくなっていきます。ビズネスロジックが変わったら何箇所を直さないといけないことになります。定期的にこういう依頼が来るとViewでの作業が大変です。そしてリファクタリングの時にモデルのリレーションやプロパティをなくすか変更する場合はエラーが起こりやすくなります。 始める前に この記事のソースコードは実際に使われているものではないので、以下の画像をHTMLページとしてメージしています。 DTOとは? 【Data Transfer Object】オブジェクト指向プログラミングでよく用いられる典型的なオブジェクトの設計パターン(デザインパターン)の一つで、関連するデータを一つにまとめ、データの格納・読み出しのためのメソッドを定義したオブジェクトのことです。異なるプログラム間やコンピュータ間でひとまとまりのデータを受け渡す際に用いられます。 引用:https://e-words.jp/w/DTO.html DTOを導入する前のView view.html @if ($book->author) @if ($book->author->firstName && $book->author->lastName) <p>{{ $book->author->firstName }} {{ $book->author->lastName }}</p> @else <p>Unknown Author</p> @endif <p>{{ $book->author->registeredDate->format('Y-m-d') }}</p> @endif <p>Price: {{ $book->price ? number_format().'円' : 'Sold out' }}</p> <a href="/buy-me">{{ session('ab.test') ? 'Buy now' : 'Buy' }}</a> @if ($book->images) @foreach ($book->images as $image) <div><img src="{{ $image->url }}"></div> @endforeach @elseif <div><img src="no-image.jpg"></div> @endif DTOオブジェクトを用意する dto.php class BookShowDto extends AbstractDto { /** * @var bool */ public $showAuthorSection; /** * @var string */ public $authorFullName; /** * @var mixed|string|null */ public $authorRegisteredDate; /** * @var array */ public $bookImages = []; /** * @var string */ public $bookPrice; /** * @var string */ public $buyButtonText = 'Buy'; // or HTML } DTOオブジェクトのプロパティを設定する logic.php $dto = new BookShowDto; if ($book->author) { $dto->showAuthorSection = true; $dto->authorFullName = $book->author->firstName.$book->author->lastName; if (!$dto->authorFullName) { $dto->authorFullName = 'Unknown Author'; } $dto->authorRegisteredDate = $book->author->registeredDate->format('Y-m-d'); } $dto->bookPrice ? $book->price ? number_format().'円' : 'Sold out'; @foreach ($book->images as $image) { $dto->bookImages[] = $image->url; } // Default image empty($dto->bookImages) and $dto->bookImages[] = '/images/no-image.jpg'; // AB testing session('ab.test') and $dto->buyButtonText = 'Buy now'; DTOを導入する後のView view.html @if ($dto->showAuthorSection) <p>{{ $dto->authorFullName }}</p> <p>{{ $dto->authorRegisteredDate }}</p> @endif <p>Price: {{ $dto->bookPrice }}</p> <a href="/buy-me">{{ $dto->buyButtonText }}</a> @foreach ($dto->bookImages as $url) <div><img src="{{ $url }}"></div> @endforeach まとめ 単純なページであればメリットをあまり感じませんが、リレーションが多い、大量な分岐が入っているページならViewとビジネスロジックの間にDTOを置けば作業が楽になるかと思います。実際のプロジェクトに導入したら以下のメリットを感じました。 ① Viewが読みやすい。 ② 多くの分岐が1箇所にまとまっている、HTMLと混ざっていないので、HTMLだけでなくphpのロジックも読みやすい。 ③ DTOのデフォルト値を設定できるので、リファクタリングの際に値の設定を忘れたり、リレーションが変わる際にもエラーが起きない。 ④ ビジネスロジックが重かったらDTOをそのまま簡単にキャッシュに乗せられる。 ⑤ ボタンや文言などのABテストの実装がやりやすい。 みなさんも是非試してみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む