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

readonly class で作る堅牢なアプリケーション

shogogg
October 10, 2023

readonly class で作る堅牢なアプリケーション

2023年10月8日に東京で行われた PHP Conference 2023 の登壇資料です。

shogogg

October 10, 2023
Tweet

More Decks by shogogg

Other Decks in Programming

Transcript

  1. ユースタイルラボラトリー株式会社 PHP Conference Japan 2023 Bronze Sponser 主な事業内容 訪問介護事業 MISSION

    すべての必要な⼈に、 必要なケアを届ける。 エンジニアのお仕事 社内向け業務システムの開発‧保守 福祉業界向けソフトウェアの開発‧保守 会社紹介
  2. final class Momoclo { public readonly string $name; public readonly

    string $color; public readonly int $age; public function __construct(string $name, string $color, int $age) { $this->name = $name; $this->color = $color; $this->age = $age; } } readonly property
  3. final class Momoclo { public function __construct( public readonly string

    $name, public readonly string $color, public readonly int $age, ) { } } readonly property
  4. final class Momoclo { public function __construct( public readonly string

    $name, public readonly string $color, public readonly int $age, ) { } } $reni = new Momoclo('高城れに', '紫', 30); $reni->color = '赤'; // => PHP Fatal Error: Uncaught Error: Cannot modify readonly property Momoclo::$color readonly property
  5. プロパティが増えると…… final class User { public function __construct( public readonly

    int $id, public readonly string $familyName, public readonly string $givenName, public readonly EmailAddress $email, public readonly DateTime $birthday, public readonly string $postcode, public readonly Prefecture $prefecture, public readonly string $city, public readonly string $street, public readonly string $room, public readonly PhoneNumber $tel, public readonly bool $isActive, ...
  6. readonly class final readonly class User { public function __construct(

    public int $id, public string $familyName, public string $givenName, public EmailAddress $email, public DateTime $birthday, public string $postcode, public Prefecture $prefecture, public string $city, public string $street, public string $room, public PhoneNumber $tel, public bool $isActive, ...
  7. // readonly ではない Momoclo クラスのインスタンス $shiori = new Momoclo( name:

    '玉井詩織', color: '黄色', // <- 黄色を指定したはずなのに…… age: 28, ); // Momoclo 型のオブジェクトを受け取って何かをするメソッドを呼び出すと…… MomocloService::doSomething($shiori); // なぜか引数に渡したオブジェクトの値が書き換わっている! echo $shiori->color; // => ピンク Mutable なオブジェクトで困ってしまう例
  8. use Carbon\Carbon; $today = Carbon::create(2023, 10, 8); $tomorrow = $today->addDays(1);

    echo $today->toDateString(); // => 2023-10-09 Carbon を使った例
  9. $x = 8; $y = $x + 1; echo $x;

    // => 8 // こうはならない echo $x; // => 9 整数 を使った例
  10. use Carbon\CarbonImmutable; $today = CarbonImmutable::create(2023, 10, 8); $tomorrow = $today->addDays(1);

    echo $today->toDateString(); // => 2023-10-08 echo $tomorrow->toDateString(); // => 2023-10-09 CarbonImmutable を使えば解決!
  11. class CarbonImmutable extends DateTimeImmutable implements CarbonInterface { // (略) }

    CarbonImmutable の定義 PHP 標準の DateTimeImmutable を継承している
  12. 介護サービスの種類(⾼齢者向け) 1. 訪問介護 2. 訪問⼊浴介護 3. 訪問看護 4. 訪問リハビリテーション 5.

    通所介護(デイサービス) 6. 通所リハビリテーション 7. 福祉⽤具貸与 8. 短期⼊所⽣活介護(ショートステイ) 9. 短期⼊所療養介護(介護⽼⼈保健施設) 10. 短期⼊所療養介護(介護療養型医療施設等) 11. 居宅療養管理指導 12. 夜間対応型訪問介護 13. 認知症対応型通所介護 14. 定期巡回‧随時対応型訪問介護看護 15. 複合型サービス(看護⼩規模多機能型居宅介護) 16. 特定施設入居者生活介護 17. 特定施設入居者生活介護(短期利用型) 18. 地域密着型特定施設入居者生活介護 19. 地域密着型特定施設入居者生活介護(短期利用型) 20. 認知症対応型共同生活介護(グループホーム) 21. 認知症対応型共同生活介護(短期利用型) 22. 特定福祉用具販売 23. 住宅改修 24. 介護福祉施設(特別養護老人ホーム) 25. 介護保険施設 26. 介護療養施設 27. 地域密着型介護福祉施設入所者生活介護 28. 特定入所者介護サービス等 29. 地域密着型通所介護 30. 居宅介護支援 ※読み飛ばし推奨
  13. 介護サービスの種類(障がい者向け) 1. 居宅介護 2. 重度訪問介護 3. 同⾏援護 4. ⾏動援護 5.

    療養介護 6. ⽣活介護 7. 経過的⽣活介護 8. 短期⼊所(ショートステイ) 9. 重度障害者等包括⽀援 10. 施設⼊所⽀援 11. 経過的施設⼊所⽀援 12. 機能訓練 13. ⽣活訓練 14. 宿泊型⾃⽴訓練 15. 就労移⾏⽀援 16. 就労移⾏⽀援(養成) 17. 就労継続支援A型 18. 就労継続支援B型 19. 就労定着支援 20. 自立生活援助 21. 共同生活援助(グループホーム) 22. 計画相談支援 23. 障害児相談支援 24. 地域相談支援(地域移行支援) 25. 地域相談支援(地域定着支援) 26. 福祉型障害児入所施設 27. 医療型障害児入所施設 28. 児童発達支援 29. 医療型児童発達支援 30. 放課後等デイサービス 31. 居宅訪問児童支援 32. 保育所等訪問支援 ※読み飛ばし推奨
  14. use Illuminate\Database\Eloquent\Model; final readonly class Item { public function __construct(

    public string $name, public int $price, ) { } } final class ItemRecord extends Model { // 略 } $record = ItemRecord::find(id: 17); $item = new Item( id: $record->id, price: $record->price, ); ORM からの変換
  15. use Illuminate\Database\Eloquent\Model; final class ItemRecord extends Model { // 略

    // ドメインモデルに変換するメソッド public function toDomain(): Item { return new Item( id: $record->id, price: $record->price, ); } } $item = ItemRecord::find(id: 17)->toDomain(); 解決策①:ドメインモデルへの変換メソッド
  16. use Illuminate\Database\Eloquent\Model; final class ItemRecord extends Model { // 略

    } final class ItemRepository { public function lookup(int $id): Item { return ItemRecord::find(id: 17)->toDomain(); } } 解決策②:リポジトリパターン
  17. // 注:このコードは動作しない final readonly class Foo { private array $cache

    = []; // 略 public function getSomething(int $arg): int { if (!isset($this->cache[$arg])) { $this->cache[$arg] = $this->computeSomething($arg); } return $this->cache[$arg]; } // 引数に対応して何かを計算して返すメソッド private function computeSomething(int $arg): int { return $arg ** 2; } } 配列を使ったシンプルなキャッシュと相性が悪い
  18. use \Illuminate\Contracts\Cache\Repository as CacheRepository; final readonly class Foo { private

    CacheRepository $cache; public function _construct() { $this->cache = Cache::store('array'); } public function getSomething(int $arg): int { return $this->cache->remenber($arg, 86400, function (int $arg): int { // 引数に対応して何かを計算して返す処理 return $arg ** 2; }); } } 解決策①:素直にフレームワークに頼る
  19. final readonly class Foo { // キャッシュを保存・読み出ししてくれるクラスを独自に用意して利用する private CacheManager $cache;

    // 略 public function getSomething(int $arg): int { return $this->cache->getOrElse($arg, function (int $arg): int { // 引数に対応して何かを計算して返す処理 return $arg ** 2; }); } } 解決策②:キャッシュ専⽤のクラスを⽤意する
  20. final readonly class Point { public function __construct(public int $x,

    public int $y) { } } $a = new Point(x: 3, y: 5); // $a と X 座標は同じで Y 座標が異なる Point オブジェクトを作成したい $b = new Point(x: $a->x, y: 7); ⼀部の値を変更したインスタンスを得る
  21. final readonly class Person { public function __construct( public int

    $id, public string $familyName, public string $givenName, public int $age, // ... ) { } function aging(Person $person): Person { return new Person( id: $person->id, familyName: $person->familyName, givenName: $person->givenName, age: $person->age + 1, // ... ); } } プロパティが多いと⾯倒だし読みにくい
  22. // Scala: case class case class Point(x: Int, y: Int)

    val a = Point(x = 3, y = 5) val b = a.copy(y = 7) // Kotlin: data class data class Point(val x: Int, val y: Int) val a = Point(x = 3, y = 5) val b = a.copy(y = 7) // Java 16 以降: Record public record Point(int x, int y) {} 他の⾔語の場合
  23. abstract readonly class Model { public function copy(...$values): self {

    return call_user_func_array([new ReflectionClass(self::class), 'newInstance'], [ ...get_object_vars($this), ...$values, ]); } } final readonly class Point extends Model { // 略 } $a = new Point(x: 3, y: 5); $b = $a->copy(y: 7); 解決策①:⾃家製 copy メソッドを実装する
  24. use Spatie\Cloneable\Cloneable; final readonly class Point { use Cloneable; public

    function __construct(public int $x, public int $y) { } } $a = new Point(x: 3, y: 5); $b = $a->with(y: 7); 解決策②:spatie/php-cloneable を使う
  25. 解決策③:withHoge メソッドを実装する use Spatie\Cloneable\Cloneable; final readonly class Item { use

    Cloneable; public function __construct( public string $name, public int $price, public Carbon $createdAt, public Carbon $updatedAt, ) { } public function withPrice(int $price): self { return $this->with(price: $price, updatedAt: Carbon::now()); } }
  26. まとめ • readonly class の登場によって、PHP でもお⼿軽、かつ安全に に不変(Immutable)オブジェクトを扱うことができるように なった。 • 複雑なビジネスロジックを扱うアプリケーションにおいては、

    Immutable なオブジェクトを⽤いることでコードの保守性の 向上が期待できる。 • かゆいところに⼿が届くとまでは⾔えないが、⼗分に実⽤的。