Slide 1

Slide 1 text

readonly class で作る 堅牢なアプリケーション Oct. 8 2023 PHP Conference Japan 2023 河瀨 翔吾 / @shogogg

Slide 2

Slide 2 text

河瀨 翔吾 ユースタイルラボラトリー(株)ソフトウェア開発部テックリード シンアジャイルコミュニティ運営 好きな⾔葉 型安全 / アジャイル 好きなゲーム ボドゲ / マリオカート / フロムゲー(最近だとAC6) 好きなアイドル ももいろクローバーZ ⾃⼰紹介 shogogg shogogg

Slide 3

Slide 3 text

ユースタイルラボラトリー株式会社 PHP Conference Japan 2023 Bronze Sponser 主な事業内容 訪問介護事業 MISSION すべての必要な⼈に、 必要なケアを届ける。 エンジニアのお仕事 社内向け業務システムの開発‧保守 福祉業界向けソフトウェアの開発‧保守 会社紹介

Slide 4

Slide 4 text

#phpcon #track4

Slide 5

Slide 5 text

今⽇お話すること ● readonly class とは? ● readonly class をなぜ使うのか ● readonly class を使う場合のつらさ

Slide 6

Slide 6 text

readonly class とは?

Slide 7

Slide 7 text

readonly class? ● PHP 8.2 で登場した読み取り専⽤のクラス ● すべてのプロパティが readonly property となる

Slide 8

Slide 8 text

readonly property? ● PHP 8.1 で登場した読み取り専⽤のプロパティ ● コンストラクタでのみ値が設定できる ● ⼀度コンストラクタで設定した値から変更ができない

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

プロパティが増えると…… 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, ...

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

readonly class をなぜ使うのか

Slide 15

Slide 15 text

A. Immutable だから

Slide 16

Slide 16 text

Immutable? ● ⼀度⽣成されたら、その状態を変更(mutate)できない 特性のこと。不変。 ● 逆に⽣成後に状態を変更できることを Mutable(可変)と 表現する。

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

use Carbon\Carbon; $today = Carbon::create(2023, 10, 8); $tomorrow = $today->addDays(1); echo $today->toDateString(); // => 2023-10-09 Carbon を使った例

Slide 19

Slide 19 text

$x = 8; $y = $x + 1; echo $x; // => 8 // こうはならない echo $x; // => 9 整数 を使った例

Slide 20

Slide 20 text

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 を使えば解決!

Slide 21

Slide 21 text

class CarbonImmutable extends DateTimeImmutable implements CarbonInterface { // (略) } CarbonImmutable の定義 PHP 標準の DateTimeImmutable を継承している

Slide 22

Slide 22 text

DateTime と DateTimeImmutable 引用:https://www.php.net/manual/ja/class.datetime.php

Slide 23

Slide 23 text

Mutable だと…… ● オブジェクトの状態(値)が変えられる、ということは、いつどこで 書き換えられてもおかしくない、ということ。 ● いつの間にか書き換えられた結果として不具合が⽣じると、どこで書 き換えられているのかを探したり、その不具合を解消するのも⼀苦 労。 ● 安全なコードを書くためには、どの処理に副作⽤があるのか把握した り、確認しながらコードを書いていく必要がある。

Slide 24

Slide 24 text

Immutable なら…… ● オブジェクトの状態(値)が変えられることがないため、⼀度⽣成し たオブジェクトは必ず同じ値を持ち続ける。 ● ⽬の前のコードを読むだけで、オブジェクトの状態(値)がどうなっ ているかを把握できるため、物事がシンプルになる。 ● 考えることが減るため、安全にコードの読み書きに集中することがで きる。

Slide 25

Slide 25 text

Q. readonly class をなぜ使うのか

Slide 26

Slide 26 text

A. Immutable だから ➡ 複雑さを減らし、シンプルにすることで   ⽬の前の課題に安⼼して集中するため

Slide 27

Slide 27 text

readonly class をいつ使うのか ● 中〜⼤規模アプリケーションの開発であり、ビジネスロジック が複雑になる(なりそうな)場合にデータを表現するクラスと して⽤いると効果は⼤きい。 ● 逆に⼩規模であったり、リクエストに応じた単純な CRUD 操作 が主体のアプリケーションなどにおいて、(無理をして)導⼊ する必要性は低いと感じる。

Slide 28

Slide 28 text

例えば介護報酬の請求 ● 医療同様、費⽤は⼀部のみを利⽤者さんに請求し、残りは保険 者‧⾃治体に請求する必要がある。 ● サービスに応じて点数を計算し、保険者や⾃治体に請求する仕 組みも医療とほぼ同じ。 ● ⾼齢者向けの介護と障がい者向けの介護で制度が全く異なる。 ● 制度の内容や点数の計算ルールが提供サービスによって⼤きく 異なる。

Slide 29

Slide 29 text

介護サービスの種類(⾼齢者向け) 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. 居宅介護支援 ※読み飛ばし推奨

Slide 30

Slide 30 text

介護サービスの種類(障がい者向け) 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. 保育所等訪問支援 ※読み飛ばし推奨

Slide 31

Slide 31 text

例えば介護報酬の請求 ● 請求に関するオブジェクトを⽣成するためのビジネスロジック が⾮常に複雑。そのコードが数千⾏に及ぶ場合もある。 ● 制度の内容や点数の計算ルールが提供サービスによって⼤きく 異なり、それぞれのサービスごとに必要。 ● ビジネスロジックが⾮常に複雑なのに、さらに Mutable なオブ ジェクトの複雑性を持ち込みたくない。 ➡ Immutable なオブジェクト = readonly class が有効!

Slide 32

Slide 32 text

readonly class を使う場合のつらさ

Slide 33

Slide 33 text

readonly class を使う場合のつらさ ● ORM からの変換が⾯倒くさい ● 配列を使ったシンプルなキャッシュと相性が悪い ● ⼀部の値を変更したインスタンスを得るのが⾯倒くさい

Slide 34

Slide 34 text

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 からの変換

Slide 35

Slide 35 text

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(); 解決策①:ドメインモデルへの変換メソッド

Slide 36

Slide 36 text

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(); } } 解決策②:リポジトリパターン

Slide 37

Slide 37 text

// 注:このコードは動作しない 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; } } 配列を使ったシンプルなキャッシュと相性が悪い

Slide 38

Slide 38 text

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; }); } } 解決策①:素直にフレームワークに頼る

Slide 39

Slide 39 text

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; }); } } 解決策②:キャッシュ専⽤のクラスを⽤意する

Slide 40

Slide 40 text

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); ⼀部の値を変更したインスタンスを得る

Slide 41

Slide 41 text

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, // ... ); } } プロパティが多いと⾯倒だし読みにくい

Slide 42

Slide 42 text

// 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) {} 他の⾔語の場合

Slide 43

Slide 43 text

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 メソッドを実装する

Slide 44

Slide 44 text

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 を使う

Slide 45

Slide 45 text

解決策③: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()); } }

Slide 46

Slide 46 text

まとめ

Slide 47

Slide 47 text

まとめ ● readonly class の登場によって、PHP でもお⼿軽、かつ安全に に不変(Immutable)オブジェクトを扱うことができるように なった。 ● 複雑なビジネスロジックを扱うアプリケーションにおいては、 Immutable なオブジェクトを⽤いることでコードの保守性の 向上が期待できる。 ● かゆいところに⼿が届くとまでは⾔えないが、⼗分に実⽤的。

Slide 48

Slide 48 text

Thank you!