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

どうしても Eloquent でリポジトリパターンをやりたいんだ #phpcon_niigat...

shogogg
November 13, 2026

どうしても Eloquent でリポジトリパターンをやりたいんだ #phpcon_niigata / phpcon-niigata-2026

2026年11月14日に新潟県新潟市・朱鷺メッセで開催されたPHPカンファレンス新潟2026の登壇資料です。

shogogg

November 13, 2026

Transcript

73 slides
  1. 自己紹介 河瀨 翔吾 / Shogo Kawase エンジニアリングマネージャー I LOVE... 妻

    / 設計 / 型安全 / アジャイル /ももクロ 麻雀 / F1 / マリオカート / ACE COMBAT shogogg shogogg
  2. Laravel as a whole is very inspired by Ruby on

    Rails more than anything. Laravel のすべてはなによりも Ruby on Rails に強い影響を受けている Taylor Otwell 2024年, Podcast「Taylor Otwell's opinions on PHP, React, Laravel and Lamborghini Memes」より
  3. Ruby on Rails, Django, even, like, .NET MVC, which I

    was using at work at the time, it takes a lot of inspiration from those various places. Ruby on Rails, Django, そして当時職場で使っていた .NET MVC など 様々なものから多くのインスピレーションを得ています Taylor Otwell 2024年, Podcast「Taylor Otwell's opinions on PHP, React, Laravel and Lamborghini Memes」より
  4. Batteries Included in Laravel • Web 開発に必要なものをあらかじめ備え、豊富なエコシステムを持つ → 開発者をツール選定の苦労から解放 •

    設計方針さえもフレームワークから強い意見として提示 → 開発者をアーキテクチャ検討の苦悩から解放
  5. So with Laravel, a lot of those opinions are baked

    in. Laravel にはそういった意見(設計方針)があらかじめ組み込まれているのです。 Taylor Otwell 2026年, Podcast「Taylor Otwell on Why Laravel + AI Pair so Well, and the Future of Programming」より
  6. Way

  7. Follow Laravel conventions unless the domain or an existing schema

    requires an override. ドメインや既存スキーマが上書きを必要としない限り、Laravelの規約に従うこと Laravel Boost 「Architecture Best Practices」より
  8. Active Record? • データベースの1行=1オブジェクト • プロパティを書き換え save() で永続化(保存)する • オブジェクトがデータ表現と

    DB 操作の両方を担う • データ操作とビジネスロジックが集約された Mutable(可変) なモデル
  9. There is no right or wrong in architecture, there are

    only trade-offs. アーキテクチャには正解も不正解もない。あるのはトレードオフだけだ。 Neal Ford 2020年,「ソフトウェアアーキテクチャの基礎 ― エンジニアリングに基づく体系的アプローチ」より ※リンク先は第2版(日本語版)
  10. それでも Laravel を選ぶ理由 • 自分がよく知っている • 経験者が多く、学習コストが低い • Laravel Way

    を離れても依然として使える強力な基盤 ◦ Routing, Validation, Migration, Mailable, Queue, FileSystem, etc... • 相対的に EOL のリスクが低い • 既に Laravel を使っている • etc...
  11. 失うものは多い • Auth 関連便利機能の数々 • Route Model Binding • Model

    Factory • Eloquent Events / Observers • 公式/サードパーティによる Eloquent を前提とした便利な機能・拡張の数々
  12. There is no right or wrong in architecture, there are

    only trade-offs. アーキテクチャには正解も不正解もない。あるのはトレードオフだけだ。 Neal Ford 2020年,「ソフトウェアアーキテクチャの基礎 ― エンジニアリングに基づく体系的アプローチ」より ※リンク先は第2版(日本語版)
  13. 詰め替えがめんどうくさい public function toDomainModel(): Order { return new Order( //

    ... created_at: Carbon::make($this->created_at), updated_at: Carbon::make($this->updated_at), payment_method: PaymentMethod::from($this->payment_method), shipping_method: ShippingMethod::from($this->shipping_method), name: new StructuredName( 'givenName' => $this->given_name, 'familyName' => $this->family_name, ), items: $this->items ->map(fn (OrderItem $x): DomainOrderItem => $x->toDomainModel()) ->toArray(), // ... ); } ※コードは Laravel 13 時点でのものです。古い/新しい Laravel では異なる場合があります
  14. Casting /** * Get the attributes that should be cast.

    * * @return array<string, string> */ protected function casts(): array { return [ // created_at, updated_at は Carbon で読み書きができる 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; } ※コードは Laravel 13 時点でのものです。古い/新しい Laravel では異なる場合があります
  15. Enum Casting /** * Get the attributes that should be

    cast. * * @return array<string, string> */ protected function casts(): array { return [ // PaymentMethod, ShippingMethod はいずれも Enum 'payment_method' => PaymentMethod::class, 'shipping_method' => ShippingMethod::class, ]; } ※コードは Laravel 13 時点でのものです。古い/新しい Laravel では異なる場合があります
  16. Value Object Casting /** * Get the attributes that should

    be cast. * * @return array<string, string> */ protected function casts(): array { return [ // AsStructuredName は CastsAttributes を実装したクラス 'name' => AsStructuredName::class, ]; } ※コードは Laravel 13 時点でのものです。古い/新しい Laravel では異なる場合があります
  17. Value Object Casting class AsStructuredName implements CastsAttributes { public function

    get(Model $model, string $key, mixed $value, array $attributes): Address { return new StructuredName( givenName: $attributes['given_name'], familyName: $attributes['family_name'], ); } public function set(Model $model, string $key, mixed $value, array $attributes): array { return [ 'given_name' => $value->givenName, 'family_name' => $value->familyName, ]; } } ※コードは Laravel 13 時点でのものです。古い/新しい Laravel では異なる場合があります
  18. Accessors // リレーションの定義 public function itemsRelation(): HasMany { return $this->hasMany(OrderItem::class,

    'order_id'); } // Accessor: $this->items は get の結果を返すようになる protected function items(): Attribute { return Attribute::make( get: fn (): array => $this->itemsRelation ->map(fn (OrderItem $x): DomainOrderItem => $x->toDomainModel()) ->toArray(), ); } ※コードは Laravel 13 時点でのものです。古い/新しい Laravel では異なる場合があります
  19. 名前付き引数( Named Arguments ) /* 位置引数(Positional Arguments)*/ echo pow(2, 6);

    // 64 /* 名前付き引数(Named Arguments)※PHP 8.0以降 */ echo pow(num: 2, exponent: 7); // 128 /* 名前付き引数は順番を入れ替えてもOK */ echo pow(exponent: 8, num: 2); // 256
  20. 引数での ... の使用 /* Argument Unpacking */ $args = [2,

    6]; echo pow(...$args); // 64 /* 順序を入れ替えると、もちろん結果が変わる */ $args = [6, 2]; echo pow(...$args); // 36 /* PHP 8.0 からは連想配列も使える */ $args = ['num' => 2, 'exponent' => 8]; echo pow(...$args); // 256 /* 順序を入れ替えるとどうなる……? */ $args = ['exponent' => 8, 'num' => 2]; echo pow(...$args); // ?
  21. 引数での ... の使用 /* Argument Unpacking */ $args = [2,

    6]; echo pow(...$args); // 64 /* 順序を入れ替えると、もちろん結果が変わる */ $args = [6, 2]; echo pow(...$args); // 36 /* PHP 8.0 からは連想配列も使える */ $args = ['num' => 2, 'exponent' => 8]; echo pow(...$args); // 256 /* 順序を入れ替えても結果は変わらない!=名前付き引数の仕組みが使われている! */ $args = ['exponent' => 8, 'num' => 2]; echo pow(...$args); // 256
  22. php-src/Zend/zend_vm_def.h if (UNEXPECTED(name)) { void *cache_slot[2] = {NULL, NULL}; have_named_params

    = 1; top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot); if (UNEXPECTED(!top)) { FREE_OP1(); HANDLE_EXCEPTION(); } } else { if (have_named_params) { zend_throw_error(NULL, "Cannot use positional argument after named argument during unpacking"); FREE_OP1(); HANDLE_EXCEPTION(); } top = ZEND_CALL_ARG(EX(call), arg_num); ZEND_CALL_NUM_ARGS(EX(call))++; } php-src/Zend/zend_vm_def.h より抜粋
  23. abstract class Table extends \Illuminate\Database\Eloquent\Model { protected static array $reflectionClass

    = []; protected static array $domainModelAttributes = []; // Returns the domain model class name. abstract protected function getDomainModelName(): string; final protected static function getReflectionClass(string $className): ReflectionClass { // Returns the reflection class instance for domain model. } final protected static function getDomainModelAttributes(string $className): array { // Returns domain model attribute names as an array. } final protected function getDomainModelAttributeValues(): iterable { // Returns attributes for domain model. } final public function toDomainModel(): object { // Converts itself to domain model. } } final public function fromDomainModel(object $model): self { // Creates an instance from domain model. }
  24. ReflectionClass の生成とキャッシュ protected static array $reflectionClass = []; // Returns

    the reflection class instance for domain model. final protected static function getReflectionClass(string $className): ReflectionClass { if (!isset(self::$reflectionClass[$className])) { self::$reflectionClass[$className] = new ReflectionClass($className); } return self::$reflectionClass[$className]; }
  25. プロパティ一覧の取得とキャッシュ protected static array $domainModelAttributes = []; // Returns domain

    model attribute names as an array. final protected static function getDomainModelAttributes(string $className): array { if (!isset(self::$domainModelAttributes[$className])) { $constructor = self::getReflectionClass($className)->getConstructor(); if ($constructor === null) { throw new LogicException("There is no constructor for {$className}"); } self::$domainModelAttributes[$className] = array_map( fn (ReflectionParameter $param) => $param->getName(), $constructor->getParameters(), ); } return self::$domainModelAttributes[$className]; }
  26. ドメインモデルへの変換 // Returns attributes for domain model. final protected function

    getDomainModelAttributeValues(): iterable { $className = $this->getDomainModelName(); foreach ($this->getDomainModelAttributes($className) as $key) { yield Str::camel($key) => $this->getAttribute($key); } } // Converts itself to domain model. final public function toDomainModel(): object { $className = $this->getDomainModelName(); $attributes = iterator_to_array($this->getDomainModelAttributeValues()); return self::getReflectionClass($className)->newInstanceArgs($attributes); }
  27. ドメインモデルからの生成 protected $fillable = [/* ... */]; // Creates an

    instance from domain model. public function fromDomainModel(object $model): self { $attributes = []; foreach ($model->toAssoc() as $key => $value) { $attributes[Str::snake($key)] = $value; } return new static($attributes); }
  28. final class Order extends Table { protected $fillable = [/*

    ... */]; protected function getDomainModelName(): string { return DomainOrder::class; } protected function casts(): array { return [ 'created_at' => 'datetime', 'updated_at' => 'datetime', 'payment_method' => PaymentMethod::class, 'shipping_method' => ShippingMethod::class, 'name' => AsStructuredName::class, ]; } public function itemsRelation(): HasMany { return $this->hasMany(OrderItem::class, 'order_id'); } } protected function items(): Attribute { return Attribute::make( get: fn (): array => $this->itemsRelation ->map(fn (OrderItem $x): DomainOrderItem => $x->toDomainModel()) ->toArray(), ); }
  29. Repository final class OrderRepository { public function lookup(int $id): ?DomainOrder

    { return Order::with('itemsRelation')->find($id)?->toDomainModel(); } public function store(Order $model): void { $order = Order::fromDomainModel($model)->save(); // OrderItem を保存するコードが続く } public function remove(DomainOrder $order): void { // 必要に応じて OrderItem を削除するコードも記述する Order::fromDomainModel($order)->delete(); } }
  30. まとめ • Laravel は “batteries included” なフレームワーク • Laravel を使い、Laravel

    Way に従うことで価値に集中できる • ”アーキテクチャには正解も不正解もない。あるのはトレードオフだけ ” • Eloquent × Repository Pattern は Laravel Way から外れる茨の道 • それでも Laravel を使う価値は十分にある • Laravel Way を外れて得るものが失うものより多いのであれば、間違いではない • Eloquent と PHP 8 のパワー があれば、ボイラープレートの大分部はなくせる
  31. 参考文献 • Do Things the Laravel Way(Laravel Boost) • Architecture

    Best Practices(Laravel Boost) • Laravelは単なるバックエンドフレームワークではない。急速に進化し続けるLaravelエコシステ ム。(Ryuta Hamasaki) • Why Laravel apps break(武田 憲太郎) • Laravelへの異常な愛情 または私は如何にして心配するのを止めてEloquentを愛するようになった か(武田 憲太郎) • ソフトウェアアーキテクチャの基礎 第2版 (Mark Richards, Neal Ford 著 / 島田 浩二 訳)

More Decks by shogogg

Other Decks in Programming