Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

$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 ) ) ࣮ߦ͢Δͱɺ͜Μͳ݁Ռ

Slide 5

Slide 5 text

ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % 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); }

Slide 6

Slide 6 text

ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % 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); } 🙅ςʔϒϧ໊΍ΧϥϜ໊͕୭͔ʹมߋ͞Εͨ 🙅 ͜͜Ͱ σʔλϕʔε઀ଓΤϥʔ

Slide 7

Slide 7 text

ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % 🙅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); }

Slide 8

Slide 8 text

ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % 🙅#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); }

Slide 9

Slide 9 text

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); } ॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦʁ # " % ͚ͬ͜͏͋Δͳ😇 ᶃ ᶄ ᶅ

Slide 10

Slide 10 text

ෆ۩߹ͷൃݟ͕ ஗ΕΕ͹஗ΕΔ΄Ͳ ই͸ਂ͘ͳΔ IUUQTXXXBTURCPSHQSFTTSPPN*452#@$FSUJ fi DBUJPO@/FXT@@IUNM

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

💀ςʔϒϧ໊΍ΧϥϜ໊͕୭͔ʹมߋ͞Εͨ 👮QBSBNT͕OVMM 👮QBSBNTͷΩʔ໊΍਺ͷෆҰக 👮QBSBNTͷ஋͕จࣈྻʹม׵ෆೳ 👮QBSBNTͷ஋͕೔࣌ͱͯ͠ղऍͰ͖ͳ͍ 🐛#VHΫϥε͕ະఆٛ 😇్தͰσʔλϕʔε઀ଓΤϥʔ 👉 👉 👉 ݱঢ়Λ෼ੳ͢Δͱɺؒҧͬͨ࢖ΘΕํΛ͞Ε΍͍͢ͱ͍͏໰୊͕େ͖͍ 👉 ຊ೔ͷߨԋ͸ ͜ΕΒͷ໰୊ʹूத͠·͢

Slide 13

Slide 13 text

ڱٛͷ ๷ޚతϓϩά ϥϛϯά΁ͷޡղ

Slide 14

Slide 14 text

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"'); } ͨͩͻͨ͢ΒೖྗΛνΣοΫ͠Α͏ͱͨ͠Γ # " %

Slide 15

Slide 15 text

ෆਖ਼ͳೖྗ͕͋ͬͯ΋ࣗ෼ͰԿͱ͔͠Α͏ͱͨ͠Γ # " % $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);

Slide 16

Slide 16 text

υΩϡϝϯτͰؒҧ͍΍͢͞Λิ͓͏ͱͨ͠Γ # " % /** * 指定した範囲の日時およびステータスに合致する Bug を検索し、ヒットした全件を Bug オブジェクトの配列として返す。 * * @param array $params 格納した検索条件の連想配列。キー "startAt" に検索範囲の 始点日時をstringで, キー "endAt" に検索範囲の終点日時をstringで, キー "status" にス テータス文字列をstringで指定すること。キー、値それぞれNULLは不可とする。 * @return Bug[] 検索結果を Bug オブジェクトにマッピングして返す。検索結果が0件 のときは空配列を返す。 */ public function findAll($params) {

Slide 17

Slide 17 text

๷ޚతϓϩάϥϛϯάͱ͸ ѱ͍ίʔυʹឺ૑ߣΛ͋ͯΔ ͜ͱͰ͸ͳ͍

Slide 18

Slide 18 text

w ʮ๷ޚతϓϩάϥϛϯάʯͱ͸ɺ 
 ໰୊ൃੜΛࣄલʹ๷͝͏ͱ͍͏ίʔσΟϯάελΠϧ w Մಡੑͷߴ͍ίʔυͱద੾ͳ໋໊نଇ w શͯͷؔ਺ͷ໭Γ஋ΛνΣοΫ w σβΠϯύλʔϯͷ࠾༻ w ཁ͢Δʹɺྑࣝ͋Δ࣮ફͷੵΈॏͶͰ͋Δ w ๷ޚతϓϩάϥϛϯά͸ɺਖ਼͍͠ίʔυ࡞੒ͷͨΊͷن཯Λ ϓϩάϥϚ͕Ұ؏ͯ͠ద༻͢ΔͨΊͷҰछͷίʔσΟϯάඪ४ ๷ޚతϓϩάϥϛϯάͱ͸ྑࣝ͋Δ࣮ફͷੵΈॏͶ IUUQTXXXBNB[PODPKQEQ

Slide 19

Slide 19 text

Agenda ܕએݴ ྻڍܕ ϞσϦϯά ෆมੑͱ౳Ձੑ ׬શੑ ੹຿ͷ഑ஔ 👉

Slide 20

Slide 20 text

ܕΛߜΔ

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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"'); } ໰୊ͱͳ͍ͬͯΔ͜ͷ࿈૝഑ྻΛ ݱঢ়

Slide 23

Slide 23 text

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"'); } ͦΕͧΕܕએݴ͞ΕͨҾ਺ʹ෼ղ͢Δ ܕએݴʹΑͬͯʮग़དྷ͍͍ͯ͜ͱ͚ͩΛग़དྷΔʯΑ͏ʹ

Slide 24

Slide 24 text

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"'); } ๷ޚతνΣοΫ͕΄΅ෆཁʹ

Slide 25

Slide 25 text

૝ఆ͠ͳ͚Ε͹ͳΒͳ͍ঢ়گ͕ݮͬͨ 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ܕͷΧϥϜʹ֨ೲ͍ͯ͠Δͱߟ͍͑ͯͩ͘͞

Slide 26

Slide 26 text

Agenda ܕએݴ ྻڍܕ ϞσϦϯά ෆมੑͱ౳Ձੑ ׬શੑ ੹຿ͷ഑ஔ 👉

Slide 27

Slide 27 text

஋ΛߜΔ

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

ྻڍܕΛ࢖͓͏ 1 ) 1 enum Status: string { case Open = 'OPEN'; case New = 'NEW'; case Fixed = 'FIXED'; } IUUQTXXXQIQOFUNBOVBMKBMBOHVBHFFOVNFSBUJPOTQIQ

Slide 30

Slide 30 text

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); } ݱঢ়

Slide 31

Slide 31 text

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); } Ҿ਺Λจࣈྻ͔Βྻڍܕʹม͑Δ จࣈྻ͔Βྻڍܕʹม͑Δ

Slide 32

Slide 32 text

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); } ૝ఆ͠ͳ͚Ε͹ͳΒͳ͍ঢ়گ͕͞Βʹݮͬͨ

Slide 33

Slide 33 text

Agenda ܕએݴ ྻڍܕ ϞσϦϯά ෆมੑͱ౳Ձੑ ׬શੑ ੹຿ͷ഑ஔ 👉

Slide 34

Slide 34 text

ϞσϦϯάͰ ᐆດ͞ΛݮΒ͢

Slide 35

Slide 35 text

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); } ར༻ଆ Ҿ਺͋ΔͷͰ ͦΕͧΕͷ໾ׂ͕Θ͔Γʹ͍͘

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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); } ؚ·ΕΔؚ·Εͳ͍Λ໊લͰࣔͯ͠΋͍͍͚ΕͲ Ҿ਺ͷ໊લͰࣔͯ͠΋ྑ͍͕ɺ ͜ͷઃܭ͸ৗʹద੾ͩΖ͏͔

Slide 38

Slide 38 text

wྑ͍ΠϯλϑΣʔεͱ͸࣍ͷͭͷ৚݅Λຬͨ ͢ΠϯλϑΣʔε wਖ਼͘͠࢖༻͢Δํ͕ૢ࡞ϛεΛ͢ΔΑΓ؆୯ wޡͬͨ࢖͍ํΛ͢Δ͜ͱ͕ࠔ೉ ΤοηΠਖ਼͍͠࢖͍ํΛ؆୯ʹɺޡͬͨ࢖͍ํΛࠔ೉ʹ IUUQTXXXPSFJMMZDPKQCPPLT ΋ͬͱޡΓ΍͢͞ΛݮΒ͢ઃܭΛ͍ͨ͠

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

͜ΕͰؒҧ͍ʹ͘͘ͳ͕ͬͨɺࠓ౓͸࢖͏ͷ͕΍΍໘౗ʜʜ 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); }

Slide 45

Slide 45 text

wྑ͍ΠϯλϑΣʔεͱ͸࣍ͷͭͷ৚݅Λຬͨ ͢ΠϯλϑΣʔε wਖ਼͘͠࢖༻͢Δํ͕ૢ࡞ϛεΛ͢ΔΑΓ؆୯ wޡͬͨ࢖͍ํΛ͢Δ͜ͱ͕ࠔ೉ ΤοηΠਖ਼͍͠࢖͍ํΛ؆୯ʹɺޡͬͨ࢖͍ํΛࠔ೉ʹ IUUQTXXXPSFJMMZDPKQCPPLT ݱঢ়Ͱ͸ਖ਼͘͠࢖͏ͷ͕΍΍໘౗

Slide 46

Slide 46 text

खݎ͞ͱॻ͖΍͢͞ͷཱ྆ΛࢼΈΑ͏ 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 ); } } ʢ ෬ ઢ ʣ

Slide 47

Slide 47 text

͜ΕͰར༻ଆΛ୹͘ॻ͚ΔΑ͏ʹͳͬͨ 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); } ʢ ෬ ઢ ʣ

Slide 48

Slide 48 text

Agenda ܕએݴ ྻڍܕ ϞσϦϯά ෆมੑͱ౳Ձੑ ׬શੑ ੹຿ͷ഑ஔ 👉

Slide 49

Slide 49 text

ෆมੑ JNNVUBCJMJUZ ঢ়ଶมԽΛഇ͢Δ

Slide 50

Slide 50 text

ผ໊ࢀর໰୊ "MJBTJOH

Slide 51

Slide 51 text

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ΦϒδΣΫτΛ࢖ͬͯΈΔ # " % αϒεΫϦϓγϣϯܖ໿Λ೥ߋ৽͢Δϝιου

Slide 52

Slide 52 text

$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 ผ໊ࢀর໰୊

Slide 53

Slide 53 text

$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 ผ໊ࢀর໰୊

Slide 54

Slide 54 text

$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 ผ໊ࢀর໰୊

Slide 55

Slide 55 text

$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 ผ໊ࢀর໰୊

Slide 56

Slide 56 text

$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ͷαϒεΫϦϓγϣϯܖ໿Λ೥ߋ৽͢Δ

Slide 57

Slide 57 text

$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 ผ໊ࢀর໰୊

Slide 58

Slide 58 text

$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ͷܖ໿ظؒ΋ ߋ৽͞Εͯ͠·͍ͬͯΔ

Slide 59

Slide 59 text

$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 ผ໊ࢀর໰୊

Slide 60

Slide 60 text

Կ͕ݪҼͩͬͨͷ͔

Slide 61

Slide 61 text

Կ͕ݪҼͩͬͨͷ͔ 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); } } # " % ͕͜͜໰୊ ࠶୅ೖͰ͸ͳ͘ͱ΋ഁյతมߋ͕ग़དྷͯ͠·͏

Slide 62

Slide 62 text

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Ϋϥε͕ഁյతมߋΛڐͯ͠͠·͍ͬͯͨ # " %

Slide 63

Slide 63 text

ෆมΦϒδΣΫτΛ͔ͭ͏

Slide 64

Slide 64 text

%BUF5JNFͱ%BUF5JNF*NNVUBCMF IUUQTXXXQIQOFUNBOVBMKBDMBTTEBUFUJNFJNNVUBCMFQIQ

Slide 65

Slide 65 text

/** * @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λάΛ͚͍ͭͯ·͢

Slide 66

Slide 66 text

/** * @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ͷҧ͍Λֶशςετʹ͢Δ

Slide 67

Slide 67 text

ෆมΦϒδΣΫτΛͭ͘Δ

Slide 68

Slide 68 text

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 # " %

Slide 69

Slide 69 text

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Λ࢖͍ͬͯ͘ ʢ ෬ ઢ ʣ

Slide 70

Slide 70 text

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ͩͬͨ # " %

Slide 71

Slide 71 text

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ʹมߋ͞Εͨ

Slide 72

Slide 72 text

ෆมΦϒδΣΫτͰͷ ໰୊ղܾ

Slide 73

Slide 73 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 74

Slide 74 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 75

Slide 75 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 76

Slide 76 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 77

Slide 77 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 78

Slide 78 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 79

Slide 79 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 80

Slide 80 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ 👍

Slide 81

Slide 81 text

$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 ෆมΦϒδΣΫτʹΑͬͯผ໊ࢀর໰୊Λղܾ

Slide 82

Slide 82 text

͕ͩͪΐͬͱ ଴ͬͯ΄͍͠

Slide 83

Slide 83 text

ί ϯ ε τ ϥ Ϋ λ ͸ Ұ ճ ͠ ͔ ݺ ΂ ͳ ͍ ͱ ࡨ ֮ ͠ ͯ ͍ ͨ ʁ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ ㅟ Ұ ମ ͍ ͭ ͔ Β ŠŠŠŠŠŠ

Slide 84

Slide 84 text

/** * @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

Slide 85

Slide 85 text

/** * @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λάΛ͚͍ͭͯ·͢

Slide 86

Slide 86 text

%BUF5JNF*NNVUBCMF͕ JNNVUBCMFͰ͸ͳ͍ͱͨ͠Β ઃܭΛͲ͏͢Δ͔ %BUF5JNF*NNVUBCMFͷίϯετϥΫλϚδοΫϝιουʢA@@DPOTUSVDUAʣΛ໌ࣔతʹݺͿ͜ͱʹΑΔഁյత มߋ͸ɺݱ࣮తʹ͸΄ͱΜͲ৺഑͠ͳ͘ͱ΋ྑ͍ɺߨԋͷͨΊͷۃ୺ͳྫͰ͋Δͱ͸ݴ͑ΔͰ͠ΐ͏ɻ ࠓޙ͸੩తղੳπʔϧʹΑͬͯ͜͏͍ͬͨޡΓΛ๷͙ํ޲ʹਐΜͰ͍͘ͱࢥΘΕ·͢ɻ ͦ͜Ͱɺ͔͜͜ΒຊߨԋͰ͸ɺՄมΦϒδΣΫτΛෆมΦϒδΣΫτͷҰ෦ͱͯ͠ઃܭ͢Δࡍͷ ஫ҙ఺΍ҰൠతͳςΫχοΫΛɺ%BUF5JNF*NNVUBCMFΛՄมͷ΋ͷͱͯ͠ѻ͏͜ͱͰઆ໌͍͖ͯ͠·͢ɻ

Slide 87

Slide 87 text

ίϯετϥΫλ಺Ͱ๷ޚత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) ΍͔ͬͨ

Slide 88

Slide 88 text

QVCMJDQSPQFSUZ͕ %BUF5JNF*NNVUBCMFͩͬͨΒ ͋ͱ͔ΒഁյͰ͖ΔͷͰ͸ʁ

Slide 89

Slide 89 text

/** * @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' ݁ہݺ΂ͯ͠·͏ ෆ҆Λςετʹ຋༁ͯ͠ݕূ͢Δˠෆ҆తத

Slide 90

Slide 90 text

݁࿦ NVUBCMFͳΦϒδΣΫτ͸ SFBEPOMZQSPQFSUZͰ͋ͬͯ΋ QVCMJDQSPQFSUZͱͯ͠࿐ग़͍ͯ͠ΔݶΓ ޙ͔ΒഁյͰ͖ͯ͠·͏

Slide 91

Slide 91 text

ઃܭมߋ๷ޚతίϐʔͱ๷ޚతΞΫηοα 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ʹ ʢ ෬ ઢ ʣ

Slide 92

Slide 92 text

ઃܭมߋ ϓϩύςΟˠϝιου ʹ͋ΘͤͯςετΛௐ੔͠੒ޭΛ֬ೝ /** * @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')); } ΍͔ͬͨ

Slide 93

Slide 93 text

Ͱ΋%BUF5JNF*NNVUBCMF ͸ fi OBMΫϥεͰ͸ͳ͍ɻ ͭ·ΓܧঝͰ͖Δɻ DMPOFͰେৎ෉ͩΖ͏͔ʁ

Slide 94

Slide 94 text

%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ྖҬʹશΠϯελϯε΁ͷࢀরΛอ࣋͢Δ શΠϯελϯεʹഁյతมߋΛߦ͏ अ ѱ

Slide 95

Slide 95 text

ѱҙ͋ΔαϒΫϥεʹΑΔഁյ͕Ͱ͖ͯ͠·ͬͨ /** * @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')); } अ ѱ

Slide 96

Slide 96 text

͜͜·Ͱͷ໌നͳѱҙ͸ແ͘ͱ΋ ϛε͸ى͜Δ ϋϯϩϯͷం౛ͱ΋ݴ͑Δ

Slide 97

Slide 97 text

%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ϝιουͷ࣮૷Λ๨Ε͍ͯΔ ෆ ஫ ҙ

Slide 98

Slide 98 text

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ͳͷͰࢀরΛڞ༗͢Δ ෆ ஫ ҙ

Slide 99

Slide 99 text

DMPOFΛ௒͑ͨ ͞Βʹ๷ޚతͳίϐʔ

Slide 100

Slide 100 text

͡Ό͋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Λੜ੒͢Δ ʢ ෬ ઢ ʣ

Slide 101

Slide 101 text

ࣦഊ͍ͯͨ͠αϒΫϥεؔ܎ͷςετ͕௨ΔΑ͏ʹͳͬͨ /** * @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()); } ΍͔ͬͨ

Slide 102

Slide 102 text

Ήɺ͍΍ɺطଘͷςετ͕͍͔ࣦͭ͘ഊ͢Δͧʜʜʁʁ $ ./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.

Slide 103

Slide 103 text

ࣦഊ͍ͯ͠ΔςετΛҰͭݟͯΈΔ /** * @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 ) 🤔

Slide 104

Slide 104 text

஋ΦϒδΣΫτͱ ౳Ձੑ

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

஋ΦϒδΣΫτͷϦϑΝΫλϦϯάࣦഊΛςετ͕ڭ͑ͯ͘Εͨ 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ͷϓϩύςΟͭʹ෼ղͨ͠Β౳Ձੑ͕੒Γཱͨͳ͘ͳͬͨ

Slide 107

Slide 107 text

IUUQTXXXQIQOFUNBOVBMKBEBUFUJNFJNNVUBCMFDSFBUFGSPNJOUFSGBDFQIQ ղܾࡦDPOWFSTJPOGBDUPSZΛ࢖͓͏ 1 ) 1

Slide 108

Slide 108 text

ղܾࡦ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

Slide 109

Slide 109 text

ͱ͜ΖͰ %BUF5JNF&OEQPJOUͷ ίϯετϥΫλΛ ճҎ্ݺΜͩΒͲ͏ͳΔͷʁ

Slide 110

Slide 110 text

/** * @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ΠσΟΦϜͷมܗͰςετΛॻ͘

Slide 111

Slide 111 text

͢΂ͯʹௐ࿨͕๚Εͨ $ ./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)

Slide 112

Slide 112 text

Agenda ܕએݴ ྻڍܕ ϞσϦϯά ෆมੑͱ౳Ձੑ ׬શੑ ੹຿ͷ഑ஔ 👉

Slide 113

Slide 113 text

ݕࡧ։࢝೔࣌ͱ ݕࡧऴྃ೔࣌ͷ ੔߹ੑΛ୲อ͢Δͷ͸ ୭ͷ੹೚

Slide 114

Slide 114 text

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ͷೖΓޱͰߦ͏ͷ͸ے͕ѱ͍

Slide 115

Slide 115 text

׬શੑ JOUFHSJUZ ଘࡏ͢ΔͳΒ͹ৗʹਖ਼͍͠ ʢෆม৚͕݅ৗʹ੒Γཱͭʣ ΦϒδΣΫτΛ໨ࢦ͢

Slide 116

Slide 116 text

'BJMGBTU ଎΍͔ʹࣦഊͤ͞Δ

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

ࣄલ৚݅ར༻ଆͷ੹೚Ͱ͋Δ৔߹͸ද໌Λ࢖͏ final class DateTimeRange { public function __construct( public readonly DateTimeEndpoint $startAt, public readonly DateTimeEndpoint $endAt, ) { assert($startAt->value() < $endAt->value()); } } JOWBSJBOU ৗʹ੒Γཱͭ΂͖ෆม৚݅ ΛࣜͰॻ͘

Slide 119

Slide 119 text

1)1ͷBTTFSU IUUQQIQOFUNBOVBMKBGVODUJPOBTTFSUQIQ 1 ) 1

Slide 120

Slide 120 text

ද໌Λ࢖Θͳ͍৔߹͸ɺར༻ଆͷ੹೚Ͱ͋Δ͜ͱΛࣔ͢ྫ֎Λ࢖͏ final class DateTimeRange { public function __construct( public readonly DateTimeEndpoint $startAt, public readonly DateTimeEndpoint $endAt, ) { if ($startAt->value() > $endAt->value()) { throw new \InvalidArgumentException('startAt > endAt'); } } } ར༻ଆͷޡΓͰ͋Δࢫͷྫ֎Λൃੜͤ͞Δ

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

ྫ֎ͱද໌ͷ࢖͍෼͚ w ຊདྷͷΤϥʔॲཧʹද໌Λ࢖ͬͯ͸͍͚·ͤΜɻ ද໌͸ى͜Γಘͳ͍͜ͱΛνΣοΫ͢ΔͨΊͷ΋ ͷͰ͢ ୡਓϓϩάϥϚʔୈ൛ w ൃੜ͕༧૝͞ΕΔঢ়گʹ͸ΤϥʔॲཧίʔυΛ࢖ ༻͠ɺൃੜͯ͠͸ͳΒͳ͍ঢ়گʹ͸Ξαʔγϣϯ Λ࢖༻͢Δ $0%&$0.1-&5&ୈ൛ IUUQTXXXPINTIBDPKQCPPL IUUQTXXXBNB[PODPKQEQ9

Slide 123

Slide 123 text

͕ͩͪΐͬͱ·ͬͯ΄͍͠ɻ্୺఺ͱԼ୺఺͕౳͍͠ͱ͖͸ʁ IUUQTKBXJLJQFEJBPSHXJLJ&$#"&@&#&"%"

Slide 124

Slide 124 text

Ұ఺ू߹Λ࣮ݱ͢Δද໌όʔδϣϯ 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(); } } ෆม৚݅ͷࣜΛߋ৽͢Δ

Slide 125

Slide 125 text

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'); } } } } Ұ఺ू߹Λ࣮ݱ͢Δྫ֎όʔδϣϯ ྫ֎όʔδϣϯͱද໌όʔδϣϯͷ ॻ͖ຯ΍ՄಡੑͳͲΛݟൺ΂ͯΈ͍ͯͩ͘͞

Slide 126

Slide 126 text

Agenda ܕએݴ ྻڍܕ ϞσϦϯά ෆมੑͱ౳Ձੑ ׬શੑ ੹຿ͷ഑ஔ 👉

Slide 127

Slide 127 text

ϨΠϠʔͱ੹຿

Slide 128

Slide 128 text

w จࣈྻ͔Β%BUF5JNF*NNVUBCMFʹม׵͢Δͷ͸୭ͷ੹೚ Ͳ͜Ͱ΍Δ w %BUF5JNF*NNVUBCMF͔Βจࣈྻʹม׵͢Δͷ͸୭ͷ੹೚ Ͳ͜Ͱ΍Δ w ʢ͍··Ͱݟͯݟ͵ৼΓΛ͍ͯͨ͠ʣλΠϜκʔϯΛѻ͏ͷ͸୭ͷ੹೚ Ͳ͜Ͱ΍Δ ·ͩ͋Δ੹຿ઃܭͷٙ໰఺Λ·ͱΊ͍ͯ͜͏

Slide 129

Slide 129 text

ෆద੾ͳ৔ॴͰจࣈྻ͔Β %BUF5JNF*NNVUBCMF΁ͷ ม׵Λߦ͍ͬͯΔ͜ͱʹ ؾ͍ͮͯ͠·ͬͨ

Slide 130

Slide 130 text

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΁ͷม׵ʹ͸ ๲େͳ૊Έ߹Θ͕ͤ͋Δ͠ɺ λΠϜκʔϯͷѻ͍΋ෆे෼ʹͳͬͯ͠·͍ͬͯΔɻ จࣈྻҾ਺ʹλΠϜκʔϯͷΦϑηοτදهΛؚΊͳ͍ ͱσϑΥϧτͷλΠϜκʔϯʹͳͬͯ͠·͏ ͭ·Γ͜͜Ͱߦ͏ͷ͸ෆద੾ɻ

Slide 131

Slide 131 text

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ʣدΓʹόΠΞε͕͔͔ͬͯ͠·ͬͨɻ ςετίʔυͷେ෦෼Ͱ͸ಛʹλΠϜκʔϯΛҙࣝͤͣ୹͘ॻ͍͍͖͍͕ͯͨɺ ίϯτϩʔϥͰ͸λΠϜκʔϯΛҙࣝͯ͠ݫີʹѻ͍͍ͨɻ ςετίʔυ΋ͻͱͭͷίϯςΫετɻ ςετ͔ΒઃܭΛۦಈ͢Δͱ͖ʹ͜ͷόΠΞεʹ஫ҙ͢Δඞཁ͕͋Δɻ # " %

Slide 132

Slide 132 text

wྑ͍ΠϯλϑΣʔεͱ͸࣍ͷͭͷ৚݅Λຬͨ ͢ΠϯλϑΣʔε wਖ਼͘͠࢖༻͢Δํ͕ૢ࡞ϛεΛ͢ΔΑΓ؆୯ wޡͬͨ࢖͍ํΛ͢Δ͜ͱ͕ࠔ೉ ΤοηΠਖ਼͍͠࢖͍ํΛ؆୯ʹɺޡͬͨ࢖͍ํΛࠔ೉ʹ IUUQTXXXPSFJMMZDPKQCPPLT ༰қ͞ΛٻΊͨ݁Ռɺ ޡͬͨ࢖͍ํΛ͢Δͷ΋༰қʹͳͬͯ͠·ͬͨ ༰қ͞ΛٻΊͨ݁Ռɺ ޡͬͨ࢖͍ํΛ͢Δͷ΋༰қʹͳͬͯ͠·ͬͨ

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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 # " %

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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Λ߹ྲྀͤ͞Δɻ ͜͏ॻ͚Ε͹े෼ͩͬͨɻ

Slide 139

Slide 139 text

จࣈྻͱ%BUF5JNF*NNVUBCMFͷ૬ޓม׵΍ 
 λΠϜκʔϯͷѻ͍͸ͦΕͧΕ୭͕Ͳ͜Ͱ΍Δ΂͖ʁ

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

ਖ਼౰ੑͱݎ࿚ੑ w ࠷దͳΤϥʔॲཧ͸Τϥʔ͕ൃੜͨ͠ιϑτ΢ΣΞͷछྨʹΑΓҟͳΔ w ਖ਼౰ੑͱ͸ɺෆਖ਼֬ͳ݁ՌΛܾͯ͠ฦ͞ͳ͍͜ͱΛҙຯ͢Δɻෆਖ਼֬ͳ ݁ՌΛฦ͘͢Β͍ͳΒɺԿ΋ฦ͞ͳ͍ํ͕·͠Ͱ͋Δ w ݎ࿚ੑͱ͸ɺιϑτ΢ΣΞͷ࣮ߦΛܧଓͰ͖ΔΑ͏ʹखΛਚ͘͢͜ͱͰ ͋ΔɻͦΕʹΑͬͯෆਖ਼֬ͳ݁Ռ͕΋ͨΒ͞ΕΔ͜ͱ͕͋ͬͯ΋͔·Θ ͳ͍ w ҆શੑʢ΍ਖ਼֬ੑʣΛॏࢹ͢ΔΞϓϦέʔγϣϯͰ͸ɺݎ࿚ੑΑΓ΋ਖ਼ ౰ੑ͕༏ઌ͞ΕΔ܏޲ʹ͋Δ w ίϯγϡʔϚΞϓϦέʔγϣϯͰ͸ɺਖ਼౰ੑΑΓ΋ݎ࿚ੑ͕༏ઌ͞ΕΔ ܏޲ʹ͋Δ IUUQTXXXBNB[PODPKQEQ9

Slide 143

Slide 143 text

ਖ਼౰ੑ ݎ࿚ੑ ৗʹਖ਼֬͞ɺਖ਼͠͞ΛॏΜ͡Δ ֎ք͔Βͷ༷ʑͳೖྗ΍ग़ྗΛɺίϯςΫετ ʹԠͯ͡ద੾ʹϋϯυϦϯά͠ɺม׵͢Δ

Slide 144

Slide 144 text

DateTimeImmutable DateTimeRange DateTimeEndpoint DateTimeZone DateTimeZone DateTimeImmutable string string 8FC͔ΒདྷͨจࣈྻΛόϦσʔγϣϯͭͭ͠ɺʢΞϓϦ έʔγϣϯͰద੾ʹอ࣋ͨ͠ʣλΠϜκʔϯΛ൐ͬͯ %BUF5JNF*NNVUBCMFʹม׵͢Δ %BUF5JNF*NNVUBCMFΛ λΠϜκʔϯΦϑηοτΛ൐ͬͨ λΠϜελϯϓจࣈྻͱͯ͠ %#ʹ౉͢

Slide 145

Slide 145 text

ࠓճͷߨԋͷ ࠷ऴతͳઃܭ

Slide 146

Slide 146 text

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 ۠ؒͷ୺఺Λࣔ͢ɻ ๷ޚతίϐʔɺ๷ޚతΞΫηοα౳Ͱ ঢ়ଶมԽΛ๷͙ɺ ෆมͷ஋ΦϒδΣΫτ

Slide 147

Slide 147 text

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 ۠ؒΛࣔ͢ɻ ίϯετϥΫλͰෆม৚݅Λ੒ཱͤ͞ɺ Ҏ߱ঢ়ଶ͕มԽ͠ͳ͍ɺ ׬શੑΛ൐ͬͨ஋ΦϒδΣΫτ

Slide 148

Slide 148 text

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ͷҾ਺͕ଘࡏ͢Δ࣌఺Ͱطʹਖ਼͍͠ঢ়ଶͳͷͰ ʢ׬શੑʣɺ๷ޚతνΣοΫ͸ෆཁʹͳΔ

Slide 149

Slide 149 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ w ๷ޚతϓϩάϥϛϯάͱ͸ѱ͍ίʔυʹឺ૑ߣΛ͋ͯΔ͜ͱͰ͸ͳ͘ɺྑࣝ͋Δ࣮ફͷੵΈॏͶ w ܕએݴͰ૝ఆ͢΂͖ೖྗͷܕΛߜΓɺ๷ޚతνΣοΫΛେ෯ʹݮΒ͢ w ྻڍܕͰऔΓಘΔ஋ΛߜΓɺ๷ޚతνΣοΫΛେ෯ʹݮΒ͢ w جૅͱͳΔܕΛϞσϦϯά͠ɺᐆດ͞΍ؒҧ͍΍͢͞Λ࡟ݮ͢Δ w جૅͱͳΔܕΛෆมΦϒδΣΫτʹ͠ɺঢ়ଶมԽ΍෭࡞༻ʹىҼ͢ΔόάΛ༧๷͢Δ w جૅͱͳΔܕΛ஋ΦϒδΣΫτʹ͠ɺΠϯελϯεͰ͸ͳ͘஋Ͱ౳ՁੑΛ൑அ͢Δ w ΦϒδΣΫτੜ੒࣌ʹෆม৚݅Λ੒ཱͤ͞ɺ͔ͭͦͷΦϒδΣΫτ͕ෆมΦϒδΣΫτͰ͋Δͳ Β͹ɺੜ੒͞ΕͨΦϒδΣΫτ͸ৗʹਖ਼͍͠ʢ׬શੑʣ w 4JNQMF͞ʢ֓೦ͱͯ͠ͷཁૉͷগͳ͞ʣͱ&BTZ͞ʢख਺ͷগͳ͞ʣΛෆ༻ҙʹࠞͥͳ͍ w ਖ਼౰ੑΛॏΜ͡ΔϨΠϠʔͱݎ࿚ੑΛॏΜ͡ΔϨΠϠʔΛ෼͚Δ w ઃܭͱ͸੹຿ͷ࠷ద഑ஔΛٻΊଓ͚Δ͜ͱɻ୭͕ԿΛ஌͍ͬͯͯԿΛ஌Δ΂͖Ͱͳ͍͔ɺԿΛ΍ Δ΂͖ͰԿΛ΍Δ΂͖Ͱͳ͍͔Λৗʹߟ͑ଓ͚Δ͜ͱ

Slide 150

Slide 150 text

ࢀߟจݙ