Hackで作る堅実な アプリケーションアーキテクチャ / Hack-application-architecture

Hackで作る堅実な アプリケーションアーキテクチャ / Hack-application-architecture

PHP Conference Sendai 2019で利用したスライドです

17d4ef53b432ebf7c566fd6a11345570?s=128

yuuki takezawa

January 22, 2019
Tweet

Transcript

  1. HackͰ࡞Δݎ࣮ͳ ΞϓϦέʔγϣϯΞʔΩςΫνϟ yuuki takezawa PHP Conference Sendai 2019

  2. Profile • ஛ᖒ ༗و / ytake • גࣜձࣾΞΠελΠϧ CTO •

    PHP, Hack, Go, Scala • Apache Hadoop, Apache Spark, Apache Kafka
 • twitter https://twitter.com/ex_takezawa • facebook https://www.facebook.com/yuuki.takezawa • github https://github.com/ytake
  3. None
  4. None
  5. HackΛ࢖ͬͨΒ۩ମతʹԿ͕Ͱ͖Δͷʁ ۩ମతͳ಺༰Λ஌Γ͍ͨʂ

  6. Agenda • PHP Array / Hack Arrays • Shapes In

    Hack • Generics (With Dependency Injection) • Trait And Interface Requirements • Implementing DDD / Generic Repository
  7. ࠓճ͸HHVM 3.27Ҏ߱ʹରԠ͢Δ࿩Ͱ͢ ݹ͍όʔδϣϯΛར༻͍ͯ͠Δ৔߹͸ ΞοϓσʔτΛ

  8. HHVM 3.30(Latest)

  9. Notice • PHPޓ׵͕αϙʔτ͞ΕΔ࠷ޙͷόʔδϣϯ • hhvm.enable_php=false 
 PHPίʔυΛແޮԽ • HackTest΍CLIɺIOपΓͷϥΠϒϥϦ͕ॆ࣮ •

    HHVMͰPHPΛಈ࡞ͤ͞ΔΑΓ΋Hackͱͯ͠
  10. Notice • GitHubͷfacebook/hhvmʹهࡌ͞Ε͍ͯΔ
 Prebuilt Packages on Centos 7.x͸ݹ͍
 • Ubuntuɺ·ͨ͸macOSਪ঑

  11. Hack͔PHP͔ • HHVM্ͰPHPΛಈ͔͢ϝϦοτ͸ແ͠
 Ҏલ͸ߴ଎ʹಈ͘؀ڥɺͱͯ͠ೝ஌͞Ε͍ͯͨ
 PHPࣗମ͕ߴ଎Խ͞ΕɺHHVMࣗମ΋Hackઐ༻ʹ • ଎౓Ͱ͸ͳ͘ɺ
 ੩తܕ෇ݴޠϥΠΫͳػೳ͕ཉ͍͔͠Ͳ͏͔
 ࣮༻తͳΞϓϦέʔγϣϯαʔό͕ཉ͍͔͠Ͳ͏͔
 async΍ɺxhpͳͲͷχʔζ͕͋Δ͔Ͳ͏͔

  12. Hackपลπʔϧͷ࿩ • composer͸ɺhhvm/hh-autoloadͱҰॹʹ࢖͏͜ͱ • Hackͷ։ൃ؀ڥ͸ɺIDEαϙʔτ͕ڧྗ
 vscodeɺnuclide • hhast͕linterͱͯ͠ػೳ
 ίʔυͷมߋͳͲ΋΍ͬͯ͘ΕΔ •

    HHVM/Hackࣗମ͕։ൃαϙʔτػೳଟ͠
  13. Ͱ΋PHPͷϥΠϒϥϦ ࢖͑ͳ͍ΜͰ͠ΐ͏ʁ

  14. Hack ϥΠϒϥϦ • facebook/hack-router Hackઐ༻ϧʔλʔ • facebook/hh-clilib CLIϥΠϒϥϦ • usox/hackttp

    Hack HTTPΠϯλʔϑΣʔε࣮૷ • ytake/hungrr Hack HTTPΠϯλʔϑΣʔε࣮૷ • hhpack/service-locator αʔϏεϩέʔλʔɾDI • nazg/glue αʔϏεϩέʔλʔɾDI • nazg/heredity ϛυϧ΢ΣΞσΟεύονϟ • nazg/hcache Ωϟογϡ
  15. PHP Array / Hack Arrays

  16. PHPͱHack • PHPͰ͸഑ྻ͸arrayͷΈͰදݱ͞ΕΔ
 ഑ྻࣗମʹܕΛࢦఆ͢Δ͜ͱ͸Ͱ͖ͳ͍ • HackͰ഑ྻ͸arrayɺdarrayɺvarrayͷ3छ
 ͲΜͳ഑ྻͰ΋ܕΛࢦఆ͠ͳ͚Ε͹ͳΒͳ͍

  17. <?php declare(strict_types=1); class ArraySet { protected $collect = []; public

    function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): array { return $this->collect; } }
  18. <?php declare(strict_types=1); class ArraySet { protected $collect = []; public

    function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): array { return $this->collect; } } ෆ࣮֬ͳܕͷࠞೖΛ๷͙ͨΊ
 ೖྗΛ੍ݶ
  19. <?php declare(strict_types=1); class ArraySet { protected $collect = []; public

    function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): array { return $this->collect; } } ੍ޚෆՄ
  20. class ExtendArraySet extends ArraySet { public function all(): array {

    $this->collect[1] = 2; return $this->collect; } } ੍ޚෆՄ
  21. None
  22. <?hh // strict class ArraySet { protected darray<string, string> $collect

    = []; public function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): darray<string, string> { return $this->collect; } }
  23. <?hh // strict class ArraySet { protected darray<string, string> $collect

    = []; public function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): darray<string, string> { return $this->collect; } } ෆ࣮֬ͳܕͷࠞೖΛ๷͙ͨΊ
 ೖྗΛ੍ݶ
  24. <?hh // strict class ArraySet { protected darray<string, string> $collect

    = []; public function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): darray<string, string> { return $this->collect; } } ܧঝ࣌Ͱ΋ͦͷ··੍ޚՄ
  25. class ExtendArraySet extends ArraySet { public function all(): darray<string, string>

    { $this->collect[1] = 2; return $this->collect; } } ૠೖෆՄ
  26. class ExtendArraySet extends ArraySet { public function all(): darray<string, string>

    { $this->collect[1] = 2; return $this->collect; } } ໭ΓͷܕΛมߋ͢Δ͜ͱ΋ෆՄ
  27. src/ArraySet.php:19:5,25: Invalid assignment (Typing[4110]) src/ArraySet.php:5:20,25: This is a string src/ArraySet.php:19:20,20:

    It is incompatible with an int src/ArraySet.php:20:12,25: Invalid return type (Typing[4110]) src/ArraySet.php:18:33,38: This is a string src/ArraySet.php:19:20,20: It is incompatible with an int
  28. public function merge(): darray<mixed, mixed> { $a = new ArraySet();

    $a->add('PHP', 'Arrays'); $ar = $a->all(); $ar[1] = 11; return $ar; } औಘޙɺ ҟͳΔܕͷ஋Λࠞೖͤ͞Δ͜ͱ͸Մೳ *໭ΓͷܕΛఆٛ͢Δ͜ͱ
  29. PHP Style Arrays • array
 ௨ৗͷ഑ྻ • varray
 ஋ͷΈͰߏ੒͞ΕΔ഑ྻ •

    darray
 σΟΫγϣφϦͷ഑ྻ
  30. <?hh // strict class Sample { protected varray<string> $varray =

    varray[ 'php', 'hack' ]; protected darray<int, string> $darray = darray[ 'testing' => 'testing', 1 => 'testing' ]; public function failedVArray(): varray<int> { return $this->varray; } public function getDArray(): darray<string, string> { return $this->darray; } }
  31. <?hh // strict class Sample { protected varray<string> $varray =

    varray[ 'php', 'hack' ]; protected darray<int, string> $darray = darray[ 'testing' => 'testing', 1 => 'testing' ]; public function failedVArray(): varray<int> { return $this->varray; } public function getDArray(): darray<string, string> { return $this->darray; } } ܕҧ͍
  32. <?hh // strict class Sample { protected varray<string> $varray =

    varray[ 'php', 'hack' ]; protected darray<int, string> $darray = darray[ 'testing' => 'testing', 1 => 'testing' ]; public function failedVArray(): varray<int> { return $this->varray; } public function getDArray(): darray<string, string> { return $this->darray; } } ໭Γͷܕҧ͍
  33. <?hh // strict class Sample { protected varray<string> $varray =

    varray[ 'php', 'hack' ]; protected darray<int, string> $darray = darray[ 'testing' => 'testing', 1 => 'testing' ]; public function failedVArray(): varray<int> { return $this->varray; } public function getDArray(): darray<string, string> { return $this->darray; } } ໭Γͷܕҧ͍
  34. Hack Arrays • vec
 ७ਮ഑ྻΛѻ͏഑ྻ
 VectorɺImmVectorஔ͖׵͑Մ • dict
 keyͱvalueͰߏ੒͞ΕΔ഑ྻ
 MapɺImmMapஔ͖׵͑Մ

  35. Hack Arrays • keyset
 ॏෳ͠ͳ͍஋Λ࣋ͭ७ਮ഑ྻ
 SetɺImmSetஔ͖׵͑Մ


  36. <?hh // strict class ArraySet { protected dict<string, string> $collect

    = dict[]; public function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): dict<string, string> { return $this->collect; } } dictࢦఆ
  37. Hack Arrays • Hack Arrays͸
 isԋࢉࢠ(HackͷΈͷԋࢉࢠ)Λ༻͍ͯ൑ఆՄೳ
 $dict is dict<_, _>

    , etc • Hack Arrays͸ͦΕͧΕͷ഑ྻʹม׵Մೳ
 (Collection͔ΒHack Arrays΍ͦͷٯ΋)
  38. $vec = vec[1,1,2,3,4,5,6,6,7,8]; var_dump($vec); var_dump(dict($vec)); var_dump(keyset($vec)); $dict = dict[1 =>

    2, 3 => 'testing']; var_dump(vec($dict), $dict is dict<_, _>);
  39. abstract final class dict<+Tk as arraykey, +Tv> implements Indexish<Tk, Tv>,

    XHPChild {} abstract final class keyset<+T as arraykey> implements Indexish<T, T>, XHPChild {} abstract final class vec<+T> implements Indexish<int, T>, XHPChild {}
  40. Hack Collections • PHPϥΠΫͳ഑ྻ΍ɺHack ArraysΑΓ΋
 ݎ͍ίϨΫγϣϯ • ͲΕΛར༻͢Δ͔͸ઃܭɾ࣮૷࣍ୈ
 foreachΑΓ΋
 filter΍mapͱ͍ͬͨॲཧΛ͢ΔͷͰ͋Ε͹Collections


    • cloneޙʹ஋͕มߋ͞ΕΔ(ϝϞϦ)
  41. class CollectionSet { protected Map<string, string> $collect = Map{}; public

    function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): Map<string, string> { return $this->collect; } } Mapࢦఆ
  42. class CollectionSet { protected Map<string, string> $collect = Map{}; public

    function add(string $key, string $value): void { $this->collect[$key] = $value; } public function all(): Map<string, string> { return $this->collect; } } ഑ྻͱಉ͡Α͏ʹૠೖ
  43. ૠೖෆՄ $c = (new CollectionSet())->all(); $c->add(Pair{1, 2});

  44. Notice: Hack Arrays / Collections • HackͰ͸issetɺunset͸ར༻Ͱ͖ͳ͍ • unset͸removeɺfilter(Collection)΍
 hhvm/hslΛར༻͢Δ͜ͱ

    • isset͸contains(Collection)΍array_key_existsɺ
 hhvm/hslར༻
  45. class HackMap { protected Map<string, string> $collect = Map{ 'a'

    => 'b', 'c' => 'd', }; public function filterRemove( string $keyName ): Map<string, string> { return $this->collect ->filterWithKey(($key, $_) ==> $key !== $keyName); } public function remove( string $keyName ): Map<string, string> { return $this->collect->remove($keyName); } } ࢦఆͨ͠ΩʔҎ֎ͷ΋ͷΛ MapͰฦ٫
  46. class HackMap { protected Map<string, string> $collect = Map{ 'a'

    => 'b', 'c' => 'd', }; public function filterRemove( string $keyName ): Map<string, string> { return $this->collect ->filterWithKey(($key, $_) ==> $key !== $keyName); } public function remove( string $keyName ): Map<string, string> { return $this->collect->remove($keyName); } } ࢦఆͨ͠ΩʔΛ࡟আ͠ɺ MapͰฦ٫
  47. Hack Arrays / Collections • array͚ͩͰ͸੍ݶͰ͖ͳ͍΋ͷΛ੍ݶ • PHP͔ΒҠߦ͢Δ৔߹͸·͔ͣ͜͜Β
 Ұ൪ޮՌ͕େ͖͍ •

    arrayૢ࡞ͱ΄΅ಉ͡ͰɺͦΕΒͷPHPؔ਺͕ར༻Մ
  48. Shapes In Hack

  49. Shapes? • ഑ྻͷϑΟʔϧυʹରͯ͠ܕΛఆٛ͢Δ΋ͷ
 • TypecheckerͰϑΟʔϧυΛ൑ఆͰ͖ΔͨΊɺ
 ෆཁͳϑΟʔϧυͷ௥Ճ΍ɺෆ࣮֬ͳ஋ΛഉআͰ͖Δ • ഑ྻͦͷ΋ͷͰ͸ͳ͍ͨΊɺforeach΍ [] ΦϖϨʔλ͸


    ར༻ෆՄ
  50. <?php declare(strict_types=1); class GenerateArrayField { public function getArray(): array {

    return [ 'q' => 'qwerty', 'w' => 'wertyu', ]; } }
  51. <?php declare(strict_types=1); class GenerateArrayField { public function getArray(): array {

    return [ 'q' => 'qwerty', 'w' => 'wertyu', ]; } } ഑ྻͷϑΟʔϧυʹରͯ͠
 ੍ݶෆՄ
  52. None
  53. <?hh // strict type SampleShape = shape('q' => string, 'w'

    => string); class GenerateArrayField { public function getArray(): SampleShape { return shape( 'q' => 'qwerty', 'w' => 'wertyu', ); } }
  54. <?hh // strict type SampleShape = shape('q' => string, 'w'

    => string); class GenerateArrayField { public function getArray(): SampleShape { return shape( 'q' => 'qwerty', 'w' => 'wertyu', ); } } ഑ྻͷϑΟʔϧυʹܕఆٛ
  55. <?hh // strict type SampleShape = shape('q' => string, 'w'

    => string); class GenerateArrayField { public function getArray(): SampleShape { return shape( 'q' => 'qwerty', 'w' => 'wertyu', ); } } shapeΛ࢖ͬͯهड़
  56. public function getArrays(): vec<SampleShape> { return vec[ shape('q' => 'qwerty',

    'w' => 'wertyu',), shape('q' => 'PHP', 'w' => 'Hack',), shape('q' => 'Java', 'w' => 'Scala',), ]; } vec഑ྻʹshapesΛ
 ૊Έ߹ΘͤͯΑΓ࣮֬ͳ഑ྻ΁
  57. namespace Acme\Shapes; type SampleShape = shape( 'a' => int, 'b'

    => int ); type NestShape = shape( 'sample' => SampleShape, 'vec' => vec<string> ); class NestedShape { public function nest(): NestShape { return shape( 'sample' => shape( 'a' => 0, 'b' => 1 ), 'vec' => vec[ 'shapes' ], ); } } Shapesಉ࢜Λ૊Έ߹Θͤͯ
 ఆٛՄ
  58. The field 'z' is not defined in this shape type,

    and this shape type does not allow unknown fields. The field 'z' is set in the shape. ఆٛ͞Ε͍ͯͳ͍ϑΟʔϧυ ར༻࣌ʹΤϥʔ
  59. Shapes? • ଞݴޠͷStructͷΑ͏ʹϑΟʔϧυͷܕએݴ • APIͳͲͷόϦσʔγϣϯ΍ɺ
 ϝοηʔδϒϩʔΧʔ΁ͷ௨৴ͳͲʹ • PHPؔ਺ͷ໭Γ஋ͱ૊Έ߹Θͤͯݫ֨ʹ • PHPʹಉ౳ػೳ͸ແ͠

  60. class HalShape { const type embeddedLinks = shape( 'name' =>

    string, '_links' => shape( 'self' => shape( 'href' => string ) ) ); public function index(): void { $url = [ 'name' => 'hack', '_links' => [ 'self' => [ 'href' =>'uri' ] ] ]; \var_dump($url as this::embeddedLinks); } }
  61. class HalShape { const type embeddedLinks = shape( 'name' =>

    string, '_links' => shape( 'self' => shape( 'href' => string ) ) ); public function index(): void { $url = [ 'name' => 'hack', '_links' => [ 'self' => [ 'href' =>'uri' ] ] ]; \var_dump($url as this::embeddedLinks); } } type constantsΛ࢖ͬͯ
 shapeΛهड़
  62. class HalShape { const type embeddedLinks = shape( 'name' =>

    string, '_links' => shape( 'self' => shape( 'href' => string ) ) ); public function index(): void { $url = [ 'name' => 'hack', '_links' => [ 'self' => [ 'href' =>'uri' ] ] ]; \var_dump($url as this::embeddedLinks); } } ഑ྻͳͲͰ஋Λهड़
  63. class HalShape { const type embeddedLinks = shape( 'name' =>

    string, '_links' => shape( 'self' => shape( 'href' => string ) ) ); public function index(): void { $url = [ 'name' => 'hack', '_links' => [ 'self' => [ 'href' =>'uri' ] ] ]; \var_dump($url as this::embeddedLinks); } } shapeͷఆٛΛݩʹ഑ྻΛݕࠪ
  64. Shapes? • ϑΟʔϧυࣗମ͕nullableͷ৔߹͸ɺϑΟʔϧυ໊ʹ? • ϑΟʔϧυͷ஋͕nullableͷ৔߹͸ɺܕʹ? • ShapeͷϑΟʔϧυΞΫηε࣌ͷσϑΥϧτ஋͸
 Shapes::idx($shape, $fieldName, 'default')

    • ഑ྻͳͲͷม׵͸toArray΍toDictͳͲ
  65. Hack Arrays + Shapes • ͜ΕΒ͸શͯTypechekcerʹΑΔܕνΣοΫͰ൑ఆՄ • ܕʹؔ͢Δίʔυ͕େ͖͘ݮΔ • ܕʹؔ͢ΔίʔυϨϏϡʔ࣌ؒ࡟ݮ

    • جຊతʹ͸strictͰهड़͢Δ͜ͱ
  66. Hack Application Architecture

  67. Dependency Injection

  68. DI Container • PHP༻ͷϥΠϒϥϦΛར༻Ͱ͖Δ͕ɺ
 4ܥ͔Βར༻Ͱ͖ͳ͍ͨΊɺϝϦοτແ͠ • PSR-11Λͦͷ··ར༻͢Δ
 ΋͘͠͸ܕʹΑΔ੍໿Λݫͨ͘͠͠΋ͷΛ։ൃ

  69. DI Container • ίϯςφ͔ΒΠϯελϯεΛ࣮֬ʹऔಘ͢ΔͨΊʹ
 ొ࿥͸Ϋϥε໊ͷΈ
 • mixedΛϦλʔϯ͢Δ৔߹͸ɺ
 ར༻࣌ʹܕνΣοΫΛߦΘͳ͚Ε͹ͳΒͳ͍(Typecheker) • औಘ࣌͸ΦϒδΣΫτͷΈฦ٫ͱ͢Δ


  70. Ϋϥε໊Λදݱ • PHPͰ͸Ϋϥε໊͸stringͰ͔͠ͳ͍ͨΊɺ
 ଘࡏ͢ΔΫϥε͔Ͳ͏͔ɺݫ֨ͳࢦఆ͸Ͱ͖ͳ͍ • HackͰ͸ stringͰ͸ͳ͘ classname<Hoge> Λར༻
 ::class

    ར༻͢Δͱ͜ͷܕʹ • GenericsΛซ༻
  71. $container = new Container(); $container->set( \stdClass::class, ($container) ==> new \stdClass()

    ); $stdClass = $container->getInstance(\stdClass::class); ೖྗΛΫϥε໊ͷΈͱ੍ͯ͠ݶ
  72. $container = new Container(); $container->set( \stdClass::class, ($container) ==> new \stdClass()

    ); $stdClass = $container->getInstance(\stdClass::class); Typechecker͕൑ఆͰ͖ͳ͍৔߹͸ɺ isͳͲΛར༻ͯ͠อূ͢Δඞཁ͕͋Δ
  73. <?hh // strict namespace Acme; use namespace HH\Lib\{C, Str}; class

    Container<T> { private dict< string, (Scope, (function(\Acme\Container<T>): T)) > $map = dict[]; public function set( classname<T> $id, (function(\Acme\Container<T>): T) $callback, Scope $scope = Scope::PROTOTYPE, ): void { $this->map[$id] = tuple($scope, $callback); }
  74. <?hh // strict namespace Acme; use namespace HH\Lib\{C, Str}; class

    Container<T> { private dict< string, (Scope, (function(\Acme\Container<T>): T)) > $map = dict[]; public function set( classname<T> $id, (function(\Acme\Container<T>): T) $callback, Scope $scope = Scope::PROTOTYPE, ): void { $this->map[$id] = tuple($scope, $callback); } Genericsར༻
  75. <?hh // strict namespace Acme; use namespace HH\Lib\{C, Str}; class

    Container<T> { private dict< string, (Scope, (function(\Acme\Container<T>): T)) > $map = dict[]; public function set( classname<T> $id, (function(\Acme\Container<T>): T) $callback, Scope $scope = Scope::PROTOTYPE, ): void { $this->map[$id] = tuple($scope, $callback); } dictΛར༻͠ɺ ίʔϧόοΫͰTΛฦ٫͢Δͱఆٛ
  76. <?hh // strict namespace Acme; use namespace HH\Lib\{C, Str}; class

    Container<T> { private dict< string, (Scope, (function(\Acme\Container<T>): T)) > $map = dict[]; public function set( classname<T> $id, (function(\Acme\Container<T>): T) $callback, Scope $scope = Scope::PROTOTYPE, ): void { $this->map[$id] = tuple($scope, $callback); } classname<T> 
 ͭ·Γଘࡏ͢ΔΫϥε໊ͷΈڐՄͱ͢Δ
  77. <?hh // strict namespace Acme; use namespace HH\Lib\{C, Str}; class

    Container<T> { private dict< string, (Scope, (function(\Acme\Container<T>): T)) > $map = dict[]; public function set( classname<T> $id, (function(\Acme\Container<T>): T) $callback, Scope $scope = Scope::PROTOTYPE, ): void { $this->map[$id] = tuple($scope, $callback); } tupleͰΠϯελϯεੜ੒ํ๏ͱɺ ίʔϧόοΫΛอ࣋
  78. dictૢ࡞ • dictʹొ࿥ࡁΈ͔Ͳ͏͔͸array_key_existsɺ
 ·ͨ͸ϥούʔؔ਺Λར༻ • Πϯελϯεੜ੒ํ๏ʹΑͬͯϝϞԽ͢Δ͔Ͳ͏͔

  79. <<__Rx>> public function getInstance(classname<T> $t): T { return $this->resolve($t); }

    <<__Memoize>> protected function shared(classname<T> $id): T { list($_, $callable) = $this->map[$id]; return $callable($this); } <<__Rx>> public function has(string $id): bool { return C\contains_key($this->map, $id); }
  80. <<__Rx>> public function getInstance(classname<T> $t): T { return $this->resolve($t); }

    <<__Memoize>> protected function shared(classname<T> $id): T { list($_, $callable) = $this->map[$id]; return $callable($this); } <<__Rx>> public function has(string $id): bool { return C\contains_key($this->map, $id); } hhvm/hsl
 array_key_existsϥούʔؔ਺
  81. <<__Rx>> public function getInstance(classname<T> $t): T { return $this->resolve($t); }

    <<__Memoize>> protected function shared(classname<T> $id): T { list($_, $callable) = $this->map[$id]; return $callable($this); } <<__Rx>> public function has(string $id): bool { return C\contains_key($this->map, $id); } ϝϞԽࢦఆ Ҿ਺Λར༻ͯ͠Ωϟογϡ͞ΕΔ γϯάϧτϯ
  82. <<__Rx>> public function getInstance(classname<T> $t): T { return $this->resolve($t); }

    <<__Memoize>> protected function shared(classname<T> $id): T { list($_, $callable) = $this->map[$id]; return $callable($this); } <<__Rx>> public function has(string $id): bool { return C\contains_key($this->map, $id); } reactiveʹ
  83. <<__Rx>> protected function resolve(classname<T> $id): T { if ($this->has($id)) {

    list($scope, $callable) = $this->map[$id]; if ($callable is nonnull) { if ($scope === Scope::SINGLETON) { return $this->shared($id); } return $callable($this); } } throw new Exception\NotFoundException( Str\format('Identifier "%s" is not binding.', $id), ); }
  84. <<__Rx>> protected function resolve(classname<T> $id): T { if ($this->has($id)) {

    list($scope, $callable) = $this->map[$id]; if ($callable is nonnull) { if ($scope === Scope::SINGLETON) { return $this->shared($id); } return $callable($this); } } throw new Exception\NotFoundException( Str\format('Identifier "%s" is not binding.', $id), ); } nullͰͳ͍͔Ͳ͏͔
  85. <<__Rx>> protected function resolve(classname<T> $id): T { if ($this->has($id)) {

    list($scope, $callable) = $this->map[$id]; if ($callable is nonnull) { if ($scope === Scope::SINGLETON) { return $this->shared($id); } return $callable($this); } } throw new Exception\NotFoundException( Str\format('Identifier "%s" is not binding.', $id), ); } γϯάϧτϯͷ৔߹͸ɺ ϝϞԽ
  86. ݎ࣮ͳΠϯελϯεऔಘ • ෆ࣮֬ͳཁૉ͕ೖΓࠐ·ͳ͍Α͏ͳ੍໿ • Ϋϥεऔಘͱొ࿥Ͱଘࡏ͠ͳ͍஋Λར༻ͤ͞ͳ͍ • ΦϒδΣΫτҎ֎Λѻ͏৔߹͸ɺ
 ͦΕ༻ͷొ࿥ɾऔಘϝιουΛ༻ҙ͢Δͷ͕࣮֬ • PHPͰ͸੍ݶͰ͖ͳ͍͜ͱ͕PHPϥΠΫͳίʔυͰՄ

  87. HackͰ࡞ΔΞϓϦέʔγϣϯઃܭͬͯʁ • ϨΠϠʔυ΍ɺΫϦʔϯΞʔΩςΫνϟɺ
 ϔΩαΰφϧΞʔΩςΫνϟͳͲ
 ΦϒδΣΫτࢦ޲Ͱ༻͍ΒΕΔ΋ͷ͸౰વՄೳ • ෆ࣮֬ͳ஋΍ૢ࡞͕ೖΓࠐ·ͳ͍Α͏ʹ͢Δ͜ͱʹ
 ప͢Δ • ͍ΘΏΔ൚༻ੑ΍ݟ΍͢͞ɺΑΓ΋খ͘͞ɺ


    ͦͯ͠TypecheckerʹܕΛڭ͑Δίʔυʹ͢Δ
  88. Trait And Interface Requirements

  89. Traitͷ՝୊ • PHPͷTrait͸੍໿Λ͔͚ΒΕͳ͍ͨΊɺ
 ෳࡶͳॲཧ͕͏·Ε΍͍͢ • ϑϨʔϜϫʔΫͳͲʹ΋͜͏ͨ͠Trait͸ଟ͍

  90. protected function validateEmail(Request $request) { $this->validate( $request, ['email' => 'required|email']

    ); }
  91. Laravelͷྫ: Illuminate\Foundation\Auth\SendsPasswordResetEmails • TraitʹύεϫʔυϦηοτʹؔ͢ΔৼΔ෣͍͕͋Δ • ྫʹڍ͛ͨvalidateϝιου͸ɺ
 ͜ͷτϨΠτʹ͸ͳ͍ϝιουͰɺ
 ଞͷΫϥεɾτϨΠτͷϝιουʹґଘ • Traitࣗମʹґଘؔ܎͕ͳ͍ͨΊɺ


    ಺༰Λ஌Βͳ͍ͱར༻Ͱ͖ͳ͍ྫ
  92. Traitʹ஫ҙ • ܧঝͰ͸ͳ͍ͨΊɺ؆୯ʹ࠶ར༻͕Ͱ͖Δ
 ͕ɺಛఆͷΫϥεͷϓϩύςΟ͕͋Δʁʂ • ϓϩύςΟ͕ଘࡏ͢Δ͔Ͳ͏͔
 νΣοΫ͍ͯ͠Δॲཧ͕͋Δʁʂ • άϩʔόϧΞΫηε͢ΔΑ͏ͳϔϧύʔؔ਺͕͋Δʁʂ

  93. /** * Fire an event when a lockout occurs. *

    * @param \Illuminate\Http\Request $request * @return void */ protected function fireLockoutEvent(Request $request) { event(new Lockout($request)); } /** * Get the rate limiter instance. * * @return \Illuminate\Cache\RateLimiter */ protected function limiter() { return app(RateLimiter::class); }
  94. ෳࡶͳґଘؔ܎ • Laravelͷ app ϔϧύʔؔ਺͸
 αʔϏείϯςφ(DIίϯςφ)ʹΞΫηε͕Ͱ͖Δ΋ͷ • τϨΠτͱͷґଘؔ܎͕͋ΔԿ͔ͷॲཧͷதͰɺ
 Πϯελϯεੜ੒ʹ͍ͭͯͷอূ͕Ͱ͖ͳ͍ͨΊɺ
 DIίϯςφʹάϩʔόϧΞΫηεΛ


    ߦ͍ΠϯελϯεΛऔಘ͢Δɺ
 ͱ͍͏αʔϏεϩέʔλґଘΛӅṭ
  95. HackͷTrait • τϨΠτΛར༻͢ΔΫϥεͷൣғΛࢦఆͰ͖Δ • ͋ΔΫϥεΛܧঝͨ͠΋ͷͷΈར༻ͤ͞Δ͔ɺ
 ͋ΔΠϯλʔϑΣʔεΛ࣮૷ͨ͠΋ͷͷΈ͔ • ෳࡶʹͳΓ΍͍͢ґଘؔ܎Λੜ·ͳ͍Α͏ʹ
 ڧ੍తʹ੍໿Λ͔͚Δ͜ͱ͕Մ

  96. <?hh // strict namespace Acme; class Request { }

  97. <?hh // strict namespace Acme; interface RequestInterface { }

  98. <?hh // strict namespace Acme; trait MessageTrait { require implements

    RequestInterface; private dict<string, vec<string>> $headers = dict[]; }
  99. <?hh // strict namespace Acme; trait MessageTrait { require implements

    RequestInterface; private dict<string, vec<string>> $headers = dict[]; } ࢦఆͨ͠ΠϯλʔϑΣʔεΛ࣮૷ ͨ͠ΫϥεͷΈʹར༻Λ੍ݶ
  100. src/Request.php:6:7,18: Failure to satisfy requirement: Acme\RequestInterface (Typing[4111]) src/MessageTrait.php:6:22,37: Required here

    ΠϯλʔϑΣʔεΛ࣮૷͍ͯ͠ͳ͍ Ϋϥεʹهड़ͨ͠৔߹͸ɺ ΤϥʔͱͳΓ·͢
  101. HackͷTrait • ར༻͢ΔΫϥεͷϝιουΛݺͿ͜ͱ͸Ͱ͖ͳ͍ • method_exists͸ܧঝݩͷ΋ͷ͔ɺ
 ·ͨ͸ಉҰͷτϨΠτ಺ͷΈҎ֎Ͱ͸
 TypececkerͰΤϥʔ

  102. <?hh // strict namespace Acme; class Request implements RequestInterface {

    use MessageTrait; protected function getVersion(): string { return '1.1'; } }
  103. trait MessageTrait { require implements RequestInterface; private dict<string, vec<string>> $headers

    = dict[]; public function castIntVersion(): int { return (int) $this->getVersion(); } }
  104. trait MessageTrait { require implements RequestInterface; private dict<string, vec<string>> $headers

    = dict[]; public function castIntVersion(): int { return (int) $this->getVersion(); } } ϦΫΤετΫϥεͷϝιουΛ ར༻͍ͨ͠
  105. src/MessageTrait.php:10:25,34: Could not find method getVersion in an object of

    type Acme\MessageTrait (Typing[4053]) src/MessageTrait.php:10:18,22: This is why I think it is an object of type Acme\MessageTrait src/MessageTrait.php:5:7,18: Declaration of Acme\MessageTrait is here ΠϯλʔϑΣʔεʹఆٛ͞Ε͍ͯͳ͍ ϝιου͸ίʔϧෆՄ
  106. HackͷTrait • ಛఆͷΫϥε͕ར༻͢Δ
 ΦϒδΣΫτͷϓϩύςΟ͕͋Δ৔߹͸ɺ
 Πϯελϯε͕ਖ਼͍͠ΦϒδΣΫτͰ͋Δ͔Ͳ͏͔Λ
 هड़͠ͳ͚Ε͹ͳΒͳ͍

  107. <?hh // strict namespace Acme; trait MessageTrait { require implements

    RequestInterface; private dict<string, vec<string>> $headers = dict[]; protected \stdClass $class; public function getStdClass(): \stdClass { return $this->class; } }
  108. <?hh // strict namespace Acme; trait MessageTrait { require implements

    RequestInterface; private dict<string, vec<string>> $headers = dict[]; protected \stdClass $class; public function getStdClass(): \stdClass { return $this->class; } } stdClassΛϓϩύςΟͱͯ͠ѻ͏৔߹
  109. <?hh // strict namespace Acme; class Request implements RequestInterface {

    use MessageTrait; protected function getClass(): string { return \strval($this->class); } } Traitʹهड़͞ΕͨϓϩύςΟΛͦͷ·· ར༻͢ΔͱΤϥʔ
  110. <?hh // strict namespace Acme; class Request implements RequestInterface {

    use MessageTrait; public function __construct( protected \stdClass $class ) {} protected function getClass(): string { return \strval($this->class); } } ࣮֬ʹΠϯελϯεΛ౉͢͜ͱ
  111. HackͷTrait • ൚༻ੑΛ࣋ͭTraitͱͨ͠৔߹ɺ
 ͋ΔΫϥεͰ͸࢖Θͳ͍ϓϩύςΟ͕
 ଘࡏ͢Δ৔߹͸Type Error͕ൃੜ͢Δ
 ෆ࣮֬ͳࢦఆ͸Ͱ͖ͳ͍ • ͜ͷϓϩύςΟͲ͜Ͱ࢖ΘΕ͍ͯΔͷʁ
 ͱ͍͏࣮૷͕ੜ·Εʹ͘͘ɺ໾ׂ͕໌֬

  112. Implementing DDD Generic Repository *ଟগΦʔόʔͳྫ

  113. https://github.com/ytake/hack-ddd-repository-sample *αϯϓϧίʔυ͸ͪ͜ΒΛ͝ཡ͍ͩ͘͞

  114. Generic Repository • GenericsͱRepository PatternΛ૊Έ߹Θͤͯɺ
 ୯Ұͷू໿ʹؔ࿈͚ͮΔ • SpecificationύλʔϯΛ૊Έ߹ΘͤΔ͜ͱͰখ͘͞͠
 نଇΛద༻͢Δ •

    ଞݴޠͰ͸Α͘ར༻͞ΕΔύλʔϯ
 HackͰ΋ಉ͜͡ͱ͕Ͱ͖Δʂ
  115. ࢦఆͨ͠೔෇ΑΓ ৽͍͠هࣄ͕ಡΈ͍ͨ

  116. هࣄΦϒδΣΫτ *DDDࣗମͷ࿩Λ͢ΔΘ͚Ͱ͸ແ͍ͷͰৄࡉ͸ׂѪ

  117. <?hh // strict namespace Acme\Domain\Model; abstract class Identifier<T> { public

    function __construct( private T $id ) {} <<__Rx>> public function id(): T { return $this->id; } <<__Rx>> public function equals(Identifier<T> $id): bool { return $this->id === $id->id(); } }
  118. <?hh // strict namespace Acme\Domain\Model; abstract class Identifier<T> { public

    function __construct( private T $id ) {} <<__Rx>> public function id(): T { return $this->id; } <<__Rx>> public function equals(Identifier<T> $id): bool { return $this->id === $id->id(); } } Ҿ਺΍໭ΓͰར༻͢ΔܕΛҰͭʹ
  119. <?hh // strict namespace Acme\Domain\Model; abstract class Identifier<T> { public

    function __construct( private T $id ) {} <<__Rx>> public function id(): T { return $this->id; } <<__Rx>> public function equals(Identifier<T> $id): bool { return $this->id === $id->id(); } } int int int int
  120. <?hh // strict namespace Acme\Domain\Model\Article; use namespace Acme\Domain\Model; final class

    ArticleId<T> extends Model\Identifier<T> { }
  121. <?hh // strict namespace Acme\Domain\Model\Article; use namespace Acme\Domain\Model; final class

    ArticleId<T> extends Model\Identifier<T> { } ྫ͑͹ɺIdͱ͍ͬͯ΋
 intͱ͸ݶΒͳ͍
  122. <?hh // strict namespace Acme\Domain\Model; interface EntityInterface<T> { public function

    getID(): T; }
  123. namespace Acme\Domain\Model\Article\Entity; use type DateTime; use type Acme\Domain\Model\EntityInterface; use type

    Acme\Domain\Model\Article\ArticleId; use type Acme\Domain\Model\Article\Body; class Article<T> implements EntityInterface<T> { const int EXPIRE_EDIT_TIME = 120; public function __construct( private ArticleId<T> $id, private Body $body, private DateTime $createdAt = new DateTime() ) {} <<__Rx>> public function getID(): T { return $this->id->id(); } // লུ }
  124. namespace Acme\Domain\Model\Article\Entity; use type DateTime; use type Acme\Domain\Model\EntityInterface; use type

    Acme\Domain\Model\Article\ArticleId; use type Acme\Domain\Model\Article\Body; class Article<T> implements EntityInterface<T> { const int EXPIRE_EDIT_TIME = 120; public function __construct( private ArticleId<T> $id, private Body $body, private DateTime $createdAt = new DateTime() ) {} <<__Rx>> public function getID(): T { return $this->id->id(); } // লུ } ୯ҰͷܕͷΈ ޡͬͨܕ͕հೖͮ͠Β͍
 (TypecheckerͰݕ஌)
  125. ϦϙδτϦ *DDDࣗମͷ࿩Λ͢ΔΘ͚Ͱ͸ແ͍ͷͰৄࡉ͸ׂѪ

  126. T? • EntityInterfaceΛܕએݴͰར༻͢Δ͜ͱ΋OK • ͨͩ͠EntityInterfaceΛ࣮૷͍ͯ͠Δ΋ͷͰ͋Ε͹
 ͳΜͰ΋Α͍͜ͱʹͳͬͯ͠·͏
 • ΑΓڧ੍͍໿Λ͔͚͍ͨ

  127. interface ArticleRepositoryInterface<TId, T> { public function add(T $entity): void; public

    function remove(T $entity): void; public function findById(TId $id): T; public function latestArticles(DateTime $date): Map<TId, public function query( SpecificationInterface<T> $specification ): Map<TId, T>; public function size(): int; }
  128. interface ArticleRepositoryInterface<TId, T> { public function add(T $entity): void; public

    function remove(T $entity): void; public function findById(TId $id): T; public function latestArticles(DateTime $date): Map<TId, public function query( SpecificationInterface<T> $specification ): Map<TId, T>; public function size(): int; } TId : Identifier͸intಛఆͷܕ͔Ͳ͏͔͸ɻɻʁ T: EntityͰ͸͋Δ͕ɺ͜ͷϦϙδτϦͰѻ͏΋ͷ͸શͯಉ͡ܕ
  129. ϦϙδτϦ ࣮૷ *DDDࣗମͷ࿩Λ͢ΔΘ͚Ͱ͸ແ͍ͷͰৄࡉ͸ׂѪ

  130. abstract class BaseRepository<TId, T as EntityInterface<TId>> implements ArticleRepositoryInterface<TId, T> {

    protected Map<TId, T> $collect = Map{}; public function add(T $article): void { $this->collect->add(Pair{$article->getID(), $article}); } // লུ <<__Rx>> public function findById(TId $id): T { if($this->collect->contains($id)) { return $this->collect->at($id); } throw new \RuntimeException('Not Found.'); } }
  131. abstract class BaseRepository<TId, T as EntityInterface<TId>> implements ArticleRepositoryInterface<TId, T> {

    protected Map<TId, T> $collect = Map{}; public function add(T $article): void { $this->collect->add(Pair{$article->getID(), $article}); } // লུ <<__Rx>> public function findById(TId $id): T { if($this->collect->contains($id)) { return $this->collect->at($id); } throw new \RuntimeException('Not Found.'); } } TId : Identifier͸intಛఆͷܕ͔Ͳ͏͔͸ɻɻʁ T: EntityInterfaceΛ࣮૷ͯ͠Δ͕ɺTIdࢦఆ͞Εͨ΋ͷ
  132. abstract class BaseRepository<TId, T as EntityInterface<TId>> implements ArticleRepositoryInterface<TId, T> {

    protected Map<TId, T> $collect = Map{}; public function add(T $article): void { $this->collect->add(Pair{$article->getID(), $article}); } // লུ <<__Rx>> public function findById(TId $id): T { if($this->collect->contains($id)) { return $this->collect->at($id); } throw new \RuntimeException('Not Found.'); } } ίϨΫγϣϯ͸ɺ<TId, T> ͷϚοϓʹ Ұ؏ੑ͕͋Γɺ͜ΕҎ֎ͷ΋ͷ͸ར༻͞Εͳ͍
  133. <?hh // strict namespace Acme\Infrastructure\Persistence\Map; use type DateTime; use type

    Acme\Domain\Model\Article\Entity\Article; class ArticleRepository extends BaseRepository<int, Article<int>> { <<__Rx>> public function latestArticles( DateTime $date ): Map<int, Article<int>> { return $this->collect->filter( $v ==> $v->createdAt() > $date ); } }
  134. <?hh // strict namespace Acme\Infrastructure\Persistence\Map; use type DateTime; use type

    Acme\Domain\Model\Article\Entity\Article; class ArticleRepository extends BaseRepository<int, Article<int>> { <<__Rx>> public function latestArticles( DateTime $date ): Map<int, Article<int>> { return $this->collect->filter( $v ==> $v->createdAt() > $date ); } } ͲΜͳܕΛར༻͢Δͷ͔ɺ۩৅ΫϥεͰ໌ه
  135. <?hh // strict namespace Acme\Infrastructure\Persistence\Map; use type DateTime; use type

    Acme\Domain\Model\Article\Entity\Article; class ArticleRepository extends BaseRepository<int, Article<int>> { <<__Rx>> public function latestArticles( DateTime $date ): Map<int, Article<int>> { return $this->collect->filter( $v ==> $v->createdAt() > $date ); } } ಛఆͷू໿ݶఆͷॲཧͳͲ͕͋Ε͹ɺ
 ΠϯλʔϑΣʔε௥Ճ΍ɺઐ༻ͷॲཧͷΈهड़
  136. <?hh // strict namespace Acme\Infrastructure\Persistence\Map; use type Acme\Domain\Model\Bookmark\Entity\Bookmark; class BookmarkRepository

    extends BaseRepository<string, Bookmark<string>> { } ྫ͑͹ར༻͢Δܕ͚ͩΛม͑Δ ू໿ͷૢ࡞ͱͯ͠͸͜Ε͚ͩͰ׬݁
 ৄࡉͳॲཧ͸࢓༷ύλʔϯͳͲʹ
  137. ࢓༷ *DDDࣗମͷ࿩Λ͢ΔΘ͚Ͱ͸ແ͍ͷͰৄࡉ͸ׂѪ

  138. ࢓༷ύλʔϯ • ΞϓϦέʔγϣϯαʔϏεͳͲ͔Β
 ϦϙδτϦͱڞʹར༻͞ΕΔ • DBΞΫηε΍Կ͔͠Βͷ֎෦ϦιʔεΛ࢖͏΋ͷ΋
 ࢓༷ͱͯ͠੾Γग़͢

  139. <?hh // strict namespace Acme\Domain\Model\Article\Specification; interface SpecificationInterface<T> { <<__Rx>> public

    function isSatisfiedBy(T $entity): bool; } ࢓༷Λຬͨ͢ͷ͔Ͳ͏͔
  140. ࢓༷ ࣮૷ *DDDࣗମͷ࿩Λ͢ΔΘ͚Ͱ͸ແ͍ͷͰৄࡉ͸ׂѪ

  141. class LatestPostSpecification implements SpecificationInterface<Article<int>> { public function __construct( private DateTime

    $since ) {} <<__Rx>> public function isSatisfiedBy( Article<int> $article ): bool { return $article->createdAt() > $this->since; } } ͜Ε·Ͱͷྫͱಉ༷ʹ۩৅Ϋϥεʹ
 ࣮ࡍʹద༻ͤ͞ΔܕΛهड़
  142. class LatestPostSpecification implements SpecificationInterface<Article<int>> { public function __construct( private DateTime

    $since ) {} <<__Rx>> public function isSatisfiedBy( Article<int> $article ): bool { return $article->createdAt() > $this->since; } } ࢦఆ͞Εͨ೔࣌ΑΓ΋৽͍͠هࣄͳΒ
 ࢓༷͕ຬͨ͞Ε·͢Α
  143. <<__Rx>> public function query( SpecificationInterface<T> $specification ): Map<TId, T> {

    return $this->collect->filter( $v ==> $specification->isSatisfiedBy($v) ); } ࢓༷ɾϦϙδτϦͱ૊Έ߹ΘͤΔ
  144. ΞϓϦέʔγϣϯαʔϏε

  145. use type Vendor\Path\ArticleRepositoryInterface as Repository; use type Vendor\Path\\ArticleSpecificationFactoryInterface as SpecificationFactory;

    final class LatestArticleFeed { public function __construct( private Repository<int, Article<int>> $repository, private SpecificationFactory<Article<int>> $specification ) { } ࢦఆͨ͠ܕΛར༻͢ΔΑ͏ʹద༻͞Εͨ ΠϯλʔϑΣʔεΛ࣮૷ͨ͠΋ͷΛར༻͍ͯͩ͘͠͞
  146. public function execute( FeedRequestTransfer $request ): vec<shape( 'id' => int,

    'content' => string, 'created_at' => \DateTime)> { $result = $this->repository->query( $this->specificationFactory ->createLatestPosts($request->getDateTime() )); if(C\count($result)) { return Vec\map($result, ($v) ==> { return shape( 'id' => $v->getID(), 'content' => $v->body()->content(), 'created_at' => $v->createdAt() ); }); } return vec[]; } ࢓༷Λຬͨ͢هࣄΦϒδΣΫτΛϦϙδτϦ͔Βऔಘ
  147. public function execute( FeedRequestTransfer $request ): vec<shape( 'id' => int,

    'content' => string, 'created_at' => \DateTime)> { $result = $this->repository->query( $this->specificationFactory ->createLatestPosts($request->getDateTime() )); if(C\count($result)) { return Vec\map($result, ($v) ==> { return shape( 'id' => $v->getID(), 'content' => $v->body()->content(), 'created_at' => $v->createdAt() ); }); } return vec[]; } Collectionૢ࡞
 ϦϙδτϦ͔Βऔಘ͞ΕͨΦϒδΣΫτΛҰׅૢ࡞ *͜͜Ͱ͸shapeͷΈͷvec഑ྻʹ
  148. ΫϥΠΞϯτ

  149. ܕએݴ͞ΕͨΠϯλʔϑΣʔεΛຬͨ͢
 ۩৅ΫϥεͷΠϯελϯε $service = new Application\Service\LatestArticleFeed( new Map\ArticleRepository(), new Map\ArticleSpecificationFactory()

    ); $result = $service->execute( new Application\FeedRequestTransfer([ 'datetime' => new DateTime('-4 hours'), ]) );
  150. $service = new Application\Service\LatestArticleFeed( new Map\ArticleRepository(), new Map\ArticleSpecificationFactory() ); $result

    = $service->execute( new Application\FeedRequestTransfer([ 'datetime' => new DateTime('-4 hours'), ]) ); ϑΥʔϜ΍APIͳͲ͔Β஋Λૹ৴
  151. final class FeedRequestTransfer { const type FeedRequest = shape( 'datetime'

    => DateTime ); public function __construct( private array<arraykey, mixed> $request ) {} public function getDateTime(): DateTime { $request = $this->request as this::FeedRequest; return $request['datetime']; } } Ұൠతͳ഑ྻΛड͚औΔ
  152. final class FeedRequestTransfer { const type FeedRequest = shape( 'datetime'

    => DateTime ); public function __construct( private array<arraykey, mixed> $request ) {} public function getDateTime(): DateTime { $request = $this->request as this::FeedRequest; return $request['datetime']; } } ShapeͰ഑ྻʹରͯ͠ ظ଴͢ΔϑΟʔϧυͱܕఆٛ
  153. final class FeedRequestTransfer { const type FeedRequest = shape( 'datetime'

    => DateTime ); public function __construct( private array<arraykey, mixed> $request ) {} public function getDateTime(): DateTime { $request = $this->request as this::FeedRequest; return $request['datetime']; } } ϑΟʔϧυͱܕݕࠪ OKͳΒͦͷ··഑ྻ NGͳΒType Error͕εϩʔ
  154. ·ͱΊ • Hack͸े෼঎༻ΞϓϦέʔγϣϯͰ׆༻ • ݎ͘࡞ΔɺܕΛҙࣝ͢Δ • Կࣄ΋΍Γ͗͢ʹ஫ҙ • ͦͷPHPΞϓϦέʔγϣϯɺ
 HackͰॻ͍ͯΈ·ͤΜ͔ʁ