Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Laravelへの異常な愛情 または私は如何にして心配するのを止めてEloquentを愛するようになったか

Laravelへの異常な愛情 または私は如何にして心配するのを止めてEloquentを愛するようになったか

武田 憲太郎

March 25, 2023
Tweet

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. 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

    View Slide

  7. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. • 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

    View Slide

  23. モデルとテーブルが対で作成される
    • 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

    View Slide

  24. ファクトリ(ダミーデータの設計図)
    • テーブルにどんなダミーデー
    タを割り当てるか定義
    • リレーションシップ先のモデ
    ルは他のファクトリに作成を
    移譲できる
    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

    View Slide

  25. ダミーデータの⽣成
    • 開発⽤データや本番環境初期
    データなどまとまった⼀式の
    データを作成
    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

    View Slide

  26. コントローラー
    • 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

    View Slide

  27. リソースコントローラー
    • 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

    View Slide

  28. バリデーション
    • $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

    View Slide

  29. 認可
    • 認証済ユーザーと操作対象の
    モデルが渡される。
    • 認証済ユーザーに対する認可
    可否を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

    View Slide

  30. 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

    View Slide

  31. 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

    View Slide

  32. 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

    View Slide

  33. 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

    View Slide

  34. 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

    View Slide

  35. 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

    View Slide

  36. 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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. 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
    フレームワークが提供する構造 = ベストプラクティス

    View Slide

  43. 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

    View Slide

  44. リソースによる責務分離(とその違反)
    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

    View Slide

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

    View Slide

  46. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  53. 例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

    View Slide

  54. 例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

    View Slide

  55. 例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

    View Slide

  56. 例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

    View Slide

  57. 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

    View Slide

  58. 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

    View Slide

  59. 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

    View Slide

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

    View Slide

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

    View Slide

  62. ./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

    View Slide

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

    View Slide

  64. 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

    View Slide

  65. 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

    View Slide

  66. 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

    View Slide

  67. 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

    View Slide

  68. モデルなのかレコードセットなのか?
    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
    モデルとレコードセットが混ざることによる不可解な挙動

    View Slide

  69. リソースによる責務分離(とその違反)
    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

    View Slide

  70. 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

    View Slide

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

    View Slide

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

    View Slide

  73. 利点と⽋点
    72
    https://readouble.com/laravel/9.x/ja/facades.html#when-to-use-facades



    ① 簡潔で覚えやすい
    ② テストが簡単
    ③ ファットクラスを
    意図せず作りがち
    ④ ファットのフィー
    ドバックが無い

    2023/3/25 PHPerKaigi 2023 / Dr. Strange Laravel

    View Slide

  74. 利点と⽋点
    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

    View Slide

  75. 便利すぎるファサード
    • 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

    View Slide

  76. ファサードを適度に使う
    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

    View Slide

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

    View Slide

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

    View Slide

  79. 何が嬉しいか?(本当にあった怖い話)
    • あるコミットを機にテストの所要時間が
    激増した
    • 広い範囲から発⽕されるイベントのリスナ
    を実装。ほとんど全てのテストケースでリ
    スナが起動されていた🔥
    • 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

    View Slide

  80. 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

    View Slide

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

    View Slide

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

    View Slide

  83. 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

    View Slide

  84. 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

    View Slide

  85. 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

    View Slide

  86. 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: 抽象に依存

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. 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

    View Slide

  91. 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

    View Slide

  92. 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

    View Slide

  93. 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

    View Slide

  94. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide