Upgrade to Pro — share decks privately, control downloads, hide ads and more …

HHVM/Hack ShapeとTypeAssertで堅実なアプリケーション

HHVM/Hack ShapeとTypeAssertで堅実なアプリケーション

HHVM/Hack勉強会Vol.1 slide
https://istyle.connpass.com/event/80398/

yuuki takezawa

April 23, 2018
Tweet

More Decks by yuuki takezawa

Other Decks in Programming

Transcript

  1. shapeͱTypeAssertͰ
    ݎ࿚ͳΞϓϦέʔγϣϯ
    yuuki takezawa (ytake)

    View full-size slide

  2. εϥΠυͰར༻͍ͯ͠Δαϯϓϧίʔυ
    https://github.com/istyle-inc/example-hack-
    strict-api

    View full-size slide

  3. ෆ࣮֬ͳAPI

    View full-size slide

  4. Ͳ͏΍ͬͯܕΛࢦఆ͢Δ͔
    • Collection (Map, Vector etc)
    • Generics
    • shape
    • tuple

    View full-size slide

  5. [
    ‘message’ => ‘hackstudy’,
    ‘date’ => ‘2018/04/20’
    ];

    View full-size slide

  6. ഑ྻ͸อূ͞ΕΔ͔
    • ͍ΘΏΔPHPͷ഑ྻ͸ɺܕͷ੍໿͕ͳ͍

    ࣗ༝ͳϋογϡςʔϒϧ
    • ഑ྻͰ͋Ε͹serialize -> json response
    • messageʹint͕ೖͬͨΒʁ
    • date͕intͩͬͨΒʁ
    • Ωʔ͕૿͑ͨΓݮͬͨΓ
    • typo?

    View full-size slide

  7. ෆ࣮֬Ͱ͔͠ͳ͍ʂ

    View full-size slide

  8. shape(
    'message' => string,
    'date' => string,
    );

    View full-size slide

  9. class Serialize {
    public function execute(shape(
    'message' => string,
    'date' => string,
    ) $shape): string {
    return serialize($shape);
    }
    }

    View full-size slide

  10. $serialize = new \Serialize();
    $serialize->execute(shape(
    'message' => 'hackstudy',
    'date' => '2018/04/20',
    ));

    View full-size slide

  11. shapeΛ౉ͯ͠΋େৎ෉
    • ഑ྻͱಉ౳ʹར༻͢Δ͜ͱ͕Ͱ͖ΔͷͰɺ

    ઃఆ஋ͳͲΛ౉͢ɺ֎෦Ϧιʔεʹର੍ͯ͠໿Λ͔͚Δ
    ͳͲʹ༻్ʹόονϦ
    • foreach͸shapeʹର࣮ͯ͠ߦͰ͖ͳ͍
    • it doesn't implement Traversable or Container.
    • []͸࢖͑·ͤΜ
    • toArrayͰม׵ͯ͠΋ྑ͍

    View full-size slide

  12. foreach͍ͨ͠ΜͰ͚͢Ͳ

    View full-size slide

  13. shape + Collection

    View full-size slide

  14. type CollectableShape = shape(
    'id' => int,
    'name' => string,
    );

    View full-size slide

  15. public function executeCollection(shape(
    'message' => string,
    'date' => string,
    'tables' => Vector,
    ) $shape): string {
    $v = Shapes::idx($shape, 'tables', Vector{ });
    return serialize(
    array_merge(
    Shapes::toArray($shape),
    ['tables' => $v->toArray()]
    )
    );
    }

    View full-size slide

  16. CollectionΛ૊Έ߹Θͤͯଟछଟ༷ʹ
    • ഑ྻʹม׵ͨ͠Γɺ΋͘͠͸഑ྻΛදݱͨ͠Γ
    • ৭ʑͳ༻్Ͱ৭ʑ੍໿Λ͔͚Δ͜ͱ͕Ͱ͖Δ
    • Ͱ΋shape

    View full-size slide

  17. ֎෦Ϧιʔε͸shapeͰ͸ͳ͍

    View full-size slide

  18. function nullthrows(
    ?T $data,
    string $message = 'unexpected null’
    ): T {
    invariant(
    $data !== null,
    $message,
    );
    return $data;
    }

    View full-size slide

  19. APIͷ໭Γ஋ͷνΣοΫʁ
    • ഑ྻʹม׵ͨ͋͠ͱʹgenerics+invariant͢Δʁ
    • ҰͭҰͭεΩϟϯʁ

    View full-size slide

  20. hhvm/type-assert

    View full-size slide

  21. ෆ࣮֬ͳ஋ΛΞαʔτʂ
    • primitiveܕ͸΋ͪΖΜɺmapͳͲʹ΋
    • is_aͳͲ΋ந৅Խ
    • Ұ൪ͷ໨ۄ͸ matches_type_structure

    View full-size slide

  22. use namespace Facebook\TypeAssert;
    class Foo {
    const type TAPIResponse = shape(
    'id' => int,
    'user' => string,
    'data' => shape(/* ... */),
    );
    public static function getAPIResponse(): self::TAPIResponse {
    $json_string = file_get_contents('https://api.example.com');
    $array = json_decode($json_string, /* associative = */ true);
    return TypeAssert\matches_type_structure(
    type_structure(self::class, 'TAPIResponse'),
    $array,
    );
    }
    }

    View full-size slide

  23. matches_type_structure
    • ReflectionTypeAlias ΫϥεͷgetTypeStructureϝιου͸഑ྻΛ
    ฦͨ͢Ίਏ͍(HackͳΒجຊstirctͰॻ͘͜ͱ͕ଟ͍)

    https://github.com/facebook/hhvm/blob/master/hphp/hack/hhi/
    reflection.hhi#L308
    • ܕ௨ΓͩͬͨΒͦͷ··௨աͯ͠ཉ͍͠ͷͰɺ

    Genericsʹͯ͠΄͍͠ʂ

    type_structure ؔ਺ར༻ͷΈରԠ

    https://github.com/facebook/hhvm/blob/master/hphp/hack/hhi/
    typestructure.hhi#L94

    View full-size slide

  24. matches_type_structure
    • ݕࠪ͢Δܕ͸const stringͰ͋Δඞཁ͕͋ΔͷͰɺ

    ಈతʹੜ੒͸Ͱ͖ͳ͍

    • shapeͰͻͨ͢Βॻ͔͘͠ͳ͍


    View full-size slide

  25. json decodeʹؔͯ͠
    • ίʔϧͨ͠APIͷ໭Γ஋ͳͲʹ΋࢖͏͜ͱ͕Ͱ͖Δ

    • JSONʹؔͯ͠͸json_decodeͰ

    ͍͔ͭ͘Hack޲͚Φϓγϣϯ͕͋Γ

    • JSON_FB_COLLECTIONS => Map

    JSON_FB_HACK_ARRAYS => dict

    https://github.com/facebook/hhvm/blob/master/hphp/
    hack/hhi/stdlib/builtins_json.hhi#L41

    View full-size slide

  26. ΞϓϦέʔγϣϯ͕ฦ٫͢Δ΋ͷʹ
    ద༻
    ݎ࿚ͳϨεϙϯεΛఏڙ͢Δ

    View full-size slide

  27. ΞϓϦέʔγϣϯͰ࣮༻͢Δʹ͸
    • HackͳΒϚΠΫϩαʔϏεʹ͓͚ΔAPI Gatewayͱ͔

    • ฦ٫͢Δ஋Λ୲อͯ͋͛͠ΔΑ͏ʹͨ͠΄͏͕ྑ͍

    • Ϟμϯͳ΋ͷͰ͋Ε͹ϛυϧ΢ΣΞΛ༻ҙͯ͠ɺ

    ࣗΒͷϨεϙϯεΛௐ΂Δ

    View full-size slide

  28. ΦϨΦϨHackϑϨʔϜϫʔΫͰ
    ૊ΈࠐΜͰΈͨ
    https://github.com/ytake/nazg-skeleton

    View full-size slide

  29. ࣮ફ
    • ΞΠελΠϧͰ͸HAL - Hypertext Application Language
    Λ࠾༻͍ͯ͠Δ

    • ߏ଄ΛshapeͰදݱ͢Δ
    • https://techblog.istyle.co.jp/2018/04/18/
    hack%E3%81%A7%E4%BD%9C%E3%82%8B%E5%A0
    %85%E5%AE%9F%E3%81%AAapi/


    View full-size slide

  30. // goal
    {
    "id":1234,
    "name":"ytake",
    "title":"type-assert for api response",
    "_links":{
    "self":{
    “href":"/",
    "type":"application/json"
    }
    },
    "_embedded":{
    "enviroments":[
    {
    "name":"HHVM/Hack",
    "_links":{
    "self":{
    "href":"https://docs.hhvm.com/"
    }
    }
    }
    ]
    }
    }

    View full-size slide

  31. // shapeͰhal+jsonΛදݱ͢Δ
    const type embeddedLinks = shape(
    'name' => string,
    '_links' => shape(
    'self' => shape(
    'href' => string
    )
    )
    );
    const type hateoasStructure = shape(
    'id' => int,
    'name' => string,
    'title' => string,
    '_links' => shape(
    'self' => shape(
    'href' => string,
    'type' => string
    )
    ),
    '_embedded' => shape(
    'enviroments' => array
    )
    );

    View full-size slide

  32. // ར༻ྫɹ͜͜Ͱ͸ImmMap
    new ImmMap([
    'id' => 1234,
    'name' => 'ytake',
    'title' => 'type-assert for api response',
    'embedded' => [
    [
    'name' => 'HHVM/Hack',
    'url' => 'https://docs.hhvm.com/'
    ],
    ]
    ]);

    View full-size slide

  33. ࣮ફ
    • ImmMapΛड͚औΓhal+jsonʹม׵

    • HalରԠϥΠϒϥϦ͸PHPͰ͍͔ͭ͋͘Δ

    Hack͸PHP͔Β཭Ε͍ͯͬͯΔͨΊɺ

    PHPϥΠϒϥϦ࢖͏ཧ༝΋ͳ͍ͷͰࣗ෼Ͱ࡞Δ(ͨ)

    • https://github.com/ytake/hhhal


    View full-size slide

  34. protected Vector $vec = Vector{};
    public function __construct(
    protected ImmMap $resource
    ) {}

    View full-size slide

  35. $map = $this->resource
    ->filterWithKey(($k, $v) ==> $k != 'embedded')
    ->toMap();
    $hal = new HalResource($map);
    $hal->withLink(new Link(
    'self',
    new Vector([
    new LinkResource(
    '/',
    shape('type' => 'application/json'))
    ]),
    ));
    // hal+jsonରԠϥΠϒϥϦ࣮૷ྫ (Hack)

    View full-size slide

  36. $embedded = $this->resource->get('embedded');
    if(is_array($embedded)) {
    foreach($embedded as $row) {
    $embeddedResource = new HalResource();
    foreach($row as $key => $value) {
    if($key === 'url') {
    $embeddedResource->withLink(
    new Link('self', new Vector([new LinkResource($value)]))
    );
    continue;
    }
    $embeddedResource->addResource(strval($key), $value);
    }
    $this->vec->add($embeddedResource);
    }
    }

    View full-size slide

  37. // γϦΞϥΠζ͢Δͱhal+jsonରԠͷjson͕ग़ྗ͞Ε·͢(Hack)
    $hal = $hal->withEmbedded('enviroments', $this->vec);
    $serialize = new Serializer(new JsonSerializer(), $hal);
    return $serialize->toArray();

    View full-size slide

  38. ࣮ફ
    • ϛυϧ΢ΣΞͷ࠷ޙʹ࣮ߦ͢ΔΑ͏ʹ͢Δ

    • PSR-15४ڌͷϛυϧ΢ΣΞͰ͋Ε͹ͳΜͰ΋


    View full-size slide

  39. public function process(
    ServerRequestInterface $request,
    RequestHandlerInterface $handler,
    ): ResponseInterface {
    $response = $handler->handle($request);
    // ϛυϧ΢ΣΞͷޙ൒Ͱಈ͘
    $decode = json_decode($response->getBody()->getContents(), true);
    TypeAssert\matches_type_structure(
    type_structure(self::class, 'hateoasStructure'),
    $decode,
    );
    return $response;
    }

    View full-size slide

  40. use Psr\Log\LoggerInterface;
    use Ytake\HHContainer\Scope;
    use Ytake\HHContainer\ServiceModule;
    use Ytake\HHContainer\FactoryContainer;
    use App\Middleware\ResponseAssertMiddleware;
    final class MiddlewareServiceModule extends ServiceModule {
    public function provide(FactoryContainer $container): void {
    $container->set(
    ResponseAssertMiddleware::class,
    $container ==> new ResponseAssertMiddleware()
    );
    // container΁ͷόΠϯυྫ
    }
    }

    View full-size slide

  41. // route΋shapeͰෆ࣮֬͞Λͳ͘͢ʂ
    \Nazg\Http\HttpMethod::GET => ImmMap {
    '/' => shape(
    'middleware' => ImmVector {
    App\Middleware\ResponseAssertMiddleware::class,
    App\Action\IndexAction::class,
    },
    )
    },

    View full-size slide

  42. ·ͱΊ
    • strictҎ֎͸ͭΒ͍
    • mixed΋ͭΒ͍
    • ϚΠΫϩαʔϏεͰ౤ೖ͢ΔͳΒtype-assertΛ͓קΊ
    • inputͱoutput ৴༻͗ͯ͢͠͸͍͚ͳ͍
    • null͕ʂܕ͕ʂͱݴΘΕͨΒHackͰఏڙͯ͠Έ·͠ΐ͏


    View full-size slide