Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

഑ྻ͸อূ͞ΕΔ͔ • ͍ΘΏΔPHPͷ഑ྻ͸ɺܕͷ੍໿͕ͳ͍
 ࣗ༝ͳϋογϡςʔϒϧ • ഑ྻͰ͋Ε͹serialize -> json response • messageʹint͕ೖͬͨΒʁ • date͕intͩͬͨΒʁ • Ωʔ͕૿͑ͨΓݮͬͨΓ • typo?

Slide 7

Slide 7 text

ෆ࣮֬Ͱ͔͠ͳ͍ʂ

Slide 8

Slide 8 text

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

string, 'date' => string, ) $shape): string { return serialize($shape); } }

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

foreach͍ͨ͠ΜͰ͚͢Ͳ

Slide 14

Slide 14 text

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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()] ) ); }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

hhvm/type-assert

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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, ); } }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

matches_type_structure • ݕࠪ͢Δܕ͸const stringͰ͋Δඞཁ͕͋ΔͷͰɺ
 ಈతʹੜ੒͸Ͱ͖ͳ͍
 • shapeͰͻͨ͢Βॻ͔͘͠ͳ͍


Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

ΞϓϦέʔγϣϯͰ࣮༻͢Δʹ͸ • HackͳΒϚΠΫϩαʔϏεʹ͓͚ΔAPI Gatewayͱ͔
 • ฦ٫͢Δ஋Λ୲อͯ͋͛͠ΔΑ͏ʹͨ͠΄͏͕ྑ͍
 • Ϟμϯͳ΋ͷͰ͋Ε͹ϛυϧ΢ΣΞΛ༻ҙͯ͠ɺ
 ࣗΒͷϨεϙϯεΛௐ΂Δ

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

࣮ફ • ΞΠελΠϧͰ͸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/


Slide 31

Slide 31 text

// 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/" } } } ] } }

Slide 32

Slide 32 text

// 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 ) );

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

࣮ફ • ImmMapΛड͚औΓhal+jsonʹม׵
 • HalରԠϥΠϒϥϦ͸PHPͰ͍͔ͭ͋͘Δ
 Hack͸PHP͔Β཭Ε͍ͯͬͯΔͨΊɺ
 PHPϥΠϒϥϦ࢖͏ཧ༝΋ͳ͍ͷͰࣗ෼Ͱ࡞Δ(ͨ)
 • https://github.com/ytake/hhhal


Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

$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)

Slide 37

Slide 37 text

$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); } }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

࣮ફ • ϛυϧ΢ΣΞͷ࠷ޙʹ࣮ߦ͢ΔΑ͏ʹ͢Δ
 • PSR-15४ڌͷϛυϧ΢ΣΞͰ͋Ε͹ͳΜͰ΋


Slide 40

Slide 40 text

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; }

Slide 41

Slide 41 text

set( ResponseAssertMiddleware::class, $container ==> new ResponseAssertMiddleware() ); // container΁ͷόΠϯυྫ } }

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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