$30 off During Our Annual Pro Sale. View Details »

予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント / Growing Reliable Code PHPerKaigi 2022

予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント / Growing Reliable Code PHPerKaigi 2022

PHPerKaigi 2022
2022/04/10 10:40〜 Track A レギュラートーク(40分)

PHP はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。

本講演では PHP 8.1 をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。

Agenda
- 型宣言
- 列挙型
- ドメインモデリング
- 不変性と等価性
- 完全性
- レイヤーと責務

Takuto Wada
PRO

April 10, 2022
Tweet

More Decks by Takuto Wada

Other Decks in Programming

Transcript

  1. ༧๷ʹউΔ๷ޚͳ͠ ݎ࿚ͳίʔυΛಋ༷͘ʑͳઃܭͷώϯτ 📷🙆 🙆 ࿨ా୎ਓʢ!U@XBEBʣ #phperkaigi #a rev.15 "QS !1)1FS,BJHJ

  2. illustrated by @mty_mno ΑΖ͓͘͠ئ͍͠·͢ #phperkaigi #a

  3. class BugRepository { private $pdo; public function __construct($pdo) { $this->pdo

    = $pdo; } public function findAll($params) { $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } ஫ॻ੶ʰ42-ΞϯνύλʔϯʱͷͻͲ͍ίʔυྫΛ͞ΒʹͻͲ͘ΞϨϯδͯ͠ॻ͍͍ͯ·͢ ͱ͋Δͱ͜Ζʹɺ͜Μͳίʔυ͕͋Γ·ͨ͠ # " % IUUQTXXXPSFJMMZDPKQCPPLT
  4. $bugs = $repo->findAll([ 'startAt' => '2021-01-01', 'endAt' => '2022-01-01', 'status'

    => 'OPEN' ]); print_r($bugs); $ php example.php Array ( [0] => PhperKaigi\Bug Object ( [bug_id] => 42 [summary] => 保存処理でクラッシュする [reported_at] => 2021-12-24 17:14:42.246896+00 ) [1] => PhperKaigi\Bug Object ( [bug_id] => 43 [summary] => XMLのサポート [reported_at] => 2021-12-25 11:53:18.203906+00 ) [2] => PhperKaigi\Bug Object ( [bug_id] => 44 [summary] => パフォーマンスの向上 [reported_at] => 2021-12-27 13:24:19.251937+00 ) ) ࣮ߦ͢Δͱɺ͜Μͳ݁Ռ
  5. ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % public function findAll($params) { $sql =

    'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); }
  6. ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % public function findAll($params) { $sql =

    'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } 🙅ςʔϒϧ໊΍ΧϥϜ໊͕୭͔ʹมߋ͞Εͨ 🙅 ͜͜Ͱ σʔλϕʔε઀ଓΤϥʔ
  7. ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % 🙅QBSBNT͕OVMM 🙅QBSBNTͷΩʔ໊΍਺ͷෆҰக 🙅QBSBNTͷ஋͕จࣈྻʹม׵ෆೳ 🙅QBSBNTͷ஋͕೔࣌ͱͯ͠ղऍͰ͖ͳ͍ 🙅 ͜͜Ͱ

    σʔλϕʔε઀ଓΤϥʔ public function findAll($params) { $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); }
  8. ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % 🙅#VHΫϥε͕ະఆٛ 🙅 ͜͜Ͱ σʔλϕʔε઀ଓΤϥʔ public function

    findAll($params) { $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); }
  9. public function findAll($params) { $sql = 'SELECT bug_id, summary, reported_at

    FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % ͚ͬ͜͏͋Δͳ😇 ᶃ ᶄ ᶅ
  10. ෆ۩߹ͷൃݟ͕ ஗ΕΕ͹஗ΕΔ΄Ͳ ই͸ਂ͘ͳΔ IUUQTXXXBTURCPSHQSFTTSPPN*452#@$FSUJ fi DBUJPO@/FXT@@IUNM

  11. lݡ໌ͳιϑτ΢ΣΞٕज़ऀʹͳΔͨΊͷ ୈҰา͸ɺಈ͘ϓϩάϥϜΛॻ͘͜ͱͱ ਖ਼͍͠ϓϩάϥϜΛద੾ʹ࡞੒͢Δ͜ͱ ͷҧ͍Λೝࣝ͢Δ͜ͱz Š."+BDLTPO 

  12. 💀ςʔϒϧ໊΍ΧϥϜ໊͕୭͔ʹมߋ͞Εͨ 👮QBSBNT͕OVMM 👮QBSBNTͷΩʔ໊΍਺ͷෆҰக 👮QBSBNTͷ஋͕จࣈྻʹม׵ෆೳ 👮QBSBNTͷ஋͕೔࣌ͱͯ͠ղऍͰ͖ͳ͍ 🐛#VHΫϥε͕ະఆٛ 😇్தͰσʔλϕʔε઀ଓΤϥʔ 👉 👉 👉

    ݱঢ়Λ෼ੳ͢Δͱɺؒҧͬͨ࢖ΘΕํΛ͞Ε΍͍͢ͱ͍͏໰୊͕େ͖͍ 👉 ຊ೔ͷߨԋ͸ ͜ΕΒͷ໰୊ʹूத͠·͢
  13. ڱٛͷ ๷ޚతϓϩά ϥϛϯά΁ͷޡղ

  14. public function findAll($params) { if (is_null($params)) { throw new \InvalidArgumentException('params

    should not be null'); } if (!is_array($params)) { throw new \InvalidArgumentException('params should be an array'); } if (count($params) !== 3) { throw new \InvalidArgumentException('params should have exact three items'); } if (!array_key_exists('startAt', $params) || !array_key_exists('endAt', $params) || !array_key_exists('status', $params)) { throw new \InvalidArgumentException('params should have keys "startAt", "endAt" and "status"'); } if (!is_string($params['startAt'])) { throw new \InvalidArgumentException('params["startAt"] should be a string'); } if (!is_string($params['endAt'])) { throw new \InvalidArgumentException('params["endAt"] should be a string'); } if (!is_string($params['status'])) { throw new \InvalidArgumentException('params["status"] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('params["status"] should be in "OPEN","NEW","FIXED"'); } ͨͩͻͨ͢ΒೖྗΛνΣοΫ͠Α͏ͱͨ͠Γ # " %
  15. ෆਖ਼ͳೖྗ͕͋ͬͯ΋ࣗ෼ͰԿͱ͔͠Α͏ͱͨ͠Γ # " % $sql = 'SELECT bug_id, summary, reported_at

    FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $safeParams = [ 'startAt' => $params['startAt'] ?? $params['start_at'] ?? '1970-01-01', 'endAt' => $params['endAt'] ?? $params['end_at'] ?? 'now', 'status' => $params['status'] ?? 'OPEN', ]; $stmt->execute($safeParams); $className = class_exists('Bug') ? 'Bug' : 'BugModel'; return $stmt->fetchAll(\PDO::FETCH_CLASS, $className);
  16. υΩϡϝϯτͰؒҧ͍΍͢͞Λิ͓͏ͱͨ͠Γ # " % /** * 指定した範囲の日時およびステータスに合致する Bug を検索し、ヒットした全件を Bug

    オブジェクトの配列として返す。 * * @param array $params 格納した検索条件の連想配列。キー "startAt" に検索範囲の 始点日時をstringで, キー "endAt" に検索範囲の終点日時をstringで, キー "status" にス テータス文字列をstringで指定すること。キー、値それぞれNULLは不可とする。 * @return Bug[] 検索結果を Bug オブジェクトにマッピングして返す。検索結果が0件 のときは空配列を返す。 */ public function findAll($params) {
  17. ๷ޚతϓϩάϥϛϯάͱ͸ ѱ͍ίʔυʹឺ૑ߣΛ͋ͯΔ ͜ͱͰ͸ͳ͍

  18. w ʮ๷ޚతϓϩάϥϛϯάʯͱ͸ɺ 
 ໰୊ൃੜΛࣄલʹ๷͝͏ͱ͍͏ίʔσΟϯάελΠϧ w Մಡੑͷߴ͍ίʔυͱద੾ͳ໋໊نଇ w શͯͷؔ਺ͷ໭Γ஋ΛνΣοΫ w σβΠϯύλʔϯͷ࠾༻

    w ཁ͢Δʹɺྑࣝ͋Δ࣮ફͷੵΈॏͶͰ͋Δ w ๷ޚతϓϩάϥϛϯά͸ɺਖ਼͍͠ίʔυ࡞੒ͷͨΊͷن཯Λ ϓϩάϥϚ͕Ұ؏ͯ͠ద༻͢ΔͨΊͷҰछͷίʔσΟϯάඪ४ ๷ޚతϓϩάϥϛϯάͱ͸ྑࣝ͋Δ࣮ફͷੵΈॏͶ IUUQTXXXBNB[PODPKQEQ
  19. Agenda  ܕએݴ  ྻڍܕ  ϞσϦϯά  ෆมੑͱ౳Ձੑ 

    ׬શੑ  ੹຿ͷ഑ஔ 👉
  20. ܕΛߜΔ

  21. ΤοηΠ+ݟ஌Β͵ਓͱ΋͏·͘΍Δʹ͸ lʮग़དྷͯ͸ͳΒ͵͜ͱΛې͡ ΔʯͷͰ͸ͳ͘ɺ͸͡Ί͔Β ʮग़དྷ͍͍ͯ͜ͱ͚ͩΛग़དྷΔ Α͏ʹ͢Δʯͱߟ͑ΔͷͰ͢z IUUQTXXXPSFJMMZDPKQCPPLT

  22. public function findAll($params) { if (is_null($params)) { throw new \InvalidArgumentException('params

    should not be null'); } if (!is_array($params)) { throw new \InvalidArgumentException('params should be an array'); } if (count($params) !== 3) { throw new \InvalidArgumentException('params should have exact three items'); } if (!array_key_exists('startAt', $params) || !array_key_exists('endAt', $params) || !array_key_exists('status', $params)) { throw new \InvalidArgumentException('params should have keys "startAt", "endAt" and "status"'); } if (!is_string($params['startAt'])) { throw new \InvalidArgumentException('params["startAt"] should be a string'); } if (!is_string($params['endAt'])) { throw new \InvalidArgumentException('params["endAt"] should be a string'); } if (!is_string($params['status'])) { throw new \InvalidArgumentException('params["status"] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('params["status"] should be in "OPEN","NEW","FIXED"'); } ໰୊ͱͳ͍ͬͯΔ͜ͷ࿈૝഑ྻΛ ݱঢ়
  23. public function findAll(\DateTime $startAt, \DateTime $endAt, string $status): array {

    if (is_null($params)) { throw new \InvalidArgumentException('params should not be null'); } if (!is_array($params)) { throw new \InvalidArgumentException('params should be an array'); } if (count($params) !== 3) { throw new \InvalidArgumentException('params should have exact three items'); } if (!array_key_exists('startAt', $params) || !array_key_exists('endAt', $params) || !array_key_exists('status', $params)) { throw new \InvalidArgumentException('params should have keys "startAt", "endAt" and "status"'); } if (!is_string($params['startAt'])) { throw new \InvalidArgumentException('params["startAt"] should be a string'); } if (!is_string($params['endAt'])) { throw new \InvalidArgumentException('params["endAt"] should be a string'); } if (!is_string($params['status'])) { throw new \InvalidArgumentException('params["status"] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('params["status"] should be in "OPEN","NEW","FIXED"'); } ͦΕͧΕܕએݴ͞ΕͨҾ਺ʹ෼ղ͢Δ ܕએݴʹΑͬͯʮग़དྷ͍͍ͯ͜ͱ͚ͩΛग़དྷΔʯΑ͏ʹ
  24. public function findAll(\DateTime $startAt, \DateTime $endAt, string $status): array {

    if (is_null($params)) { throw new InvalidArgumentException('params should not be null'); } if (!is_array($params)) { throw new InvalidArgumentException('params should be an array'); } if (count($params) !== 3) { throw new InvalidArgumentException('params should be have exact three items'); } if (!array_key_exists('startAt', $params) || !array_key_exists('endAt', $params) || !array_key_exists('status', $params)) { throw new InvalidArgumentException('params should have key "startAt", "endAt" and "status" only'); } if (!is_string($params['startAt'])) { throw new InvalidArgumentException('params["startAt"] should be a string'); } if (!is_string($params['endAt'])) { throw new InvalidArgumentException('params["endAt"] should be a string'); } if (!is_string($params['status'])) { throw new InvalidArgumentException('params["status"] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('params["status"] should be in "OPEN","NEW","FIXED"'); } ๷ޚతνΣοΫ͕΄΅ෆཁʹ
  25. ૝ఆ͠ͳ͚Ε͹ͳΒͳ͍ঢ়گ͕ݮͬͨ const TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.uP'; public function findAll(\DateTime $startAt, \DateTime

    $endAt, string $status): array { if (!in_array($status, ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('status should be in "OPEN","NEW","FIXED"'); } $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':startAt', $startAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':endAt', $endAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':status', $status, \PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } ˞ຊߨԋͰ͸1PTUHSF42-ͷUJNFTUBNQXJUIUJNF[POFܕͷΧϥϜʹ֨ೲ͍ͯ͠Δͱߟ͍͑ͯͩ͘͞
  26. Agenda  ܕએݴ  ྻڍܕ  ϞσϦϯά  ෆมੑͱ౳Ձੑ 

    ׬શੑ  ੹຿ͷ഑ஔ 👉
  27. ஋ΛߜΔ

  28. wݴޠ૊ΈࠐΈͷܕʢJOU TUSJOH౳ʣΛ࢖͏ ͱɺऔΓಘΔ஋ͷ૊Έ߹Θ͕ͤ๲େʹͳΔ w໰୊ྖҬͷ஌ࣝΛ׆༻ͯ͠ݻ༗ͷܕΛ࡞Δ ͜ͱͰɺऔΓಘΔ૊Έ߹ΘͤΛେ෯ʹݮΒ ͤΔ ΤοηΠؔ਺ͷʮαΠζʯΛখ͘͢͞Δ IUUQTXXXPSFJMMZDPKQCPPLT

  29. ྻڍܕΛ࢖͓͏ 1 ) 1   enum Status: string {

    case Open = 'OPEN'; case New = 'NEW'; case Fixed = 'FIXED'; } IUUQTXXXQIQOFUNBOVBMKBMBOHVBHFFOVNFSBUJPOTQIQ
  30. const TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.uP'; public function findAll(\DateTime $startAt, \DateTime $endAt,

    string $status): array { if (!in_array($status, ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('status should be in "OPEN","NEW","FIXED"'); } $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':startAt', $startAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':endAt', $endAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':status', $status, \PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } ݱঢ়
  31. const TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.uP'; public function findAll(\DateTime $startAt, \DateTime $endAt,

    Status $status): array { if (!in_array($status, ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('status should be in "OPEN","NEW","FIXED"'); } $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':startAt', $startAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':endAt', $endAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':status', $status->value, \PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } Ҿ਺Λจࣈྻ͔Βྻڍܕʹม͑Δ จࣈྻ͔Βྻڍܕʹม͑Δ
  32. const TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.uP'; public function findAll(\DateTime $startAt, \DateTime $endAt,

    Status $status): array { if (!in_array($status, ['OPEN', 'NEW', 'FIXED'], true)) { throw new \InvalidArgumentException('params["status"] should be in "OPEN","NEW","FIXED"'); } $sql = 'SELECT bug_id, summary, reported_at FROM Bugs WHERE reported_at >= :startAt AND reported_at < :endAt AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':startAt', $startAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':endAt', $endAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':status', $status->value, \PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } ૝ఆ͠ͳ͚Ε͹ͳΒͳ͍ঢ়گ͕͞Βʹݮͬͨ
  33. Agenda  ܕએݴ  ྻڍܕ  ϞσϦϯά  ෆมੑͱ౳Ձੑ 

    ׬શੑ  ੹຿ͷ഑ஔ 👉
  34. ϞσϦϯάͰ ᐆດ͞ΛݮΒ͢

  35. public function testFindAll(): void { $xmas2020 = new \DateTime('2020-12-25'); $xmas2021

    = new \DateTime('2021-12-25'); $bugs = $this->repo->findAll( $xmas2020, $xmas2021, Status::New ); $this->assertCount(3, $bugs); } ར༻ଆ Ҿ਺͋ΔͷͰ ͦΕͧΕͷ໾ׂ͕Θ͔Γʹ͍͘
  36. public function testFindAll(): void { $xmas2020 = new \DateTime('2020-12-25'); $xmas2021

    = new \DateTime('2021-12-25'); $bugs = $this->repo->findAll( startAt: $xmas2020, endAt: $xmas2021, status: Status::New ); $this->assertCount(3, $bugs); } ໊લ෇͖Ҿ਺ͰՄಡੑ͸޲্͕ͨ͠ɺ ݕࡧൣғʹFOE"U͸ؚ·ΕΔͷ͔ ؚ·Εͳ͍ͷ͔͕఻ΘΒͳ͍ ໊લ෇͖Ҿ਺ͰՄಡੑΛ্͛Δ 1 ) 1 
  37. public function testFindAll(): void { $xmas2020 = new \DateTime('2020-12-25'); $xmas2021

    = new \DateTime('2021-12-25'); $bugs = $this->repo->findAll( startAt: $xmas2020, endAtExclusive: $xmas2021, status: Status::New ); $this->assertCount(3, $bugs); } ؚ·ΕΔؚ·Εͳ͍Λ໊લͰࣔͯ͠΋͍͍͚ΕͲ Ҿ਺ͷ໊લͰࣔͯ͠΋ྑ͍͕ɺ ͜ͷઃܭ͸ৗʹద੾ͩΖ͏͔
  38. wྑ͍ΠϯλϑΣʔεͱ͸࣍ͷͭͷ৚݅Λຬͨ ͢ΠϯλϑΣʔε wਖ਼͘͠࢖༻͢Δํ͕ૢ࡞ϛεΛ͢ΔΑΓ؆୯ wޡͬͨ࢖͍ํΛ͢Δ͜ͱ͕ࠔ೉ ΤοηΠਖ਼͍͠࢖͍ํΛ؆୯ʹɺޡͬͨ࢖͍ํΛࠔ೉ʹ IUUQTXXXPSFJMMZDPKQCPPLT ΋ͬͱޡΓ΍͢͞ΛݮΒ͢ઃܭΛ͍ͨ͠

  39. ର৅ͷྖҬʢυϝΠϯʣ ʹֶ͍ͭͯͼɺ ϞσϦϯάʹΑͬͯ ޡΓ΍͢͞ΛݮΒ͢

  40. ൣғʢ۠ؒʣʹֶ͍ͭͯͿ୺఺ IUUQTKBXJLJQFEJBPSHXJLJ&$#"&@&#&"%"

  41. ൣғʢ۠ؒʣʹֶ͍ͭͯͿด۠ؒɺ։۠ؒ IUUQTXFLBOB[BXBJUBDKQNBUIDBUFHPSZPUIFSTZVVHPVIFOLBOUFYDHJ UBSHFUNBUIDBUFHPSZPUIFSTZVVHPVLVLBOOIUNM

  42. ൣғʢ۠ؒʣʹֶ͍ͭͯͿ൒։۠ؒ IUUQTXFLBOB[BXBJUBDKQNBUIDBUFHPSZPUIFSTZVVHPVIFOLBOUFYDHJ UBSHFUNBUIDBUFHPSZPUIFSTZVVHPVLVLBOOIUNM

  43. final class DateTimeRange { public function __construct( public readonly DateTimeEndpoint

    $startAt, public readonly DateTimeEndpoint $endAt, ) {} } final class DateTimeEndpoint { public function __construct( public readonly \DateTime $value, public readonly bool $inclusive, ) {} } 1)1Ͱ͸ $POTUSVDUPS1SPQFSUZ1SPNPUJPOͱ 3FBEPOMZ1SPQFSUJFTͷ૊Έ߹ΘͤͰ γϯϓϧʹॻ͚Δ লུͤ͞ͳ͍ʢσϑΥϧτ஋Λ༻ҙ͠ͳ͍ʣ͜ͱͰ ୺఺Λҙࣝ͠΍͘͢ͳΔ جૅͱͳΔܕΛ͍ͭͬͯ͘͘ 1 ) 1  
  44. ͜ΕͰؒҧ͍ʹ͘͘ͳ͕ͬͨɺࠓ౓͸࢖͏ͷ͕΍΍໘౗ʜʜ public function testFindAll(): void { $startAt = new DateTimeEndpoint(

    value: new \DateTime('2020-12-25'), inclusive: true ); $endAt = new DateTimeEndpoint( value: new \DateTime('2021-12-25'), inclusive: false ); $range = new DateTimeRange($startAt, $endAt); $bugs = $this->repo->findAll(searchRange: $range, status: Status::New); $this->assertCount(3, $bugs); }
  45. wྑ͍ΠϯλϑΣʔεͱ͸࣍ͷͭͷ৚݅Λຬͨ ͢ΠϯλϑΣʔε wਖ਼͘͠࢖༻͢Δํ͕ૢ࡞ϛεΛ͢ΔΑΓ؆୯ wޡͬͨ࢖͍ํΛ͢Δ͜ͱ͕ࠔ೉ ΤοηΠਖ਼͍͠࢖͍ํΛ؆୯ʹɺޡͬͨ࢖͍ํΛࠔ೉ʹ IUUQTXXXPSFJMMZDPKQCPPLT ݱঢ়Ͱ͸ਖ਼͘͠࢖͏ͷ͕΍΍໘౗

  46. खݎ͞ͱॻ͖΍͢͞ͷཱ྆ΛࢼΈΑ͏ final class DateTimeEndpoint { public function __construct( public readonly

    \DateTime $value, public readonly bool $inclusive, ) {} public static function including(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTime($dateTimeStr), inclusive: true ); } public static function excluding(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTime($dateTimeStr), inclusive: false ); } } ʢ ෬ ઢ ʣ
  47. ͜ΕͰར༻ଆΛ୹͘ॻ͚ΔΑ͏ʹͳͬͨ public function testFindAll(): void { $range = new DateTimeRange(

    startAt: DateTimeEndpoint::including('2020-12-25'), endAt: DateTimeEndpoint::excluding('2021-12-25') ); $bugs = $this->repo->findAll(searchRange: $range, status: Status::New); $this->assertCount(3, $bugs); } ʢ ෬ ઢ ʣ
  48. Agenda  ܕએݴ  ྻڍܕ  ϞσϦϯά  ෆมੑͱ౳Ձੑ 

    ׬શੑ  ੹຿ͷ഑ஔ 👉
  49. ෆมੑ JNNVUBCJMJUZ  ঢ়ଶมԽΛഇ͢Δ

  50. ผ໊ࢀর໰୊ "MJBTJOH

  51. 4VCTDSJQUJPOΦϒδΣΫτΛྫʹผ໊ࢀর໰୊ΛֶͿ class Subscription implements \Stringable { public function __construct( private

    readonly string $name, private readonly DateTimeRange $range, ) { } public function __toString(): string { $startAt = $this->range->startAt->value; $endAt = $this->range->endAt->value; return $this->name . '(' . $startAt->format('Y-m-d') . ' -> ' . $endAt->format('Y-m-d') . ')'; } public function renew(): void { $oneYear = \DateInterval::createFromDateString('1 year'); $this->range->startAt->value->add($oneYear); $this->range->endAt->value->add($oneYear); } } ઌ΄Ͳ࡞੒ͨ͠%BUF5JNF3BOHFΦϒδΣΫτΛ࢖ͬͯΈΔ # " % αϒεΫϦϓγϣϯܖ໿Λ೥ߋ৽͢Δϝιου
  52. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊
  53. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊
  54. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊
  55. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊
  56. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊ 1IQ4UPSNͷαϒεΫϦϓγϣϯܖ໿Λ೥ߋ৽͢Δ
  57. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊
  58. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm, PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2023-01-01 00:00:00  ผ໊ࢀর໰୊ 1Z$IBSNͷܖ໿ظؒ΋ ߋ৽͞Εͯ͠·͍ͬͯΔ
  59. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm->renew(); echo $phpstorm, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $pycharm, PHP_EOL; // PyCharm(2022-01-01 -> 2023-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s'), PHP_EOL; // 2022-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s'), PHP_EOL; // 2023-01-01 00:00:00 ผ໊ࢀর໰୊
  60. Կ͕ݪҼͩͬͨͷ͔

  61. Կ͕ݪҼͩͬͨͷ͔ class Subscription implements \Stringable { public function __construct( private

    readonly string $name, private readonly DateTimeRange $range, ) { } public function __toString(): string { $startAt = $this->range->startAt->value; $endAt = $this->range->endAt->value; return $this->name . '(' . $startAt->format('Y-m-d') . ' -> ' . $endAt->format('Y-m-d') . ')'; } public function renew(): void { $oneYear = \DateInterval::createFromDateString('1 year'); $this->range->startAt->value->add($oneYear); $this->range->endAt->value->add($oneYear); } } # " % ͕͜͜໰୊ ࠶୅ೖͰ͸ͳ͘ͱ΋ഁյతมߋ͕ग़དྷͯ͠·͏
  62. 1)1ͷ%BUF5JNFΫϥε͕ഁյతมߋΛڐͯ͠͠·͏ final class DateTimeEndpoint { public function __construct( public readonly

    \DateTime $value, public readonly bool $inclusive, ) {} } final class DateTimeRange { public function __construct( public readonly DateTimeEndpoint $startAt, public readonly DateTimeEndpoint $endAt, ) {} (後略) 1)1ͷ%BUF5JNFΫϥε͕ഁյతมߋΛڐͯ͠͠·͍ͬͯͨ # " %
  63. ෆมΦϒδΣΫτΛ͔ͭ͏

  64. %BUF5JNFͱ%BUF5JNF*NNVUBCMF IUUQTXXXQIQOFUNBOVBMKBDMBTTEBUFUJNFJNNVUBCMFQIQ

  65. /** * @test * @group learning */ public function DateTimeのaddは自身の状態を変更しつつ自身を返す():

    void { $halloween = new \DateTime('2021-10-31'); $oneYear = \DateInterval::createFromDateString('1 year'); $halloween2022 = $halloween->add($oneYear); $this->assertSame($halloween, $halloween2022); $this->assertEquals('2022-10-31', $halloween->format('Y-m-d')); $this->assertEquals('2022-10-31', $halloween2022->format('Y-m-d')); } /** * @test * @group learning */ public function DateTimeImmutableのaddは自身の状態を変更せず新しい状態を伴う新しいインスタンスを返す(): void { $halloween = new \DateTimeImmutable('2021-10-31'); $oneYear = \DateInterval::createFromDateString('1 year'); $halloween2022 = $halloween->add($oneYear); $this->assertNotSame($halloween, $halloween2022); $this->assertEquals('2021-10-31', $halloween->format('Y-m-d')); $this->assertEquals('2022-10-31', $halloween2022->format('Y-m-d')); } %BUF5JNFͱ%BUF5JNF*NNVUBCMFͷҧ͍Λֶशςετʹ͢Δ ֶशςετʢֶͼ͕໨తͷςετʣΛ ݟ෼͚ΔͨΊʹ MFBSOJOHλάΛ͚͍ͭͯ·͢
  66. /** * @test * @group learning */ public function DateTimeのaddは自身の状態を変更しつつ自身を返す():

    void { $halloween = new \DateTime('2021-10-31'); $oneYear = \DateInterval::createFromDateString('1 year'); $halloween2022 = $halloween->add($oneYear); $this->assertSame($halloween, $halloween2022); $this->assertEquals('2022-10-31', $halloween->format('Y-m-d')); $this->assertEquals('2022-10-31', $halloween2022->format('Y-m-d')); } /** * @test * @group learning */ public function DateTimeImmutableのaddは自身の状態を変更せず新しい状態を伴う新しいインスタンスを返す(): void { $halloween = new \DateTimeImmutable('2021-10-31'); $oneYear = \DateInterval::createFromDateString('1 year'); $halloween2022 = $halloween->add($oneYear); $this->assertNotSame($halloween, $halloween2022); $this->assertEquals('2021-10-31', $halloween->format('Y-m-d')); $this->assertEquals('2022-10-31', $halloween2022->format('Y-m-d')); } %BUF5JNFͱ%BUF5JNF*NNVUBCMFͷҧ͍Λֶशςετʹ͢Δ
  67. ෆมΦϒδΣΫτΛͭ͘Δ

  68. final class DateTimeEndpoint { public function __construct( public readonly \DateTime

    $value, public readonly bool $inclusive, ) {} public static function including(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTime($dateTimeStr), inclusive: true ); } public static function excluding(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTime($dateTimeStr), inclusive: false ); } } .VUBCMFͳ%BUF5JNF&OEQPJOU # " %
  69. final class DateTimeEndpoint { public function __construct( public readonly \DateTimeImmutable

    $value, public readonly bool $inclusive, ) {} public static function including(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTimeImmutable($dateTimeStr), inclusive: true ); } public static function excluding(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTimeImmutable($dateTimeStr), inclusive: false ); } } *NNVUBCMFͳ%BUF5JNF&OEQPJOU %BUF5JNF*NNVUBCMFΛ࢖͍ͬͯ͘ ʢ ෬ ઢ ʣ
  70. class Subscription implements \Stringable { public function __construct( private readonly

    string $name, private readonly DateTimeRange $range, ) { } (中略) public function renew(): void { $oneYear = \DateInterval::createFromDateString('1 year'); $this->range->startAt->value->add($oneYear); $this->range->endAt->value->add($oneYear); } ʢ.VUBCMFͩͬͨ͜Ζͷʣ4VCTDSJQUJPO %BUF5JNFΦϒδΣΫτͷ ഁյతมߋΛલఏʹ͍ͯͨ͠ มߋલ͸%BUF5JNF3BOHF͕อ࣋͢Δ %BUF5JNF&OEQPJOU͕NVUBCMFͩͬͨ # " %
  71. class Subscription implements \Stringable { public function __construct( private readonly

    string $name, private readonly DateTimeRange $range, ) { } (中略) public function renew(): Subscription { $oneYear = \DateInterval::createFromDateString('1 year'); $startAt = $this->range->startAt; $endAt = $this->range->endAt; return new Subscription( name: $this->name, range: new DateTimeRange( startAt: new DateTimeEndpoint( value: $startAt->value->add($oneYear), inclusive: $startAt->inclusive ), endAt: new DateTimeEndpoint( value: $endAt->value->add($oneYear), inclusive: $endAt->inclusive ) ) ); } *NNVUBCMFͳ4VCTDSJQUJPO ࣗ਎ͷঢ়ଶΛมߋͤͣɺ ৽͍͠ঢ়ଶΛอ࣋ͨ͠ෆมΦϒδΣΫτΛฦ͢ %BUF5JNF3BOHF͕อ࣋͢Δ %BUF5JNF&OEQPJOU͕อ࣋͢Δ೔෇ܕ͕ %BUF5JNF*NNVUBCMFʹมߋ͞Εͨ
  72. ෆมΦϒδΣΫτͰͷ ໰୊ղܾ

  73. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  74. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  75. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  76. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  77. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022->toString() . PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  78. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm->toString() . PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  79. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm->toString() . PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  80. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s') . PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ 👍
  81. $year2021 = new DateTimeRange( startAt: DateTimeEndpoint::including('2021-01-01'), endAt: DateTimeEndpoint::excluding('2022-01-01') ); $phpstorm

    = new Subscription('PhpStorm', $year2021); echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) $pycharm = new Subscription('PyCharm', $year2021); echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) $phpstorm2022 = $phpstorm->renew(); echo $phpstorm2022, PHP_EOL; // PhpStorm(2022-01-01 -> 2023-01-01) echo $phpstorm, PHP_EOL; // PhpStorm(2021-01-01 -> 2022-01-01) echo $pycharm, PHP_EOL; // PyCharm(2021-01-01 -> 2022-01-01) echo $year2021->startAt->value->format('Y-m-d H:i:s'), PHP_EOL; // 2021-01-01 00:00:00 echo $year2021->endAt->value->format('Y-m-d H:i:s'), PHP_EOL; // 2022-01-01 00:00:00 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ
  82. ͕ͩͪΐͬͱ ଴ͬͯ΄͍͠

  83. ί ϯ ε τ ϥ Ϋ λ ͸  

    Ұ ճ ͠ ͔ ݺ ΂ ͳ ͍ ͱ   ࡨ ֮ ͠ ͯ ͍ ͨ ʁ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ Ұ ମ  ͍ ͭ ͔ Β ŠŠŠŠŠŠ
  84. /** * @test * @group learning */ public function コンストラクタをもう一度呼ぶと破壊的変更ができてしまう():

    void { $dt = new \DateTimeImmutable('2021-12-24'); $this->assertSame('2021-12-24', $dt->format('Y-m-d')); $dt->__construct('2022-01-01'); $this->assertSame('2022-01-01', $dt->format('Y-m-d')); } ͳΜʜʜͩͱʜʜʂʁ  ޙ೔ஊ͜ͷߨԋΛ͖͔͚ͬʹQIQTSDʹJTTVFͱͯ͠ใࠂ͞Εɺٞ࿦͕ߦΘΕ͍ͯ·͢ɻ IUUQTHJUIVCDPNQIQQIQTSDJTTVFT ޙ೔ஊ͜ͷߨԋΛ͖͔͚ͬʹ1)14UBO 1TBMNʹػೳఏҊ͕ߦΘΕɺ 1)14UBOʹ!NVOP@͞Μ͕࡞੒ͨ͠QVMMSFRVFTU͕࠾༻͞ΕɺϦϦʔε͞Ε·ͨ͠ɻ IUUQTHJUIVCDPNQIQTUBOQIQTUBOTSDQVMM
  85. /** * @test * @group debugging */ public function 生成時に渡したvalueを後から破壊されても影響を受けないこと():

    void { $dt = new \DateTimeImmutable('2020-12-25'); $endpoint = new DateTimeEndpoint(value: $dt, inclusive: false); $this->assertSame('2020-12-25', $endpoint->value->format('Y-m-d')); $dt->__construct('2022-01-01'); $this->assertSame('2020-12-25', $endpoint->value->format('Y-m-d')); } ෆ҆Λςετʹ຋༁ͯ͠ݕূ͢Δˠෆ҆తத 1) PhperKaigi\DateTimeEndpointTest::生成時に渡したvalueを後から破壊されても影響を受けないこと Failed asserting that two strings are identical. --- Expected +++ Actual @@ @@ -'2020-12-25' +'2022-01-01' ໰୊Λ࠶ݱ͢Δςετʹ͸ EFCVHHJOHλάΛ͚͍ͭͯ·͢
  86. %BUF5JNF*NNVUBCMF͕ JNNVUBCMFͰ͸ͳ͍ͱͨ͠Β ઃܭΛͲ͏͢Δ͔ %BUF5JNF*NNVUBCMFͷίϯετϥΫλϚδοΫϝιουʢA@@DPOTUSVDUAʣΛ໌ࣔతʹݺͿ͜ͱʹΑΔഁյత มߋ͸ɺݱ࣮తʹ͸΄ͱΜͲ৺഑͠ͳ͘ͱ΋ྑ͍ɺߨԋͷͨΊͷۃ୺ͳྫͰ͋Δͱ͸ݴ͑ΔͰ͠ΐ͏ɻ ࠓޙ͸੩తղੳπʔϧʹΑͬͯ͜͏͍ͬͨޡΓΛ๷͙ํ޲ʹਐΜͰ͍͘ͱࢥΘΕ·͢ɻ ͦ͜Ͱɺ͔͜͜ΒຊߨԋͰ͸ɺՄมΦϒδΣΫτΛෆมΦϒδΣΫτͷҰ෦ͱͯ͠ઃܭ͢Δࡍͷ ஫ҙ఺΍ҰൠతͳςΫχοΫΛɺ%BUF5JNF*NNVUBCMFΛՄมͷ΋ͷͱͯ͠ѻ͏͜ͱͰઆ໌͍͖ͯ͠·͢ɻ

  87. ίϯετϥΫλ಺Ͱ๷ޚతDMPOFΛߦ͏ final class DateTimeEndpoint { public readonly \DateTimeImmutable $value; public

    function __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = clone $value; } ॳظԽ࣌ʹ౉͞Εͨ%BUF5JNF*NNVUBCMFΛ DMPOFͯ͠อ࣋͢Δ $ ./vendor/bin/phpunit tests/ PHPUnit 9.5.19 #StandWithUkraine ...................................................... 54 / 54 (100%) Time: 00:00.030, Memory: 6.00 MB OK (54 tests, 115 assertions) ΍͔ͬͨ
  88. QVCMJDQSPQFSUZ͕ %BUF5JNF*NNVUBCMFͩͬͨΒ ͋ͱ͔ΒഁյͰ͖ΔͷͰ͸ʁ

  89. /** * @test * @group learning * @group debugging */

    public function readonlyなvalueプロパティのコンストラクタを呼んで破壊できるか(): void { $endpoint = new DateTimeEndpoint(value: new \DateTimeImmutable('2020-12-25'), inclusive: false); $this->assertSame('2020-12-25', $endpoint->value->format('Y-m-d')); $endpoint->value->__construct('2022-01-01'); $this->assertSame('2020-12-25', $endpoint->value->format('Y-m-d')); } 1) PhperKaigi\DateTimeEndpointTest::readonlyなvalueプロパティのコンストラクタを呼んで破壊できるか Failed asserting that two strings are identical. --- Expected +++ Actual @@ @@ -'2020-12-25' +'2022-01-01' ݁ہݺ΂ͯ͠·͏ ෆ҆Λςετʹ຋༁ͯ͠ݕূ͢Δˠෆ҆తத
  90. ݁࿦ NVUBCMFͳΦϒδΣΫτ͸ SFBEPOMZQSPQFSUZͰ͋ͬͯ΋ QVCMJDQSPQFSUZͱͯ͠࿐ग़͍ͯ͠ΔݶΓ ޙ͔ΒഁյͰ͖ͯ͠·͏

  91. ઃܭมߋ๷ޚతίϐʔͱ๷ޚతΞΫηοα final class DateTimeEndpoint { private readonly \DateTimeImmutable $value; public

    function __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = clone $value; } public function value(): \DateTimeImmutable { return clone $this->value; } ˞ϓϩύςΟͱϝιουͷҰ؏ੑରশੑ่͕Εͯ͠·ͬͨͷͰ $PNQVUFE1SPQFSUZͷΑ͏ͳ࢓૊ΈΛ ಋೖ͍ͨ͠ͱ͜ΖͰ͕͢ 1)1ݴޠ࢓༷ʹ͸·ͩͳ͍ͷͰ ຊߨԋͰ͸είʔϓ֎ͱ͠·͢ ॳظԽ࣌ʹ౉͞Εͨ%BUF5JNF*NNVUBCMFΛ DMPOFͯ͠อ࣋͢Δ HFUUFSͰNVUBCMFͳΦϒδΣΫτͷࢀরΛฦ͢ͱ ഁյతมߋ͕͋Γ͏ΔͨΊDMPOFͨ͠΋ͷΛฦ͢ ϓϩύςΟ͸QSJWBUF͔ͭSFBEPOMZʹ ʢ ෬ ઢ ʣ
  92. ઃܭมߋ ϓϩύςΟˠϝιου ʹ͋ΘͤͯςετΛௐ੔͠੒ޭΛ֬ೝ /** * @test * @group debugging */

    public function 生成時に渡したvalueを後から破壊されても影響を受けないこと(): void { $dt = new \DateTimeImmutable('2020-12-25'); $endpoint = new DateTimeEndpoint(value: $dt, inclusive: false); $this->assertSame('2020-12-25', $endpoint->value()->format('Y-m-d')); $dt->__construct('2022-01-01'); $this->assertSame('2020-12-25', $endpoint->value()->format('Y-m-d')); } /** * @test * @group debugging */ public function valueメソッドの戻り値を破壊しても影響を受けないこと(): void { $endpoint = new DateTimeEndpoint(value: new \DateTimeImmutable('2020-12-25'), inclusive: false); $this->assertSame('2020-12-25', $endpoint->value()->format('Y-m-d')); $endpoint->value()->__construct('2022-01-01'); $this->assertSame('2020-12-25', $endpoint->value()->format('Y-m-d')); } ΍͔ͬͨ
  93. Ͱ΋%BUF5JNF*NNVUBCMF ͸ fi OBMΫϥεͰ͸ͳ͍ɻ ͭ·ΓܧঝͰ͖Δɻ DMPOFͰେৎ෉ͩΖ͏͔ʁ

  94. %BUF5JNF*NNVUBCMFͷअѱͳαϒΫϥεΛ࡞ͬͯΈΔ class DestructiveDateTime extends \DateTimeImmutable { private static array $instances;

    public function __construct($datetime, $timezone = null) { parent::__construct($datetime, $timezone); self::$instances[] = $this; } public function __clone(): void { self::$instances[] = $this; } public static function bringEverythingBackToEpoch(): void { // 全てを無に還すッ……!! foreach(self::$instances as $dt) { $dt->__construct('1970-01-01T00:00:00.000000+00:00'); } } } TUBUJDྖҬʹશΠϯελϯε΁ͷࢀরΛอ࣋͢Δ TUBUJDྖҬʹશΠϯελϯε΁ͷࢀরΛอ࣋͢Δ શΠϯελϯεʹഁյతมߋΛߦ͏ अ ѱ
  95. ѱҙ͋ΔαϒΫϥεʹΑΔഁյ͕Ͱ͖ͯ͠·ͬͨ /** * @test * @group debugging */ public function

    悪意あるサブクラスによる破壊(): void { $ddt = new DestructiveDateTime('2020-12-25', new \DateTimeZone('Asia/Tokyo')); $endpoint = new DateTimeEndpoint(value: $ddt, inclusive: false); $this->assertSame('2020-12-25T00:00:00+09:00', $endpoint->value()->format('Y-m-d\TH:i:sP')); DestructiveDateTime::bringEverythingBackToEpoch(); $this->assertSame('1970-01-01T00:00:00+00:00', $endpoint->value()->format('Y-m-d\TH:i:sP')); }  अ ѱ
  96. ͜͜·Ͱͷ໌നͳѱҙ͸ແ͘ͱ΋ ϛε͸ى͜Δ ϋϯϩϯͷం౛ͱ΋ݴ͑Δ

  97. %BUF5JNF*NNVUBCMFͷෆ஫ҙͳαϒΫϥεΛ࡞ͬͯΈΔ class TimeZoneCachingDateTime extends \DateTimeImmutable { private readonly ?\DateTimeZone $tz;

    public function __construct($datetime, $timezone = null) { parent::__construct($datetime, $timezone); $this->tz = $timezone; } public function getTimezone(): \DateTimeZone|false { return $this->tz; } } %BUF5JNF;POF΁ͷࢀরΛอ࣋͢Δ͕ɺ @@DMPOFϝιουͷ࣮૷Λ๨Ε͍ͯΔ ෆ ஫ ҙ
  98. DMPOF͸TIBMMPXDPQZͳͷͰϛε͸ى͜Δ /** * @test * @group learning */ public function

    cloneはシャローコピーなので参照を共有してしまう(): void { $dt = new TimeZoneCachingDateTime('2020-12-25', new \DateTimeZone('Asia/Tokyo')); $endpoint = new DateTimeEndpoint(value: $dt, inclusive: false); $this->assertSame('Asia/Tokyo', $endpoint->value()->getTimezone()->getName()); $endpoint->value()->getTimezone()->__construct('Europe/Berlin'); $this->assertSame('Europe/Berlin', $endpoint->value()->getTimezone()->getName()); } TimeZoneCachingDateTime DateTimeZone TimeZoneCachingDateTime clone DMPOF͸TIBMMPXDPQZͳͷͰࢀরΛڞ༗͢Δ ෆ ஫ ҙ
  99. DMPOFΛ௒͑ͨ ͞Βʹ๷ޚతͳίϐʔ

  100. ͡Ό͋TUSJOHͷSFBEPOMZQSPQFSUZͳΒݎ࿚ͩΖʜʜʂʂ final class DateTimeEndpoint { private readonly string $datetime; private

    readonly string $tzname; public function __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->datetime = $value->format('Y-m-d\TH:i:s.u'); $this->tzname = $value->getTimezone()->getName(); } public function value(): \DateTimeImmutable { return new \DateTimeImmutable($this->datetime, new \DateTimeZone($this->tzname)); } DMPOFΛ࢖ΘͣɺॳظԽ࣌ʹ࣌ࠁͱλΠϜ κʔϯͷ৘ใ͚ͩΛอ࣋͢Δ ΞΫηοαʹ͓͍ͯ ຖճ%BUF5JNF*NNVUBCMFΛੜ੒͢Δ ʢ ෬ ઢ ʣ
  101. ࣦഊ͍ͯͨ͠αϒΫϥεؔ܎ͷςετ͕௨ΔΑ͏ʹͳͬͨ /** * @test * @group debugging */ public function

    悪意あるサブクラスによる破壊の影響を受けないこと(): void { $ddt = new DestructiveDateTime('2020-12-25', new \DateTimeZone('Asia/Tokyo')); $endpoint = new DateTimeEndpoint(value: $ddt, inclusive: false); $this->assertSame('2020-12-25T00:00:00+09:00', $endpoint->value()->format('Y-m-d\TH:i:sP')); DestructiveDateTime::bringEverythingBackToEpoch(); $this->assertSame('1970-01-01T00:00:00+00:00', $ddt->format('Y-m-d\TH:i:sP')); $this->assertSame('2020-12-25T00:00:00+09:00', $endpoint->value()->format('Y-m-d\TH:i:sP')); } /** * @test * @group debugging */ public function 参照の共有による副作用を生じないこと(): void { $tcdt = new TimeZoneCachingDateTime('2020-12-25', new \DateTimeZone('Asia/Tokyo')); $endpoint = new DateTimeEndpoint(value: $tcdt, inclusive: false); $this->assertSame('Asia/Tokyo', $endpoint->value()->getTimezone()->getName()); $endpoint->value()->getTimezone()->__construct('Europe/Berlin'); $this->assertSame('Asia/Tokyo', $endpoint->value()->getTimezone()->getName()); } ΍͔ͬͨ
  102. Ήɺ͍΍ɺطଘͷςετ͕͍͔ࣦͭ͘ഊ͢Δͧʜʜʁʁ $ ./vendor/bin/phpunit tests/ PHPUnit 9.5.19 #StandWithUkraine .......F...................................F............ 56 /

    56 (100%) Time: 00:00.015, Memory: 6.00 MB There were 2 failures: 1) PhperKaigi\DateTimeEndpointTest::valueのタイムゾーンが異なっても同じ時刻を指している場合は等価とみなす Failed asserting that two objects are equal. --- Expected +++ Actual @@ @@ PhperKaigi\DateTimeEndpoint Object ( - 'datetime' => '2021-12-24T15:00:00.000000' - 'tzname' => '+00:00' + 'datetime' => '2021-12-25T00:00:00.000000' + 'tzname' => 'Asia/Tokyo' 'inclusive' => true ) /usr/src/myapp/tests/PhperKaigi/DateTimeEndpointTest.php:77 2) PhperKaigi\DateTimeRangeTest::保持する端点が異なるタイムゾーンであっても同じ時刻の区間を指していれば等価とみなす Failed asserting that two objects are equal. --- Expected +++ Actual @@ @@ PhperKaigi\DateTimeRange Object ( 'startAt' => PhperKaigi\DateTimeEndpoint Object ( - 'datetime' => '2021-12-24T15:00:00.000000' - 'tzname' => '+00:00' + 'datetime' => '2021-12-25T00:00:00.000000' + 'tzname' => 'Asia/Tokyo' 'inclusive' => true ) 'endAt' => PhperKaigi\DateTimeEndpoint Object ( - 'datetime' => '2022-12-24T15:00:00.000000' - 'tzname' => '+00:00' + 'datetime' => '2022-12-25T00:00:00.000000' + 'tzname' => 'Asia/Tokyo' 'inclusive' => true ) ) /usr/src/myapp/tests/PhperKaigi/DateTimeRangeTest.php:100 FAILURES! Tests: 56, Assertions: 120, Failures: 2.
  103. ࣦഊ͍ͯ͠ΔςετΛҰͭݟͯΈΔ /** * @test */ public function valueのタイムゾーンが異なっても同じ時刻を指している場合は等価とみなす(): void {

    $utc = new \DateTimeImmutable('2021-12-24T15:00:00+00:00'); $jst = new \DateTimeImmutable('2021-12-25T00:00:00', new \DateTimeZone('Asia/Tokyo')); $ep1 = new DateTimeEndpoint(value: $utc, inclusive: true); $ep2 = new DateTimeEndpoint(value: $jst, inclusive: true); $this->assertEquals($ep1, $ep2); $this->assertTrue($ep1 == $ep2); } 1) PhperKaigi\DateTimeEndpointTest::valueのタイムゾーンが異なっても同じ時刻を指している場合は等価とみなす Failed asserting that two objects are equal. --- Expected +++ Actual @@ @@ PhperKaigi\DateTimeEndpoint Object ( - 'datetime' => '2021-12-24T15:00:00.000000' - 'tzname' => '+00:00' + 'datetime' => '2021-12-25T00:00:00.000000' + 'tzname' => 'Asia/Tokyo' 'inclusive' => true ) 🤔
  104. ஋ΦϒδΣΫτͱ ౳Ձੑ

  105. 1)1ʹ͓͚ΔΦϒδΣΫτͷ౳Ձੑ IUUQTXXXQIQOFUNBOVBMKBMBOHVBHFPPQPCKFDUDPNQBSJTPOQIQ

  106. ஋ΦϒδΣΫτͷϦϑΝΫλϦϯάࣦഊΛςετ͕ڭ͑ͯ͘Εͨ 1) PhperKaigi\DateTimeEndpointTest::valueのタイムゾーンが異なっても 同じ時刻を指している場合は等価とみなす Failed asserting that two objects are

    equal. --- Expected +++ Actual @@ @@ PhperKaigi\DateTimeEndpoint Object ( - 'datetime' => '2021-12-24T15:00:00.000000' - 'tzname' => '+00:00' + 'datetime' => '2021-12-25T00:00:00.000000' + 'tzname' => 'Asia/Tokyo' 'inclusive' => true ) 🦁ॻ͍ͯͯΑ͔ͬͨࣗಈςετ🦁 %BUF5JNF*NNVUBCMF͸λΠϜκʔϯΛ·͍ͨͩ౳ՁੑΛఏڙ͍͕ͯͨ͠ɺ TUSJOHͷϓϩύςΟͭʹ෼ղͨ͠Β౳Ձੑ͕੒Γཱͨͳ͘ͳͬͨ
  107. IUUQTXXXQIQOFUNBOVBMKBEBUFUJNFJNNVUBCMFDSFBUFGSPNJOUFSGBDFQIQ ղܾࡦDPOWFSTJPOGBDUPSZΛ࢖͓͏ 1 ) 1 

  108. ղܾࡦDPOWFSTJPOGBDUPSZΛ࢖͓͏ final class DateTimeEndpoint { private readonly \DateTimeImmutable $value; public

    function __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = \DateTimeImmutable::createFromInterface($value); } public function value(): \DateTimeImmutable { return \DateTimeImmutable::createFromInterface($this->value); } DPOWFSTJPOGBDUPSZΛ࢖ͬͯ %BUF5JNF*NNVUBCMFͷ ৽͍͠Πϯελϯεʹม׵͢Δ λΠϜκʔϯΛ·͍ͨͩ౳ՁੑΛఏڙ͍ͯ͠Δ%BUF5JNF*NNVUBCMFΛ QSJWBUF͔ͭSFBEPOMZϓϩύςΟͱͯ͠อ࣋͢Δ ΞΫηοαʹ͓͍ͯ΋ίϐʔΛฦ͢ ʢͪ͜Β͸DMPOFͰ΋ྑ͍ʣ 1 ) 1 
  109. ͱ͜ΖͰ %BUF5JNF&OEQPJOUͷ ίϯετϥΫλΛ ճҎ্ݺΜͩΒͲ͏ͳΔͷʁ

  110. /** * @test */ public function DateTimeEndpointのコンストラクタを2回以上呼び出せないこと(): void { $dt

    = new \DateTimeImmutable('2020-12-25'); $endpoint = new DateTimeEndpoint(value: $dt, inclusive: false); $newDate = new \DateTimeImmutable('2022-01-01'); try { $endpoint->__construct(value: $newDate, inclusive: true); } catch(\Error $expected) { $this->assertSame('Cannot modify readonly property PhperKaigi\DateTimeEndpoint::$inclusive', $expected->getMessage()); return; } $this->fail('例外が発生していない'); } ΋ͪΖΜςετΛॻ͜͏ SFBEPOMZQSPQFSUZͷ͋Γ͕ͨΈ͕Θ͔ͬͨ ͜ͷ఺ʹ͓͍ͯ͸SFBEPOMZQSPQFSUZ͸ ศར͔ͭ҆৺ͱ͍͑ͦ͏ &SSPS "TTFSUJPO&SSPSͷ਌ྫ֎ ͕ظ଴஋ͳͷͰUSZͷதʹGBJM͸ॻ͚ͣɺ USZGBJMDBUDIΠσΟΦϜͷมܗͰςετΛॻ͘
  111. ͢΂ͯʹௐ࿨͕๚Εͨ $ ./vendor/bin/phpunit tests/ PHPUnit 9.5.19 #StandWithUkraine ........................................................ 56 /

    56 (100%) Time: 00:00.015, Memory: 6.00 MB OK (56 tests, 122 assertions)
  112. Agenda  ܕએݴ  ྻڍܕ  ϞσϦϯά  ෆมੑͱ౳Ձੑ 

    ׬શੑ  ੹຿ͷ഑ஔ 👉
  113. ݕࡧ։࢝೔࣌ͱ ݕࡧऴྃ೔࣌ͷ ੔߹ੑΛ୲อ͢Δͷ͸ ୭ͷ੹೚

  114. public function findAll(DateTimeRange $searchRange, Status $status): array { $startAt =

    $searchRange->startAt->value(); $endAt = $searchRange->endAt->value(); if ($endAt < $startAt) { throw new \InvalidArgumentException('endAt < startAt'); } $startAtOp = $searchRange->startAt->inclusive ? '<=' : '<'; $endAtOp = $searchRange->endAt->inclusive ? '<=' : '<'; $sql = "SELECT bug_id, summary, reported_at FROM Bugs WHERE status = :status AND :startAt ${startAtOp} reported_at AND reported_at ${endAtOp} :endAt"; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':status', $status->value, \PDO::PARAM_STR); $stmt->bindValue(':startAt', $startAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':endAt', $endAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } ͜͜Ͱ΍Δͷ͸ے͕ѱ͍ fi OE"MMͷೖΓޱͰߦ͏ͷ͸ے͕ѱ͍
  115. ׬શੑ JOUFHSJUZ  ଘࡏ͢ΔͳΒ͹ৗʹਖ਼͍͠ ʢෆม৚͕݅ৗʹ੒Γཱͭʣ ΦϒδΣΫτΛ໨ࢦ͢

  116. 'BJMGBTU ଎΍͔ʹࣦഊͤ͞Δ

  117. 5JQૣΊʹΫϥογϡͤ͞Δ͜ͱ w ίʔυதʹʮ͋Γಘͳ͍ʯͱࢥΘΕΔԿ͔͕ൃੜͨ͠৔߹ɺͦͷ࣌ ఺ͰϓϩάϥϜ͸΋͸΍࣮ߦՄೳͳ΋ͷͱ͸ͳ͍ͬͯͳ͍ w ԿΒ͔ͷ͍͕ٙ͋ΔͷͰ͋Ε͹ɺͲͷΑ͏ͳ৔߹Ͱ΋଎΍͔ʹఀࢭ ͤ͞Δ΂͖ɻ௨ৗͷ৔߹ɺো֐Λ๊͑ͯத్൒୺ʹಈ͍͍ͯΔϓϩ άϥϜΑΓ΋ࢮΜͩϓϩάϥϜͷ΄͏͕μϝʔδ͸গͳ͍ IUUQTXXXPINTIBDPKQCPPL

  118. ࣄલ৚݅ར༻ଆͷ੹೚Ͱ͋Δ৔߹͸ද໌Λ࢖͏ final class DateTimeRange { public function __construct( public readonly

    DateTimeEndpoint $startAt, public readonly DateTimeEndpoint $endAt, ) { assert($startAt->value() < $endAt->value()); } } JOWBSJBOU ৗʹ੒Γཱͭ΂͖ෆม৚݅ ΛࣜͰॻ͘
  119. 1)1ͷBTTFSU IUUQQIQOFUNBOVBMKBGVODUJPOBTTFSUQIQ 1 ) 1 

  120. ද໌Λ࢖Θͳ͍৔߹͸ɺར༻ଆͷ੹೚Ͱ͋Δ͜ͱΛࣔ͢ྫ֎Λ࢖͏ final class DateTimeRange { public function __construct( public readonly

    DateTimeEndpoint $startAt, public readonly DateTimeEndpoint $endAt, ) { if ($startAt->value() > $endAt->value()) { throw new \InvalidArgumentException('startAt > endAt'); } } } ར༻ଆͷޡΓͰ͋Δࢫͷྫ֎Λൃੜͤ͞Δ
  121. Throwable ├── Error │ ├── ArithmeticError │ │ └── DivisionByZeroError

    │ ├── AssertionError │ ├── CompileError │ │ └── ParseError │ ├── FiberError │ ├── TypeError │ │ └── ArgumentCountError │ ├── UnhandledMatchError │ └── ValueError └── Exception ├── ClosedGeneratorException ├── DOMException ├── ErrorException ├── JsonException ├── LogicException │ ├── BadFunctionCallException │ │ └── BadMethodCallException │ ├── DomainException │ ├── InvalidArgumentException │ ├── LengthException │ └── OutOfRangeException ├── PharException ├── ReflectionException ├── RuntimeException │ ├── OutOfBoundsException │ ├── OverflowException │ ├── PDOException │ ├── RangeException │ ├── UnderflowException │ └── UnexpectedValueException └── SodiumException 😇3VOUJNF&YDFQUJPOܥྫ֎Λࣔ͢ 👮-PHJD&YDFQUJPOܥόάΛࣔ͢ 🔥&SSPSܥ಺෦ΤϥʔόάΛࣔ͢ 1)1ͷྫ֎ܧঝߏ଄ 1 ) 1 
  122. ྫ֎ͱද໌ͷ࢖͍෼͚ w ຊདྷͷΤϥʔॲཧʹද໌Λ࢖ͬͯ͸͍͚·ͤΜɻ ද໌͸ى͜Γಘͳ͍͜ͱΛνΣοΫ͢ΔͨΊͷ΋ ͷͰ͢ ୡਓϓϩάϥϚʔୈ൛  w ൃੜ͕༧૝͞ΕΔঢ়گʹ͸ΤϥʔॲཧίʔυΛ࢖ ༻͠ɺൃੜͯ͠͸ͳΒͳ͍ঢ়گʹ͸Ξαʔγϣϯ

    Λ࢖༻͢Δ $0%&$0.1-&5&ୈ൛ IUUQTXXXPINTIBDPKQCPPL IUUQTXXXBNB[PODPKQEQ9
  123. ͕ͩͪΐͬͱ·ͬͯ΄͍͠ɻ্୺఺ͱԼ୺఺͕౳͍͠ͱ͖͸ʁ IUUQTKBXJLJQFEJBPSHXJLJ&$#"&@&#&"%"

  124. Ұ఺ू߹Λ࣮ݱ͢Δද໌όʔδϣϯ final class DateTimeRange { public function __construct( public readonly

    DateTimeEndpoint $startAt, public readonly DateTimeEndpoint $endAt, ) { assert($startAt->value() < $endAt->value() || self::isEquivalentAndInclusive($startAt, $endAt)); } public static function isEquivalentAndInclusive(DateTimeEndpoint $startAt, DateTimeEndpoint $endAt): bool { return $startAt->inclusive && $endAt->inclusive && $startAt->value() == $endAt->value(); } } ෆม৚݅ͷࣜΛߋ৽͢Δ
  125. final class DateTimeRange { public function __construct( public readonly DateTimeEndpoint

    $startAt, public readonly DateTimeEndpoint $endAt, ) { $startAtValue = $startAt->value(); $endAtValue = $endAt->value(); if ($startAtValue > $endAtValue) { throw new \InvalidArgumentException('startAt > endAt'); } if ($startAtValue == $endAtValue) { if (!$startAt->inclusive || !$endAt->inclusive) { throw new \InvalidArgumentException('Both endpoints should be inclusive if startAt == endAt'); } } } } Ұ఺ू߹Λ࣮ݱ͢Δྫ֎όʔδϣϯ ྫ֎όʔδϣϯͱද໌όʔδϣϯͷ ॻ͖ຯ΍ՄಡੑͳͲΛݟൺ΂ͯΈ͍ͯͩ͘͞
  126. Agenda  ܕએݴ  ྻڍܕ  ϞσϦϯά  ෆมੑͱ౳Ձੑ 

    ׬શੑ  ੹຿ͷ഑ஔ 👉
  127. ϨΠϠʔͱ੹຿

  128. w จࣈྻ͔Β%BUF5JNF*NNVUBCMFʹม׵͢Δͷ͸୭ͷ੹೚ Ͳ͜Ͱ΍Δ  w %BUF5JNF*NNVUBCMF͔Βจࣈྻʹม׵͢Δͷ͸୭ͷ੹೚ Ͳ͜Ͱ΍Δ  w ʢ͍··Ͱݟͯݟ͵ৼΓΛ͍ͯͨ͠ʣλΠϜκʔϯΛѻ͏ͷ͸୭ͷ੹೚

    Ͳ͜Ͱ΍Δ ·ͩ͋Δ੹຿ઃܭͷٙ໰఺Λ·ͱΊ͍ͯ͜͏
  129. ෆద੾ͳ৔ॴͰจࣈྻ͔Β %BUF5JNF*NNVUBCMF΁ͷ ม׵Λߦ͍ͬͯΔ͜ͱʹ ؾ͍ͮͯ͠·ͬͨ

  130. final class DateTimeEndpoint { private readonly \DateTimeImmutable $value; public function

    __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = \DateTimeImmutable::createFromInterface($value); } public function value(): \DateTimeImmutable { return \DateTimeImmutable::createFromInterface($this->value); } public static function including(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTimeImmutable($dateTimeStr), inclusive: true ); } public static function excluding(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTimeImmutable($dateTimeStr), inclusive: false ); } } ઃܭͷෆ٢ͳष͍ʹؾ͍ͮͯ͠·ͬͨ # " % จࣈྻ͔Β%BUF5JNF*NNVUBCMF΁ͷม׵ʹ͸ ๲େͳ૊Έ߹Θ͕ͤ͋Δ͠ɺ λΠϜκʔϯͷѻ͍΋ෆे෼ʹͳͬͯ͠·͍ͬͯΔɻ จࣈྻҾ਺ʹλΠϜκʔϯͷΦϑηοτදهΛؚΊͳ͍ ͱσϑΥϧτͷλΠϜκʔϯʹͳͬͯ͠·͏  ͭ·Γ͜͜Ͱߦ͏ͷ͸ෆద੾ɻ จࣈྻ͔Β%BUF5JNF*NNVUBCMF΁ͷม׵ʹ͸ ๲େͳ૊Έ߹Θ͕ͤ͋Δ͠ɺ λΠϜκʔϯͷѻ͍΋ෆे෼ʹͳͬͯ͠·͍ͬͯΔɻ จࣈྻҾ਺ʹλΠϜκʔϯͷΦϑηοτදهΛؚΊͳ͍ ͱσϑΥϧτͷλΠϜκʔϯʹͳͬͯ͠·͏  ͭ·Γ͜͜Ͱߦ͏ͷ͸ෆద੾ɻ
  131. public function testFindAll(): void { $range = new DateTimeRange( startAt:

    DateTimeEndpoint::including('2020-12-25'), endAt: DateTimeEndpoint::excluding('2021-12-25') ); $bugs = $this->repo->findAll(searchRange: $range, status: Status::New); $this->assertCount(3, $bugs); } ࢖͏ͷ͕΍΍໘౗ͩͬͨͷͰɺ୹͘ॻ͚ΔศརͳϝιουΛఏڙ͔ͨͬͨ͠ͷͩͬͨ ༰қ͞ʹدΓ͗ͯ͢ɺ ؒҧ͑΍͢͞΋্͕ͬͯ͠·͍ͬͯͳ͍ͩΖ͏͔ ςετΛ୹͘ॻ͖͍ͨͷͰ༰қ͞ʢ&BTZʣدΓʹόΠΞε͕͔͔ͬͯ͠·ͬͨɻ ςετίʔυͷେ෦෼Ͱ͸ಛʹλΠϜκʔϯΛҙࣝͤͣ୹͘ॻ͍͍͖͍͕ͯͨɺ ίϯτϩʔϥͰ͸λΠϜκʔϯΛҙࣝͯ͠ݫີʹѻ͍͍ͨɻ ςετίʔυ΋ͻͱͭͷίϯςΫετɻ ςετ͔ΒઃܭΛۦಈ͢Δͱ͖ʹ͜ͷόΠΞεʹ஫ҙ͢Δඞཁ͕͋Δɻ # " %
  132. wྑ͍ΠϯλϑΣʔεͱ͸࣍ͷͭͷ৚݅Λຬͨ ͢ΠϯλϑΣʔε wਖ਼͘͠࢖༻͢Δํ͕ૢ࡞ϛεΛ͢ΔΑΓ؆୯ wޡͬͨ࢖͍ํΛ͢Δ͜ͱ͕ࠔ೉ ΤοηΠਖ਼͍͠࢖͍ํΛ؆୯ʹɺޡͬͨ࢖͍ํΛࠔ೉ʹ IUUQTXXXPSFJMMZDPKQCPPLT ༰қ͞ΛٻΊͨ݁Ռɺ ޡͬͨ࢖͍ํΛ͢Δͷ΋༰қʹͳͬͯ͠·ͬͨ ༰қ͞ΛٻΊͨ݁Ռɺ ޡͬͨ࢖͍ํΛ͢Δͷ΋༰қʹͳͬͯ͠·ͬͨ

  133. 4JNQMFͱ&BTZ͸ࠞͥΔͳةݥ IUUQTUXJUUFSDPNU@XBEBTUBUVT IUUQTUXJUUFSDPNU@XBEBTUBUVT

  134. 4JNQMFͱ&BTZΛ ࠞͥͳ͍ &BTZ͞͸ ঢ়گʹΑΓҟͳΔ

  135. final class DateTimeEndpoint { private readonly \DateTimeImmutable $value; public function

    __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = \DateTimeImmutable::createFromInterface($value); } public function value(): \DateTimeImmutable { return \DateTimeImmutable::createFromInterface($this->value); } public static function including(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTimeImmutable($dateTimeStr), inclusive: true ); } public static function excluding(string $dateTimeStr): DateTimeEndpoint { return new DateTimeEndpoint( value: new \DateTimeImmutable($dateTimeStr), inclusive: false ); } } 4JNQMFͱ&BTZ͕ࠞͬͯ͟͠·͍ͬͯͨ Simple Easy # " %
  136. final class DateTimeEndpoint { private readonly \DateTimeImmutable $value; public function

    __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = \DateTimeImmutable::createFromInterface($value); } public function value(): \DateTimeImmutable { return \DateTimeImmutable::createFromInterface($this->value); } public static function including(\DateTimeImmutable $value): DateTimeEndpoint { return new DateTimeEndpoint( value: $value, inclusive: true ); } public static function excluding(\DateTimeImmutable $value): DateTimeEndpoint { return new DateTimeEndpoint( value: $value, inclusive: false ); } } جૅͱͳΔܕ͸4JNQMF͞Λอͭ จࣈྻ͔Βͷม׵ΛѻΘͳ͍ Simple Simple 4 JN Q MF
  137. final class DateTimeShorthandHelper { public static function jst(string $dateTimeStr): \DateTimeImmutable

    { return new \DateTimeImmutable($dateTimeStr, new \DateTimeZone('Asia/Tokyo')); } public static function utc(string $dateTimeStr): \DateTimeImmutable { return new \DateTimeImmutable($dateTimeStr, new \DateTimeZone('UTC')); } } &BTZ͞͸ผͷϨΠϠʔͰఏڙ͢Δɻྫ͑͹ςετ༻ͷϔϧύʔ &BTZ
  138. use PHPUnit\Framework\TestCase; use PhperKaigi\DateTimeShorthandHelper as DT; class DateTimeRangeTest extends TestCase

    { public function testDateRangeShorthand(): void { $range = new DateTimeRange( startAt: DateTimeEndpoint::including(DT::jst('2020-12-25')), endAt: DateTimeEndpoint::excluding(DT::jst('2021-12-25')) ); $this->assertSame('2020-12-25', $range->startAt->value()->format('Y-m-d')); $this->assertSame('2021-12-25', $range->endAt->value()->format('Y-m-d')); } &BTZ͞͸ผͷϨΠϠʔͰఏڙ͢Δɻྫ͑͹ςετ༻ͷϔϧύʔ ςετίʔυ্Ͱ4JNQMFͱ&BTZΛ߹ྲྀͤ͞Δɻ ͜͏ॻ͚Ε͹े෼ͩͬͨɻ
  139. จࣈྻͱ%BUF5JNF*NNVUBCMFͷ૬ޓม׵΍ 
 λΠϜκʔϯͷѻ͍͸ͦΕͧΕ୭͕Ͳ͜Ͱ΍Δ΂͖ʁ

  140. ΈΜͳେ޷͖ͳ$MFBO"SDIJUFDUVSFʹग़ͯ͘Δ ಉ৺ԁΛྫʹͯ͠ߟ͑ͯΈΔ

  141. ੺ͱ੨ͷք໘ɺ૚ͷ๷ޚϥΠϯ͕͋Δ

  142. ਖ਼౰ੑͱݎ࿚ੑ w ࠷దͳΤϥʔॲཧ͸Τϥʔ͕ൃੜͨ͠ιϑτ΢ΣΞͷछྨʹΑΓҟͳΔ w ਖ਼౰ੑͱ͸ɺෆਖ਼֬ͳ݁ՌΛܾͯ͠ฦ͞ͳ͍͜ͱΛҙຯ͢Δɻෆਖ਼֬ͳ ݁ՌΛฦ͘͢Β͍ͳΒɺԿ΋ฦ͞ͳ͍ํ͕·͠Ͱ͋Δ w ݎ࿚ੑͱ͸ɺιϑτ΢ΣΞͷ࣮ߦΛܧଓͰ͖ΔΑ͏ʹखΛਚ͘͢͜ͱͰ ͋ΔɻͦΕʹΑͬͯෆਖ਼֬ͳ݁Ռ͕΋ͨΒ͞ΕΔ͜ͱ͕͋ͬͯ΋͔·Θ ͳ͍

    w ҆શੑʢ΍ਖ਼֬ੑʣΛॏࢹ͢ΔΞϓϦέʔγϣϯͰ͸ɺݎ࿚ੑΑΓ΋ਖ਼ ౰ੑ͕༏ઌ͞ΕΔ܏޲ʹ͋Δ w ίϯγϡʔϚΞϓϦέʔγϣϯͰ͸ɺਖ਼౰ੑΑΓ΋ݎ࿚ੑ͕༏ઌ͞ΕΔ ܏޲ʹ͋Δ IUUQTXXXBNB[PODPKQEQ9
  143. ਖ਼౰ੑ ݎ࿚ੑ ৗʹਖ਼֬͞ɺਖ਼͠͞ΛॏΜ͡Δ ֎ք͔Βͷ༷ʑͳೖྗ΍ग़ྗΛɺίϯςΫετ ʹԠͯ͡ద੾ʹϋϯυϦϯά͠ɺม׵͢Δ

  144. DateTimeImmutable DateTimeRange DateTimeEndpoint DateTimeZone DateTimeZone DateTimeImmutable string string 8FC͔ΒདྷͨจࣈྻΛόϦσʔγϣϯͭͭ͠ɺʢΞϓϦ έʔγϣϯͰద੾ʹอ࣋ͨ͠ʣλΠϜκʔϯΛ൐ͬͯ

    %BUF5JNF*NNVUBCMFʹม׵͢Δ %BUF5JNF*NNVUBCMFΛ λΠϜκʔϯΦϑηοτΛ൐ͬͨ λΠϜελϯϓจࣈྻͱͯ͠ %#ʹ౉͢
  145. ࠓճͷߨԋͷ ࠷ऴతͳઃܭ

  146. final class DateTimeEndpoint { private readonly \DateTimeImmutable $value; public function

    __construct( \DateTimeImmutable $value, public readonly bool $inclusive, ) { $this->value = \DateTimeImmutable::createFromInterface($value); } public function value(): \DateTimeImmutable { return \DateTimeImmutable::createFromInterface($this->value); } public static function including(\DateTimeImmutable $value): DateTimeEndpoint { return new DateTimeEndpoint( value: $value, inclusive: true ); } public static function excluding(\DateTimeImmutable $value): DateTimeEndpoint { return new DateTimeEndpoint( value: $value, inclusive: false ); } } %BUF5JNF&OEQPJOU ۠ؒͷ୺఺Λࣔ͢ɻ ๷ޚతίϐʔɺ๷ޚతΞΫηοα౳Ͱ ঢ়ଶมԽΛ๷͙ɺ ෆมͷ஋ΦϒδΣΫτ
  147. final class DateTimeRange { public function __construct( public readonly DateTimeEndpoint

    $startAt, public readonly DateTimeEndpoint $endAt, ) { $startAtValue = $startAt->value(); $endAtValue = $endAt->value(); if ($startAtValue > $endAtValue) { throw new \InvalidArgumentException('startAt > endAt'); } if ($startAtValue == $endAtValue) { if (!$startAt->inclusive || !$endAt->inclusive) { throw new \InvalidArgumentException('Both endpoints should be inclusive if startAt == endAt'); } } } } %BUF5JNF3BOHF ۠ؒΛࣔ͢ɻ ίϯετϥΫλͰෆม৚݅Λ੒ཱͤ͞ɺ Ҏ߱ঢ়ଶ͕มԽ͠ͳ͍ɺ ׬શੑΛ൐ͬͨ஋ΦϒδΣΫτ
  148. final class BugRepository implements BugRepositoryInterface { const TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s.uP';

    public function __construct( private readonly \PDO $pdo ) { } public function findAll(DateTimeRange $searchRange, Status $status): array { $startAt = $searchRange->startAt->value(); $endAt = $searchRange->endAt->value(); $startAtOp = $searchRange->startAt->inclusive ? '<=' : '<'; $endAtOp = $searchRange->endAt->inclusive ? '<=' : '<'; $sql = "SELECT bug_id, summary, reported_at FROM Bugs WHERE status = :status AND :startAt ${startAtOp} reported_at AND reported_at ${endAtOp} :endAt"; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':status', $status->value, \PDO::PARAM_STR); $stmt->bindValue(':startAt', $startAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->bindValue(':endAt', $endAt->format(self::TIMESTAMP_FORMAT), \PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(\PDO::FETCH_CLASS, Bug::class); } } #VH3FQPTJUPSZ fi OE"MMͷҾ਺͕ଘࡏ͢Δ࣌఺Ͱطʹਖ਼͍͠ঢ়ଶͳͷͰ ʢ׬શੑʣɺ๷ޚతνΣοΫ͸ෆཁʹͳΔ
  149. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ w ๷ޚతϓϩάϥϛϯάͱ͸ѱ͍ίʔυʹឺ૑ߣΛ͋ͯΔ͜ͱͰ͸ͳ͘ɺྑࣝ͋Δ࣮ફͷੵΈॏͶ w ܕએݴͰ૝ఆ͢΂͖ೖྗͷܕΛߜΓɺ๷ޚతνΣοΫΛେ෯ʹݮΒ͢ w ྻڍܕͰऔΓಘΔ஋ΛߜΓɺ๷ޚతνΣοΫΛେ෯ʹݮΒ͢ w جૅͱͳΔܕΛϞσϦϯά͠ɺᐆດ͞΍ؒҧ͍΍͢͞Λ࡟ݮ͢Δ w

    جૅͱͳΔܕΛෆมΦϒδΣΫτʹ͠ɺঢ়ଶมԽ΍෭࡞༻ʹىҼ͢ΔόάΛ༧๷͢Δ w جૅͱͳΔܕΛ஋ΦϒδΣΫτʹ͠ɺΠϯελϯεͰ͸ͳ͘஋Ͱ౳ՁੑΛ൑அ͢Δ w ΦϒδΣΫτੜ੒࣌ʹෆม৚݅Λ੒ཱͤ͞ɺ͔ͭͦͷΦϒδΣΫτ͕ෆมΦϒδΣΫτͰ͋Δͳ Β͹ɺੜ੒͞ΕͨΦϒδΣΫτ͸ৗʹਖ਼͍͠ʢ׬શੑʣ w 4JNQMF͞ʢ֓೦ͱͯ͠ͷཁૉͷগͳ͞ʣͱ&BTZ͞ʢख਺ͷগͳ͞ʣΛෆ༻ҙʹࠞͥͳ͍ w ਖ਼౰ੑΛॏΜ͡ΔϨΠϠʔͱݎ࿚ੑΛॏΜ͡ΔϨΠϠʔΛ෼͚Δ w ઃܭͱ͸੹຿ͷ࠷ద഑ஔΛٻΊଓ͚Δ͜ͱɻ୭͕ԿΛ஌͍ͬͯͯԿΛ஌Δ΂͖Ͱͳ͍͔ɺԿΛ΍ Δ΂͖ͰԿΛ΍Δ΂͖Ͱͳ͍͔Λৗʹߟ͑ଓ͚Δ͜ͱ
  150. ࢀߟจݙ