Slide 1

Slide 1 text

Laravelへの異常な愛情 または私は如何にして⼼配するのを⽌めて Eloquentを愛するようになったか Dr. Strange Laravel Or: How I Learned To Stop Worrying And Love Eloquent

Slide 2

Slide 2 text

1 Laravelへ寄せられる声 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 3

Slide 3 text

シニアエンジニアの声 「綺麗な設計で書けない!」 「Eloquent◎※△!!」 「Deleting Laravel solves all problems」 2 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 4

Slide 4 text

ノービスエンジニアの声 「Laravel難しい!」 「勉強の仕⽅が解らない!」 「フロントエンドに転向しようかなあ…」 3 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 5

Slide 5 text

様々な声を総括する Laravelとは、 シニアにとって使いづらく、 ノービスにとっては難しいフレームワーク。 4 🤔 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 6

Slide 6 text

5 https://trends.google.co.jp/trends/explore?date=2016-02-04%202023-03-04&q=%2Fm%2F09cjcl,%2Fm%2F0jwy148,%2Fm%2F09t3sp,%2Fm%2F0j3cj1y 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 7

Slide 7 text

6 https://trends.google.co.jp/trends/explore?date=2016-02-04%202023-03-04&q=%2Fm%2F0505cl,%2Fm%2F0jwy148,%2Fm%2F06y_qx 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 8

Slide 8 text

7 このギャップは何? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 9

Slide 9 text

ギャップの正体を探り ベストプラクティスを⾒極めれば Laravel開発の⽣産性を 向上できるのではないか? 8 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 10

Slide 10 text

アジェンダ 1.⾃⼰紹介 2.Laravelを知る 3.Laravelに寄り添う 4.Laravelは何を⽬指しているのか? 9 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 11

Slide 11 text

⾃⼰紹介 武⽥ 憲太郎 / @KentarouTakeda Webアプリケーションエンジニア 10 開発⾔語 好き:TypeScript 得意:PHP JSフレームワーク 好き:Angular 得意:Next.js PHPフレームワーク 好き:Symfony 好き・得意:Laravel 正しく書けば⾃然と堅牢な設計になっていくような技術が好き 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 12

Slide 12 text

アジェンダ 1.⾃⼰紹介 2.Laravelを知る 3.Laravelに寄り添う 4.Laravelは何を⽬指しているのか? 11 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 13

Slide 13 text

Laravelが解決しようとしている課題は何か? 12 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 14

Slide 14 text

Laravelが⽣まれた背景 13 https://qiita.com/kumamon_engineer/items/c4ac0942fa01d4617b38 • Taylor Otwell個⼈のニーズがきっかけ。 • 副業でPHPを触っていた。 • CodeIgniterの代替として作られた。 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 15

Slide 15 text

CodeIgniterのコンセプト •豊富なライブラリ •コードの量を最⼩限に •ほとんど設定がいらない •柔軟な書き⽅ •プロジェクトの開発速度を加速 14 https://codeigniter.jp/user_guide/3/general/welcome.html 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 16

Slide 16 text

Laravelの特徴 – スケーラブル •簡単かつ無制限に ⽔平スケール 15 https://readouble.com/laravel/9.x/ja/installation.html#why-laravel 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 17

Slide 17 text

Laravelの特徴 – フルスタック • laravel/framework • フロントエンド • ルーティング • ビュー • ORM • キャッシュ • メール • ... • laravel/* • SPA • 決済 • OAuth • ... 16 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 18

Slide 18 text

Laravelの特徴 – 対象ユーザー 17 •幅広いユーザー 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 19

Slide 19 text

総括 • 短い時間と少ないコード量で • 無制限にスケールできる • フルスタックなアプリケーションを • シニアもノービスも開発できる 18 「FWが⽤意した答え = ベストプラクティス」 とは? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 20

Slide 20 text

composer create-project laravel/laravel •ほとんど全てのLaravel アプリケーションはこ の状態から開始される。 •初期状態のコードがベ ストプラクティスから 外れていることはあり 得ないはず。 19 https://github.com/laravel/laravel 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 21

Slide 21 text

./artisan make:* •コードの多くは雛形から⾃動 ⽣成。 •Laravel⾃⾝が⽣成するコード である以上 意図された使い⽅ =ベストプラクティス である はず。 20 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 22

Slide 22 text

• Post.php • Eloquentモデル • create_posts_table.php • マイグレーション・テーブル作成 • PostFactory.php • ファクトリ(フィクスチャ) • PostSeeder.php • シーダ(初期データ作成) • StorePostRequest.php • 保存時のバリデーション • UpdatePostRequest.php • 更新時のバリデーション • PostController.php • コントローラー • PostPolicy.php • 認可処理 • PostControllerTest • Featureテスト 21 ./artisan make:model Post --all ./artisan make:test PostControllerTest 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 23

Slide 23 text

モデルとテーブルが対で作成される • postsテーブルとPostモデルが対で作成される • Postモデルは実装が何もない(このままで問題なく動作する) • ファクトリの利⽤だけが宣⾔されている 22 return new class extends Migration { public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); // 主キー $table->timestamps(); // タイムスタンプ $table->text('title'); // 「件名」カラムを追加する例 }); } }; class Post extends Model { use HasFactory; } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 24

Slide 24 text

ファクトリ(ダミーデータの設計図) • テーブルにどんなダミーデー タを割り当てるか定義 • リレーションシップ先のモデ ルは他のファクトリに作成を 移譲できる 23 class PostFactory extends Factory { public function definition(): array { return [ // ダミーの文言とユーザーで投稿を作成 'user_id' => User::factory(), 'title' => fake()->sentence(), ]; } } class UserFactory extends Factory { public function definition(): array { return [ 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), ‘password’ => ‘...(省略)', // password 'remember_token' => Str::random(10), ]; } } ※laravel/laravel初期状態 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 25

Slide 25 text

ダミーデータの⽣成 • 開発⽤データや本番環境初期 データなどまとまった⼀式の データを作成 24 class UserSeeder extends Seeder { public function run(): void { // ユーザーを10名作成 User::factory()->count(10)->create(); } } class PostSeeder extends Seeder { public function run(): void { // 作成済ユーザーを取得 $users = User::all(); // 作成済ユーザーに所属する投稿を100件作成 Post::factory()->count(100) ->sequence(fn() => [ 'user_id' => fake()->randomElement($users) ]) ->create(); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 26

Slide 26 text

コントローラー • index, store, showなどの予約 名。 • ⼀部モデルやリクエストも 渡される。 • リクエストが渡されたらモ デルに保存、モデルが渡さ れたらそのまま返却、これ が基本。 25 class PostController extends Controller { public function index() { return Post::all(); // 投稿全件を返却 } public function store(StorePostRequest $request) { // 投稿を作成しそれを返却 return Post::create($request->validated()); } public function show(Post $post) { return $post; // 指定された投稿を1件返却 } /* 省略 */ } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 27

Slide 27 text

リソースコントローラー • CRUD操作に必要な全てのURLと HTTPメソッドがルートに⼀括登 録される。 • URLパラメータをIDとみなしモデ ルを解決。 • 解決できない場合Not Found。 26 https://readouble.com/laravel/9.x/ja/controllers.html#actions-handled-by-resource-controller // URLとそれを処理するクラスを紐付け Route::resource( ‘/posts’, PostController::class ); ※「/posts」にPostControllerを紐付け(⼿動で追記) 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 28

Slide 28 text

バリデーション • $this->post で操作対象モデル(投稿)にアクセス • $this->user() で認証ユーザーにアクセス • 組み合わせバリデーションやドメインバリデーションも実装可 27 class StorePostRequest extends FormRequest { public function rules(): array { return [ // 新規作成時のバリデーションルール ]; } } class UpdatePostRequest extends FormRequest { public function rules(): array { return [ // 更新時のバリデーションルール ]; } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 29

Slide 29 text

認可 • 認証済ユーザーと操作対象の モデルが渡される。 • 認証済ユーザーに対する認可 可否をboolとして返却。 • falseを返却した場合403エラー。 28 class PostPolicy { public function viewAny(User $user): bool { // 認証済ユーザーは投稿の一覧表示が可能か? } public function view(User $user, Post $post): bool { // 認証済ユーザーは指定された投稿を閲覧可能か? return $user->is_admin || $post->user_id === $user->id; } public function create(User $user): bool { // 認証済ユーザーは投稿を作成可能か? } } class PostController extends Controller { public function show(Post $post) { // 閲覧可能でない場合403エラー $this->authorize(‘view', $post); return $post; } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 30

Slide 30 text

Featureテスト(初期状態) • これ⾒よがしに使われていな いuseが2件 29 namespace Tests¥Feature; use Illuminate¥Foundation¥Testing¥RefreshDatabase; use Illuminate¥Foundation¥Testing¥WithFaker; use Tests¥TestCase; class PostControllerTest extends TestCase { public function test_example(): void { $response = $this->get('/’); $response->assertStatus(200); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 31

Slide 31 text

Featureテスト(実装例) • use RefreshDatabase; テスト毎のDBリセット • use WithFaker; Fakerを利⽤ • 準備 適当な件名で投稿を1件作成 • テスト ID指定でGET • アサーション 作成と返却との件名の⼀致 30 class PostControllerTest extends TestCase { use RefreshDatabase; use WithFaker; public function IDを指定し投稿を1件取得(): void { $post = Post::factory()->create([ 'title' => $this->faker()->sentence(), ]); $response = $this->get("/posts/{$post->id}"); $response->assertJsonFragment([ 'title' => $post->title ]); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 32

Slide 32 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 相関図 31 コマンド2回、⾃動⽣成ファイル9個で以上の構成 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 33

Slide 33 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 32 モデルとテーブルは1対1の対応 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 34

Slide 34 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 33 CRUD操作はリソースルートとして束ねられ、 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 35

Slide 35 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 34 リソースはモデルへ解決された状態で、 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 36

Slide 36 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 35 バリデーション処理や、 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 37

Slide 37 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 36 認可処理が⾏われる。 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 38

Slide 38 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 37 コントローラーは⼊⼒されたモデルを書き戻すのみ 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 39

Slide 39 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 38 テストはシーダやファクトリでダミーデータを⽣成し 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 40

Slide 40 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 39 通常のリクエストと同じ経路でFeatureテスト 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 41

Slide 41 text

Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test Postモデルの全体像 40 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 42

Slide 42 text

Laravelの全体像 41 Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test User Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test Post Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test Comment Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test Tag 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel フレームワークが提供する構造 = ベストプラクティス

Slide 43

Slide 43 text

MVCモデルによる責務分離(とその違反例) • Viewに漏れ出したモ デルの知識 • 表⽰を司ってしまっ たコントローラー • モデルがリソース表 現に関与 42 Model Controller View Model Controller View Model Controller View $user ->name $html = '

' . $post->body . '

'; return url( '/posts/' . $this->id} ); 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 44

Slide 44 text

リソースによる責務分離(とその違反) 43 User Model User Controller users Table User Resource View - C View - R View - U View - D Post Model Post Controller posts Table Post Resource View - C View - R View - U View - D return $post->user; Post::where( 'user_id', $user->id ) 責務の境界はレイヤーではなくリソース 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 45

Slide 45 text

rails generate scaffold • CRUD操作を網羅するコントローラーを作成 • 対応するモデルとそれを格納するテーブル (マイグレーション)を作成 • それらリソースへのルーティングの作成 • フィクスチャ(ファクトリ)を作成 • テストファイルの作成 44 https://railsdoc.com/rails#rails_scaffold 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel Ruby on Railsと同じ考え⽅

Slide 46

Slide 46 text

Ruby on Railsのパラダイム 45 builderscon tokyo 2019 Ruby (off|with) the Rails https://www.youtube.com/watch?v=g7nU45RgBvw https://speakerdeck.com/shinpeim/ruby-off-with-the-rails?slide=15 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 47

Slide 47 text

46 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel https://zenn.dev/mpyw/articles/ce7d09eb6d8117

Slide 48

Slide 48 text

Eloquentと各機能との関係 • Eloquentの利⽤が前提 • 例:認可 • Eloquentによる追加機能 • 例:モデル結合ルート • Eloquentを想定 • 例:ジョブとSerializesModels Laravelのほとんどの機能が Eloquentと「何らか」関係 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel 47

Slide 49

Slide 49 text

Laravelを知る - 登壇者によるLaravelの解釈 48 全層をEloquentモデルで⼀気通貫する リソース志向のフレームワーク 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 50

Slide 50 text

アジェンダ 1.⾃⼰紹介 2.Laravelを知る 3.Laravelに寄り添う 4.Laravelは何を⽬指しているのか? 49 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 51

Slide 51 text

Laravelに寄り添う • Eloquent中⼼に考える • Eager Loading vs join • Facadeを使う • Laravelの機能を使う 50 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 52

Slide 52 text

Eloquentモデルに何を書くか? • リソースの責務がモデルに集中する。 • クエリビルダやEloquent⾃⾝による多くの標準メソッド。 • 肥⼤化と破綻の予感しかない。 Eloquentを「正しく」使い秩序を与える。 EloquentモデルはEloquentの機能で実装する。 51 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 53

Slide 53 text

例1: あるモデルに関連する何かを探す 52 class User extends Model { // Bad ユーザーの全投稿を取得 public function getPosts() { return Post::where('user_id', $this->id) ->get(); } } class User extends Model { // Good 投稿へのリレーションシップを宣言 public function posts() { return $this->hasMany(Post::class); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 54

Slide 54 text

例2: モデルの値を変形する 53 class User extends Model { // Bad フルネームを取得 public function getName() { return $this->last_name . ' ' . $this->first_name; } } class User extends Model { // Good フルネームの取得方法を宣言 protected function name(): Attribute { return Attribute::make( get: fn($_, $attributes) => $attributes['last_name'] . ' ' . $attributes['first_name'] ); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 55

Slide 55 text

例3: バリデーション 54 class User extends Model { // Bad バリデーション通過した場合のみ保存 public function saveWithValidate() { if($this->isDirty('name')) { throw new DomainException( '名前は変更できません' ); } $this->save(); } } class User extends Model { // Good イベントの購読を宣言 protected static function booted() { static::updating(function (self $user) { if($user->isDirty('name')) { throw new DomainException( '名前は変更できません' ); } }); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 56

Slide 56 text

例4: クエリ(検索)のユースケース 55 class User extends Model { // Bad 「アクティブユーザー」を取得 public static function getActiveUsers() { return self::query()->whereHas( 'posts', fn($q) => $q->where( 'created_at', '>=', now()->subDays(7)) ) ->get(); } } class User extends Model { // Good 「アクティブユーザー」のスコープを宣言 public function scopeWhereActive(Builder $query) { return $query->whereHas( 'posts', fn($q) => $q->where( 'created_at', '>=', now()->subDays(7)) ); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 57

Slide 57 text

Eloquentの機能で宣⾔的に実装するメリット • 命名規則やタイプヒント要件の存在 • ビルトインメソッドと衝突するリスクが⽐較的低い • 「Laravel標準」による認知負荷の低減 ※Laravel⾃体の学習コストは必要 • 再利⽤可能性 • 責務の⼩さなローカルスコープをチェーンし⼤きなユースケースを満たす • Observer、Castable、BootableTraitによる処理の切り出し • Interfaceやabstract traitによる契約の宣⾔ • 例: implements Taggable(タグ付け可能・ミューテタ) • 例: implements FullTextSearchable(全⽂検索可能・ローカルスコープ) • use Prunable(モデルの整理・Laravel標準機能) • 仕様管理 🤔 56 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 58

Slide 58 text

EloquentとDocs as Code 57 return new class extends Migration { public function up(): void { Schema::create('users', function (Blueprint $table) { $table->id()->comment('ID'); $table->string('first_name')->comment('名'); $table->string('last_name')->comment('姓'); $table->string('email')->unique() ->comment ('メールアドレス'); $table->string('password')->comment('パスワード'); $table->timestamps(); }); } }; class User extends Model { /** @comment 投稿 */ public function posts() { return $this->hasMany(Post::class); } /** @comment アクティブユーザーへのクエリ */ public function scopeWhereActive() { // 省略 } /** @comment アクティブユーザーか否か */ protected function isActive(): Attribute { return Attribute::make( get: fn() => '省略', ); } } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 59

Slide 59 text

barryvdh/laravel-ide-helper • プロパティ、リレーションシップ、スコープ、アクセサ、 、コメント、ほぼ 全てに対応 ※更新ペースが遅くLaravelの⽐較的新しい機能に対応していない点に注意 58 /** * App¥Models¥User * * @property int $id ID * @property string $first_name 名 * @property string $last_name 姓 * @property string $email メールアドレス * @property string $password パスワード * @property Carbon|null $created_at * @property Carbon|null $updated_at * @property-read Collection $posts 投稿 * @method static Builder|User newModelQuery() * @method static Builder|User newQuery() * @method static Builder|User query() * @method static Builder|User whereActive() アクティブユーザーへのクエリ * @mixin ¥Eloquent */ 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 60

Slide 60 text

based/laravel-typescript 59 https://github.com/lepikhinb/laravel-typescript 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 61

Slide 61 text

beyondcode/laravel-er-diagram-generator 60 https://github.com/beyondcode/laravel-er-diagram-generator 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 62

Slide 62 text

./artisan model:show >=9.21.0 2022/07/19 • モデルの実装(宣⾔)をメタデータと して出⼒ • json出⼒による再利⽤可能性 61 { "class": "App¥¥Models¥¥User", "database": "sqlite", "table": "users", "policy": null, "attributes": [ { "name": "id", "type": "integer", /* 省略 */ }, /* 省略 */ ], "relations": [ { "name": "posts", "type": "HasMany", "related": "App¥¥Models¥¥Post" } ], "observers": [] } 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 63

Slide 63 text

Laravelに寄り添う • Eloquent中⼼に考える • Eager Loading vs join • Facadeを使う • Laravelの機能を使う 62 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 64

Slide 64 text

Eager loading vs JOIN 63 User::query() ->with('posts') ->get(); User::query() ->join( 'posts', 'user.id', '=', 'post.user_id' ) ->get(); 検討:どちらを使うか? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 65

Slide 65 text

Eager loading vs JOIN 64 User::query() ->with('posts') ->get(); User::query() ->join( 'posts', 'user.id', '=', 'post.user_id' ) ->get(); 結論:joinとEloquentは混ぜてはいけない 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 66

Slide 66 text

Eager loading vs JOIN 65 $users = User::query() ->with('posts') ->get(); get_class($user[0]); // App¥Models¥User $users->count(); // ユーザーの人数 $users = User::query() ->join( 'posts', 'user.id', '=', 'post.user_id' ) ->get(); get_class($users[0]); // App¥Models¥User $users->count(); // 投稿の件数 $users[0]->title // 投稿の件名 理由:「テーブル |-| モデル」の関係が崩れることによる不可解な挙動 • 型はUser • 実体は「ユーザーでも投稿 でもない何か」 • アクティブレコードの利点 を全て失う • バグの温床 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 67

Slide 67 text

JOINの利⽤ • モデルとテーブルは1対1対応ではあるが全く同じものではない。 • 結果セットを変形するクエリをEloquentから使うのを避ける。 • `group by`句や集約関数も同じ理由で要注意 • モデルなのかレコードセットなのか変数名などで明確に区別。 66 $rows = User::query()->getQuery() ->join( 'posts', 'user.id', '=', 'post.user_id' ) ->get(); $rows = DB::table('users') ->join( 'posts', 'user.id', '=', 'post.user_id' ) ->get(); 解法1:Eloquentビルダをクエリビルダに変換 解法2:モデルではなくテーブルをクエリする 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 68

Slide 68 text

モデルなのかレコードセットなのか? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel 67 Eloquentとクエリビルダを両⽅使った実装の失敗例 - 第150回 PHP勉強会 https://www.youtube.com/live/qz9Y-WXgxxY?feature=share&t=9672 https://speakerdeck.com/shinpeim/ruby-off-with-the-rails?slide=15 モデルとレコードセットが混ざることによる不可解な挙動

Slide 69

Slide 69 text

リソースによる責務分離(とその違反) 68 User Model User Controller users Table User Resource View - C View - R View - U View - D Post Model Post Controller posts Table Post Resource View - C View - R View - U View - D ユーザーでも 投稿でもない 何か 責務 暴⾛ 複数のモデルへ関⼼が横断する場合どうするか? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 70

Slide 70 text

Single Action Controller • 「ダッシュボード画⾯」「マイページ」「LP」 • CRUD操作を束ねる必要が無い 69 User Model users Table Awesome Controller View Post Model posts Table Comment Model comments Table 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 71

Slide 71 text

Eloquent中⼼の設計 70 • Eloquentモデルは宣⾔的に実装 • メタデータを再利⽤する • リソースルートやRoute Model Bindingによりコード量を削減する • 「テーブル = モデル = リソース」と考えリソース毎に関⼼を分離する • この範疇に収まらない要件の場合Single Action Controllerを選択 • この場合は設計は何でもアリ • 「テーブル=モデル=リソース」の前提にも⽴たなくて良い 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 72

Slide 72 text

Laravelに寄り添う • Eloquent中⼼に考える • Eager Loading vs join • Facadeを使う • Laravelの機能を使う 71 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel https://twitter.com/taylorotwell/status/1560020999378292736

Slide 73

Slide 73 text

利点と⽋点 72 https://readouble.com/laravel/9.x/ja/facades.html#when-to-use-facades ① ② ③ ① 簡潔で覚えやすい ② テストが簡単 ③ ファットクラスを 意図せず作りがち ④ ファットのフィー ドバックが無い ④ 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 74

Slide 74 text

利点と⽋点 73 https://readouble.com/laravel/9.x/ja/facades.html#when-to-use-facades @if(Auth::user()) {{ Auth::user()->name }} @else ログイン @endif 🤔 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 75

Slide 75 text

便利すぎるファサード • app.phpでのエイリアス設定 • ファサードを簡潔に「使えてし まう」のはこの設定によるもの。 • エディタも静的解析もこのエイ リアスを認識できない • 設定を全て削除 74 // app.php 'aliases' => Facade::defaultAliases()->merge([ // 'ExampleClass' => App¥Example¥ExampleClass::class, ])->toArray(), // app.php 'aliases' => [ 'App' => Illuminate¥Support¥Facades¥App::class, 'Arr' => Illuminate¥Support¥Arr::class, 'Artisan' => Illuminate¥Support¥Facades¥Artisan::class, 'Auth' => Illuminate¥Support¥Facades¥Auth::class, // 省略 ], // app.php 'aliases' => [ ], 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 76

Slide 76 text

ファサードを適度に使う 75 @if(Illuminate¥Support¥Facades¥Auth::user()) {{ Illuminate¥Support¥Facades¥Auth::user()->name }} @else ログイン @endif namespace App¥Http¥Controllers; use App¥Models¥Post; use App¥Models¥User; use Illuminate¥Http¥Request; use Illuminate¥Http¥Response; use Illuminate¥Support¥Facades¥Cache; use Illuminate¥Support¥Facades¥Http; use Illuminate¥Support¥Facades¥Session; use Illuminate¥Support¥Facades¥View; • Bladeテンプレート 事実上の利⽤不可 • コントローラー 視覚的なフィードバック 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 77

Slide 77 text

ファサードとテスト •ファサード標準機能とし てMockeryのラッパーが⽤ 意されている。 •「ファサード」のドキュ メントではこの⽅法が紹 介されている。 76 https://readouble.com/laravel/9.x/ja/facades.html#facades-vs-dependency-injection 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 78

Slide 78 text

ファサードごとに個別に⽤意されたテスト ファサードに応じたそれぞれのテスト機能 • Http • URLを指定しレスポンスをモック • リクエストの有無や内容をアサーション • モックされていないリクエストの禁⽌ • Storage • ファイル作成有無のアサーション • ディレクトリ内容のアサーション • ダミーファイルの⽣成(画像可) • テスト終了時の初期化 • Mail • 送信有無のアサーション • 本⽂とヘッダを区別したアサーション 77 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 79

Slide 79 text

何が嬉しいか?(本当にあった怖い話) • あるコミットを機にテストの所要時間が 激増した • 広い範囲から発⽕されるイベントのリスナ を実装。ほとんど全てのテストケースでリ スナが起動されていた🔥 • CIがSaaSのAPIをコールし課⾦発⽣💰 • ストレージのテストを実装した数⽇後に ローカル環境のディスク容量が溢れた😇 • ステージング環境のS3バケットに正体不 明のダミーファイルが⽇々作られ続けて いた😥 • メーr.....😱😱😱😱 78 abstract class TestCase extends BaseTestCase { use CreatesApplication; public function setUp(): void { parent::setUp(); // テスト時は強制的に全てモック Http::fake(); Http::preventStrayRequests(); Storage::fake(); Mail::fake(); Event::fake(); Bus::fake(); } } インシデントを根絶 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 80

Slide 80 text

Cast Policy Route Model Binding Model Controller Table Resource View - C View - R View - U View - D Form Request Seeder Factory test 何が嬉しいか?(ファサードを使うべき理由) 79 システム外への副作⽤を伴う処理の場合も リクエストレスポンスのテストが容易かつ安全に可能 Facade Fake 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 81

Slide 81 text

Facadeを使う 責務の漏れ出し⼝を塞いだ上で、敢えて積極的にFacade • エイリアスを無効化して「使いにくく」する • 既存コードで実施する際は影響箇所の修正が必要(「Auth::」等⼀通りで検 索) • それぞれのファサードに応じた便利テスト機能を駆使 • テスト時は強制的に全てをモックする 80 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 82

Slide 82 text

Laravelに寄り添う • Eloquent中⼼に考える • Eager Loading vs join • Facadeを使う • Laravelの機能を使う 81 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 83

Slide 83 text

PHPの機能で書く 82 実装例 こんな時に困る ファイル保存 file_put_contents(...); • Webサーバを並列化したい メール送信 mb_send_mail(...); • メール送信をSaaSに乗り換えたい ログ error_log(...); • 本館環境をコンテナ化したい アラート送信 file_put_contents(/*Slack API*/); • 開発環境⽤if⽂が必要 バッチ起動 # crontab * * * * * php artisan ... • バッチサーバがSPOFになりがち • コード管理不可 • ローカル環境で⼀部機能が動かない 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 84

Slide 84 text

Laravelの機能で書き換える 83 実装例 Laravelでの書き⽅ ファイル保存 file_put_contents(...); Storage::put(...); メール送信 mb_send_mail(...); Mail::to(...) ->send(...); ログ error_log(...); Log::warning(...); アラート送信※ file_put_contents(/*Slack API*/); Log::critical(...); バッチ起動 # crontab * * * * * php artisan ... $schedule ->command(...) ->cron('* * * * *'); ※アラート送信 • アプリケーションは「それがクリティカルである」までしか関知しない • 「クリティカルをどう扱うか?」は監視(Observability)の責務 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 85

Slide 85 text

Laravelに環境を制御させる(ログ出⼒の例) • single • storage/logs/laravel.logに出⼒ • ローカル環境におすすめ • daily • storage/logs/に出⼒しローテーション • EC2での運⽤におすすめ • stderr • 標準エラー出⼒へ出⼒。 • monologによる⾼度なカスタマイズが可能 • 本番コンテナはこれ⼀択 • slack • CriticalレベルのみSlack通知 • Critical以外は捨てられる(stackと組み合わせる) • syslog • OSやjournaldに管理させる。 • stack • 複数の設定を同時に使う • 例: stderrでDocker Daemonへ送りつつsingleでコンテ ナ内にも保存、かつslackでのアラートも発報。 84 config('logging.default') • 環境変数「LOG_CHANNEL」を設定 • 無ければ「stack」を設定 config/logging.php https://github.com/laravel/laravel/blob/10.x/config/logging.php 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 86

Slide 86 text

Laravelに環境を制御させる(ファイル保存の例) 85 if('production' === config('app.env')) { Storage::disk('s3')->put(...); } else { Storage::disk('local')->put(...); } services: laravel: environment: # ローカルディスクを指定 FILESYSTEM_DISK: local taskDefinition.addContainer('laravel', { containerName: 'laravel', environment: { # S3を指定 FILESYSTEM_DISK: 's3', }, } Storage::put(...); Bad: • 本番環境はs3に保存 • それ以外はローカルに保存 Good: • ストレージ(抽象)に保存 • 具象の振る舞いは外部から注⼊ docker-compose.yml(ローカル環境) ECSタスク定義(本番環境・CDK) 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel Bad: 下位のモジュールに依存 Good: 抽象に依存

Slide 87

Slide 87 text

Laravelの機能を使う • Laravelでの⽅法が⽤意されている場合はそれを使う • 具体の動作は関知せずデフォルトのまま実装する • 具体の動作は設定で制御する • 設定ファイルは編集せず環境変数で制御する • Laravelの機能をデフォルトのまま使う 86 この原則に従った実装はコンテナ化やスケールが容易 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 88

Slide 88 text

アジェンダ 1.⾃⼰紹介 2.Laravelを知る 3.Laravelに寄り添う 4.Laravelは何を⽬指しているのか? 87 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 89

Slide 89 text

Laravelは何を⽬指しているのか? 88 とあるエピソードから読み解く 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 90

Slide 90 text

2022/10/11 [10.x] Uses PHP Native Type Declarations • 戻り値アノテーショ ンをタイプヒントへ 修正。 • 本体以外の全ファー ストパーティーパッ ケージも対象。 • ライブラリも含む多 くの開発者にとって 痛みの伴う修正。 89 https://github.com/laravel/laravel/pull/6010 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 91

Slide 91 text

2022/10/12 [10.x] Uses PHP Native Type Declarations 90 https://github.com/laravel/framework/pull/44545 • laravel/laravelが提供 する初期コード や./artisan make:*の ⾃動⽣成も修正の対 象。 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 92

Slide 92 text

Taylor OtwellのツイートとLaravel Newsの記事 91 https://twitter.com/taylorotwell/status/1592227118481805312 https://laravel-news.com/laravel-10-type-declarations 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 93

Slide 93 text

2023/2/14 Laravel 10 リリース • リリースノート中の順序 はphpバージョンの変更 に継ぐ2番⽬に掲載 92 https://laravel.com/docs/10.x/releases#laravel-10 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 94

Slide 94 text

2023/2/18 YouTuber Povilas Korop 93 https://twitter.com/dillinghamdev/status/1626989292227551232 https://www.youtube.com/c/LaravelDaily Laravel10に混乱している。 リソースコントローラーを作成したら返却型にResponseが 追加されていた。 私はいつも通りview()を返却した。エラーに遭遇した。 確かにResponseではなくViewを返却したが、何か間違えた だろうか?毎回⼿作業で修正するのだろうか? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 95

Slide 95 text

様々な意⾒ 94 このバグが修正されるのを待っているので、 まだLaravel 9を使ってる。 これはバグではないのだが?書いたコードに あわせてタイプヒントを修正してほしい。 私はシニア開発者ではない。 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 96

Slide 96 text

2023/2/19 Tylor Otwell 95 https://twitter.com/taylorotwell/status/1626987508369178624 多くの⼈が、これはバグでありLaravel 9 にとどまらなけれ ばならないと考えているようだ。 バックスペースキーで戻り値型を削除し修正するだけなの だが… Viewの⽅が理に適ってるか、⾒てみようと思う。 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 97

Slide 97 text

2023/2/19 戻り値型をmixedに変更するPR 96 ※最終的には戻り値型が削除された状態でマージ https://github.com/laravel/framework/pull/46166 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 98

Slide 98 text

エピソードを総括する • Taylor Otwell⾃⾝が、積極的に議論に参加。 • コメントの意⾒により、議論の途中で⽅針を変えている。 • 結論Laravel 10の⽬⽟となる変更を(⼀部とは⾔え)リバート。 97 PR作成→ マージ→ 「私はシニア開発者ではない」 この声に応えたのではないか? 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 99

Slide 99 text

98 https://laravel.com/docs/10.x#meet-laravel 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

Slide 100

Slide 100 text

総括する 99 Laravelは、 シニアにとっては使いづらく、 ノービスにとって難しいフレームワーク。 万⼈にとっての使いやすさを⽬指した 独⾃のパラダイムを持つフレームワーク 2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel