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

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

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

17d4ef53b432ebf7c566fd6a11345570?s=128

yuuki takezawa

January 22, 2019
Tweet

Transcript

  1. 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
  2. 3.
  3. 4.
  4. 6.

    Agenda • PHP Array / Hack Arrays • Shapes In

    Hack • Generics (With Dependency Injection) • Trait And Interface Requirements • Implementing DDD / Generic Repository
  5. 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 Ωϟογϡ
  6. 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; } }
  7. 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; } } ෆ࣮֬ͳܕͷࠞೖΛ๷͙ͨΊ
 ೖྗΛ੍ݶ
  8. 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; } } ੍ޚෆՄ
  9. 20.

    class ExtendArraySet extends ArraySet { public function all(): array {

    $this->collect[1] = 2; return $this->collect; } } ੍ޚෆՄ
  10. 21.
  11. 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; } }
  12. 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; } } ෆ࣮֬ͳܕͷࠞೖΛ๷͙ͨΊ
 ೖྗΛ੍ݶ
  13. 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; } } ܧঝ࣌Ͱ΋ͦͷ··੍ޚՄ
  14. 25.

    class ExtendArraySet extends ArraySet { public function all(): darray<string, string>

    { $this->collect[1] = 2; return $this->collect; } } ૠೖෆՄ
  15. 26.

    class ExtendArraySet extends ArraySet { public function all(): darray<string, string>

    { $this->collect[1] = 2; return $this->collect; } } ໭ΓͷܕΛมߋ͢Δ͜ͱ΋ෆՄ
  16. 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
  17. 28.

    public function merge(): darray<mixed, mixed> { $a = new ArraySet();

    $a->add('PHP', 'Arrays'); $ar = $a->all(); $ar[1] = 11; return $ar; } औಘޙɺ ҟͳΔܕͷ஋Λࠞೖͤ͞Δ͜ͱ͸Մೳ *໭ΓͷܕΛఆٛ͢Δ͜ͱ
  18. 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; } }
  19. 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; } } ܕҧ͍
  20. 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; } } ໭Γͷܕҧ͍
  21. 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; } } ໭Γͷܕҧ͍
  22. 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ࢦఆ
  23. 37.

    Hack Arrays • Hack Arrays͸
 isԋࢉࢠ(HackͷΈͷԋࢉࢠ)Λ༻͍ͯ൑ఆՄೳ
 $dict is dict<_, _>

    , etc • Hack Arrays͸ͦΕͧΕͷ഑ྻʹม׵Մೳ
 (Collection͔ΒHack Arrays΍ͦͷٯ΋)
  24. 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 {}
  25. 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ࢦఆ
  26. 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; } } ഑ྻͱಉ͡Α͏ʹૠೖ
  27. 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Ͱฦ٫
  28. 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Ͱฦ٫
  29. 51.

    <?php declare(strict_types=1); class GenerateArrayField { public function getArray(): array {

    return [ 'q' => 'qwerty', 'w' => 'wertyu', ]; } } ഑ྻͷϑΟʔϧυʹରͯ͠
 ੍ݶෆՄ
  30. 52.
  31. 53.

    <?hh // strict type SampleShape = shape('q' => string, 'w'

    => string); class GenerateArrayField { public function getArray(): SampleShape { return shape( 'q' => 'qwerty', 'w' => 'wertyu', ); } }
  32. 54.

    <?hh // strict type SampleShape = shape('q' => string, 'w'

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

    <?hh // strict type SampleShape = shape('q' => string, 'w'

    => string); class GenerateArrayField { public function getArray(): SampleShape { return shape( 'q' => 'qwerty', 'w' => 'wertyu', ); } } shapeΛ࢖ͬͯهड़
  34. 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Λ
 ૊Έ߹ΘͤͯΑΓ࣮֬ͳ഑ྻ΁
  35. 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ಉ࢜Λ૊Έ߹Θͤͯ
 ఆٛՄ
  36. 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. ఆٛ͞Ε͍ͯͳ͍ϑΟʔϧυ ར༻࣌ʹΤϥʔ
  37. 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); } }
  38. 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Λهड़
  39. 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); } } ഑ྻͳͲͰ஋Λهड़
  40. 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ͷఆٛΛݩʹ഑ྻΛݕࠪ
  41. 71.

    $container = new Container(); $container->set( \stdClass::class, ($container) ==> new \stdClass()

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

    $container = new Container(); $container->set( \stdClass::class, ($container) ==> new \stdClass()

    ); $stdClass = $container->getInstance(\stdClass::class); Typechecker͕൑ఆͰ͖ͳ͍৔߹͸ɺ isͳͲΛར༻ͯ͠อূ͢Δඞཁ͕͋Δ
  43. 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); }
  44. 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ར༻
  45. 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Λฦ٫͢Δͱఆٛ
  46. 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> 
 ͭ·Γଘࡏ͢ΔΫϥε໊ͷΈڐՄͱ͢Δ
  47. 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ͰΠϯελϯεੜ੒ํ๏ͱɺ ίʔϧόοΫΛอ࣋
  48. 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); }
  49. 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ϥούʔؔ਺
  50. 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); } ϝϞԽࢦఆ Ҿ਺Λར༻ͯ͠Ωϟογϡ͞ΕΔ γϯάϧτϯ
  51. 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ʹ
  52. 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), ); }
  53. 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Ͱͳ͍͔Ͳ͏͔
  54. 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), ); } γϯάϧτϯͷ৔߹͸ɺ ϝϞԽ
  55. 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); }
  56. 98.

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

    RequestInterface; private dict<string, vec<string>> $headers = dict[]; }
  57. 99.

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

    RequestInterface; private dict<string, vec<string>> $headers = dict[]; } ࢦఆͨ͠ΠϯλʔϑΣʔεΛ࣮૷ ͨ͠ΫϥεͷΈʹར༻Λ੍ݶ
  58. 102.

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

    use MessageTrait; protected function getVersion(): string { return '1.1'; } }
  59. 103.

    trait MessageTrait { require implements RequestInterface; private dict<string, vec<string>> $headers

    = dict[]; public function castIntVersion(): int { return (int) $this->getVersion(); } }
  60. 104.

    trait MessageTrait { require implements RequestInterface; private dict<string, vec<string>> $headers

    = dict[]; public function castIntVersion(): int { return (int) $this->getVersion(); } } ϦΫΤετΫϥεͷϝιουΛ ར༻͍ͨ͠
  61. 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 ΠϯλʔϑΣʔεʹఆٛ͞Ε͍ͯͳ͍ ϝιου͸ίʔϧෆՄ
  62. 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; } }
  63. 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ΛϓϩύςΟͱͯ͠ѻ͏৔߹
  64. 109.

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

    use MessageTrait; protected function getClass(): string { return \strval($this->class); } } Traitʹهड़͞ΕͨϓϩύςΟΛͦͷ·· ར༻͢ΔͱΤϥʔ
  65. 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); } } ࣮֬ʹΠϯελϯεΛ౉͢͜ͱ
  66. 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(); } }
  67. 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(); } } Ҿ਺΍໭ΓͰར༻͢ΔܕΛҰͭʹ
  68. 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
  69. 121.

    <?hh // strict namespace Acme\Domain\Model\Article; use namespace Acme\Domain\Model; final class

    ArticleId<T> extends Model\Identifier<T> { } ྫ͑͹ɺIdͱ͍ͬͯ΋
 intͱ͸ݶΒͳ͍
  70. 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(); } // লུ }
  71. 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Ͱݕ஌)
  72. 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; }
  73. 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Ͱ͸͋Δ͕ɺ͜ͷϦϙδτϦͰѻ͏΋ͷ͸શͯಉ͡ܕ
  74. 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.'); } }
  75. 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ࢦఆ͞Εͨ΋ͷ
  76. 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> ͷϚοϓʹ Ұ؏ੑ͕͋Γɺ͜ΕҎ֎ͷ΋ͷ͸ར༻͞Εͳ͍
  77. 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 ); } }
  78. 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 ); } } ͲΜͳܕΛར༻͢Δͷ͔ɺ۩৅ΫϥεͰ໌ه
  79. 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 ); } } ಛఆͷू໿ݶఆͷॲཧͳͲ͕͋Ε͹ɺ
 ΠϯλʔϑΣʔε௥Ճ΍ɺઐ༻ͷॲཧͷΈهड़
  80. 136.

    <?hh // strict namespace Acme\Infrastructure\Persistence\Map; use type Acme\Domain\Model\Bookmark\Entity\Bookmark; class BookmarkRepository

    extends BaseRepository<string, Bookmark<string>> { } ྫ͑͹ར༻͢Δܕ͚ͩΛม͑Δ ू໿ͷૢ࡞ͱͯ͠͸͜Ε͚ͩͰ׬݁
 ৄࡉͳॲཧ͸࢓༷ύλʔϯͳͲʹ
  81. 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; } } ͜Ε·Ͱͷྫͱಉ༷ʹ۩৅Ϋϥεʹ
 ࣮ࡍʹద༻ͤ͞ΔܕΛهड़
  82. 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; } } ࢦఆ͞Εͨ೔࣌ΑΓ΋৽͍͠هࣄͳΒ
 ࢓༷͕ຬͨ͞Ε·͢Α
  83. 143.

    <<__Rx>> public function query( SpecificationInterface<T> $specification ): Map<TId, T> {

    return $this->collect->filter( $v ==> $specification->isSatisfiedBy($v) ); } ࢓༷ɾϦϙδτϦͱ૊Έ߹ΘͤΔ
  84. 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 ) { } ࢦఆͨ͠ܕΛར༻͢ΔΑ͏ʹద༻͞Εͨ ΠϯλʔϑΣʔεΛ࣮૷ͨ͠΋ͷΛར༻͍ͯͩ͘͠͞
  85. 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[]; } ࢓༷Λຬͨ͢هࣄΦϒδΣΫτΛϦϙδτϦ͔Βऔಘ
  86. 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഑ྻʹ
  87. 150.

    $service = new Application\Service\LatestArticleFeed( new Map\ArticleRepository(), new Map\ArticleSpecificationFactory() ); $result

    = $service->execute( new Application\FeedRequestTransfer([ 'datetime' => new DateTime('-4 hours'), ]) ); ϑΥʔϜ΍APIͳͲ͔Β஋Λૹ৴
  88. 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']; } } Ұൠతͳ഑ྻΛड͚औΔ
  89. 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Ͱ഑ྻʹରͯ͠ ظ଴͢ΔϑΟʔϧυͱܕఆٛ
  90. 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͕εϩʔ