Hackで作るマイクロフレームワーク

 Hackで作るマイクロフレームワーク

For PHPerKaigi 2018
PHPからHackへ移行するときに気をつけるもの
PHPのライブラリを使ってフレームワークなどを作るにはどうしたらいいか、という内容です

17d4ef53b432ebf7c566fd6a11345570?s=128

yuuki takezawa

March 06, 2018
Tweet

Transcript

  1. HackͰ࡞Δ ϚΠΫϩϑϨʔϜϫʔΫ yuuki takezawa (ytake) PHPerKaigi 2018

  2. Profile • ஛ᖒ ༗و / ytake • גࣜձࣾΞΠελΠϧ • 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. author

  4. None
  5. None
  6. ͸ͳ͢͜ͱ • ։ൃ͢ΔͨΊͷ஌ࣝ 
 .hhconfigɺdecl / strictɺhhi • ڧྗͳϥΠϒϥϦ
 hhvm/hhvm-autoloadɺhhvm/hack-router

    • PHPϥΠϒϥϦͱͷޓ׵Λอͭ։ൃ
  7. <?hh ։ൃ͢ΔͨΊͷ஌ࣝ

  8. <?hh Hackʁ • PHPͷίʔυ͸جຊతʹͦͷ··࣮ߦՄೳ • ݫ֨ͳType Checker • AsyncɺXHPɺGenericsɺCollections •

    3.24 LTS (2018/03/10 ݱࡏ) • HHVM͸ɺPHPͱHack྆ํΛαϙʔτ͢ΔͷͰ͸ͳ ͘ɺHackΛର৅ͱ͍ͯ͘͠ *Forget PHP! Facebook’s HHVM engine switches to Hack instead https://www.infoworld.com/article/3226489/web-development/forget-php-facebooks-hhvm-engine-switches-to-hack-instead.html ࢀর
  9. <?hh Type checker • Ϟʔυ͸3ͭ Partial / Strict / Decl

    
 • σϑΥϧτͰ͸Partial
  10. <?hh Type Check: Partial • PHPͷܕએݴ strictͱಉఔ౓ • ඞཁҎ্ʹܕνΣοΫ͸͠ͳ͍ •

    ओʹPHPͷίʔυΛ Hackͱ࣮ͯ͠ߦ͢Δ
 ίʔυҠ২தͷϑΝΠϧ౳Ͱར༻ • ࢀর౉͠ ར༻Մೳ
  11. <?hh Type Check: Decl • <?hh // decl • ܕνΣοΫ͸͠ͳ͍

    • ଞͷίʔυνΣοΫ࣌ʹ͸ࢀর͞ΕΔ • New Hack code should never be written 
 in decl mode
  12. <?hh Type Check: Strict • <?hh // strict • PHPґଘ͕ͳ͘ɺ100%

    HackͰ࣮૷͢Δ৔߹ʹબ୒ • ݫ֨ͳܕνΣοΫΛߦ͏ϞʔυͷͨΊɺ
 ίʔυϨϏϡʔ࣌ͷܕએݴʹ͍ͭͯͷٞ࿦͸ͳ͠ • ૝ఆ֎ͷܕม׵ͳͲ͸ߦΘΕͳ͍ͨΊɺ
 ϨϏϡʔ͸ΫϥεઃܭɾΞʔΩςΫνϟͳͲʹ
 ϑΥʔΧεͰ͖Δ • Type Checkerʹڭ͑ΔͨΊͷίʔυʹ
  13. <?hh Type Check: Strict • ࠷΋ݫ֨ͳϞʔυͷͨΊɺ
 PHPϥΠϒϥϦΛ࢖͏ίʔυͰ͸ܕએݴΤϥʔൃੜ
 => hhiϑΝΠϧΛ࡞੒(ޙड़) •

    __invokeͷѻ͍͕PHPͱҟͳΔͨΊܕΤϥʔൃੜ
 => strictͰ__invokeͰॲཧ͢Δϛυϧ΢ΣΞ(ඇPSR-15)
 ΛऔΓೖΕΔ৔߹͸஫ҙ
 • HackͰ࣮૷͢ΔͷͰ͋Ε͹StrictΛج४ʹɺ
 Ұ෦͚ͩPartial͕ϕετ
  14. <?hh callableʹؾΛ͚ͭΔ • PHPͰ͸ callableͰܕએݴ͞Ε͍ͯΔ΋ͷ͸Closureɺ
 ·ͨ͸__invokeϝιουΛ࣋ͭΫϥεͰ͋Ε͹ྑ͍
 • HackͰ͸callableͰ͸ͳ͘ɺແ໊ؔ਺ͷҾ਺΋ܕએݴ
 __invoke͸callableѻ͍Ͱ͸ͳ͍
 ->

    PHPͷ __invoke Λ࣮૷͢Δϛυϧ΢ΣΞܥΛ
 ͦͷ··strictͰҠ২͸Ͱ͖ͳ͍
 • inst_meth ͱ͍͏બ୒ࢶ
  15. class Example { public function foo<T>(T $t): T { return

    $t; } } inst_meth(new Example(), ‘foo’); Must be a constant string.
  16. <?hh hhi

  17. <?hh // strict • TypeScript, flowͱಉ༷ͳܕఆٛϑΝΠϧ • ୯ମͰ͸࣮ߦͰ͖ͳ͍ • ܕఆٛϑΝΠϧΛ࡞Δ͜ͱͰɺܕએݴΛߦ͍ɺ


    ݫ֨ϞʔυͰ࣮ߦ͢Δ͜ͱ͕Մೳ
 • ࢖͍͍ͨPHPͷίʔυ΋ิ׬ͯ͠΄͍͠ʂʂ
 ͱ͍͏ํ͸࡞੒ͯ͠packagistͰެ։͍ͯͩ͘͠͞ʂ
  18. packagistʹ͋ΔhhiϑΝΠϧͨͪ • hack-psr/psr7-http-message-hhi • 91carriage/phpunit-hhi • ytake/psr-http-handlers-hhi • ytake/psr-container-hhi •

    libreworks/psr3-log-hhi
 • HackରԠϥΠϒϥϦ։ൃऀ͕࢖͏΋ͷఔ౓͔͋͠Γ· ͤΜ • nikic/fast-route ʹ΋ؚ·Ε͍ͯ·͢
  19. PSR For Hack • https://github.com/facebookexperimental/hack-http- request-response-interfaces • This project aims

    to create standard request and response interfaces for Hack, using PSR-7 as a starting point.

  20. <?hh hhi PSR-11 Container Interface : Example

  21. <?hh // strict namespace Psr\Container; interface ContainerInterface { public function

    get(string $id): mixed; public function has(string $id): bool; } ໭ΓͷܕએݴΛ௥Ճ͚ͨͩ͠
  22. <?hh .hhconfig

  23. <?hh .hhconfig • HackͰ࣮ߦ؀ڥʹઃஔ͢ΔϑΝΠϧ • ࣮͸༷ʑͳઃఆΛهड़Ͱ͖Δ • PHPར༻Λ૝ఆ͠ͳ͍(PHPࠞࡏෆՄ) 
 assume_php

    = false(default: true) • Type Checker Ұ෦ແࢹ
 ignored_paths = [ "vendor/hhvm/hhast/.+" ]
  24. <?hh ڧྗͳϥΠϒϥϦ

  25. <?hh hhvm/hhvm-autoload

  26. <?hh HHVM-Autoload • HHVM؀ڥઐ༻ͷComposer Plugin
 A Composer plugin for autoloading

    classes, enums, functions, typedefs, and constants on HHVM. • vendor/autoload.php Λ vendor/hh_autoload.php ʹ • hh_autoload.jsonΛઃஔ
  27. <?hh HHVM-Autoload • લड़ͨ͠hhiϑΝΠϧͳͲΛvendorσΟϨΫτϦ͔Β
 ݟ͚ͭग़͠ɺTypeCheckerରԠ͚ͩͰ͸ͳ͘ɺ
 Nuclide, VSCͷิ׬ͱͯ͠΋࡞༻
 • HackͰ࣮૷͢Δ৔߹͸ඞͣೖΕ͓͖ͯ·͠ΐ͏

  28. <?hh Composer install • php7ґଘͷ΋ͷ͸iniϑΝΠϧ͔ίϚϯυͰղܾ
 hhvm -d xdebug.enable=0 -d hhvm.jit=0

    -d hhvm.php7.all=1\ 
 -d hhvm.hack.lang.auto_typecheck=0 \
 $(which composer) require vendor/package

  29. Zend Expressive with HHVM/Hack

  30. <?hh Zend Expressive 1.0 • HHVM/HackͰ΋ɺԿ΋ؾʹͤͣར༻Մೳ • Zend ServiceManager +

    HHVM/HackͰ࣮૷ɾՔಇ
 • PHPײ֮ͰखܰʹHHVM/HackΞϓϦέʔγϣϯΛ։ൃ ͢Δ৔߹ʹΦεεϝ
  31. <?hh Zend Expressive >= 2.0 • zend-config-aggregator ͷҰ෦ͷίʔυ͕ಈ࡞ͤͣ
 __invoke, yield͕ಈ͔ͣ

    (Generator<Tk, +Tv, -Ts>)
 • fast-routeͷhhi͕ϝϯς͞Ε͍ͯͳ͍ͨΊType Error
 PHPͱͷ෼཭͕ਐΉͨΊPRͤͣ
 • ಈ͔ͳ͍෦෼Λ
 Hack࣮૷ͷϥΠϒϥϦͱೖΕସ͑Δ͜ͱʹ
  32. <?hh • PHPϥΠϒϥϦͷ࣮૷Λؾʹ͢Δͷ͸ɾɾɾ
 • PHPฒΈʹίʔυิ׬͕Ͱ͖ɺ
 HackͳΒͰ͸ͷ࣮૷Λ͢Δʹ͸HackͰ࡞Δ͔͠ͳ͍

  33. <?hh hhvm/hack-router

  34. <?hh hack-router • PSR-7ରԠͷHackઐ༻RouterϥΠϒϥϦ
 • Ҏલ͸fast-route֦ு͕ͩͬͨআ֎
 • GenericsΛ࢖͍ɺ
 Routerͷ࣮૷Λ஌Βͣʹར༻Ͱ͖ΔϥΠϒϥϦ
 •

    PHPαϙʔτΛ΍ΊΔ͜ͱʹ͋ͨΓɺ
 ͜ͷϥΠϒϥϦϕʔεʹҠߦ
  35. type TResponder = ImmVector<classname<MiddlewareInterface>>; final class Router extends BaseRouter<TResponder> {

    
 <<__Override>> protected function getRoutes( ): ImmMap<HackRouterHttpMethod, ImmMap<string, TResponder>> { $i = $this->routeMap->getIterator(); $map = []; while ($i->valid()) { $map[$this->convertHttpMethod($i->key())] = $i->current(); $i->next(); } return new ImmMap($map); } ࢦఆͨ͠ΫϥεɺIFΛܧঝɺ࣮૷ͨ͠Ϋϥε໊એݴ Routerʹ֨ೲ͠ɺϚον࣌ʹTResponderΛฦ٫
  36. <?hh ytake/hh-container

  37. <?hh PSR-11 • PHPϥΠϒϥϦΛ࢖͍ଓ͚Δҙຯ΋͋·Γͳ͍ͨΊɺ
 Hackઐ༻ͷPimpleϥΠΫͳίϯςφΛ։ൃ
 • PSR-11 hhiΛ༻ҙ͠ɺHackͰ࣮૷
 • HackͳΒͰ͸ͷํ๏ͰSingleton͸

    <<__Memoize>>
 • Πϯελϯεղܾํ๏ఆٛ͸ࣗ༝
  38. public function set( string $id, (function(FactoryContainer): mixed) $callback, Scope $scope

    = Scope::PROTOTYPE, ): void { if (!$this->locked) { $this->bindings->add(Pair {$id, $callback}); $this->scopes->add(Pair {$id, $scope}); } } Closure Type enums Map
  39. use Ytake\HHContainer\FactoryContainer; $container = new FactoryContainer(); $container->set( MessageClass::class, $container ==>

    new MessageClass(‘testing') );
 $container->parameters( MessageClient::class, 'message', $container ==> $container->get(‘message.class’) ); $instance = $container->get(MessageClient::class); Lambda PSR-11࣮૷
  40. final class TestingInvokable { public function __invoke(FactoryContainer $container): int {

    return 1; } } $container = new \Ytake\HHContainer\FactoryContainer(); $container->set(TestingInvokable::class, $container ==> $container->callable( new \Ytake\HHContainer\Invokable( new TestingInvokable(), '__invoke', $container ) ) ); Zend ServiceManager Factory෩ Πϯελϯεੜ੒࣌ʹ࣮ߦ͢Δ
  41. <?hh PSR-11 • Zend ServiceManagerޓ׵Λ໨ࢦͨ͠ϥΠτ൛
 • Zend ExpressiveରԠͨ͠ͱ͜Ζɺ
 ಈతϝιουίʔϧ͸ਪ঑͞Ε͍ͯͳ͍ͨΊɺ
 Type

    Error • /* UNSAFE_EXPR */
  42. ໽հͳmixed PSR-11ͷྫ(strict)

  43. <?hh // strict namespace Psr\Container; interface ContainerInterface { public function

    get(string $id): mixed; public function has(string $id): bool; } PSR-11 get (): mixed
  44. $container = require 'config/services.php'; $app = $container->get('Zend\Expressive\Application'); mixedͳͨΊɺԿ͕ฦ٫͞ΕΔ͔Θ͔Βͳ͍

  45. $config = $container->get('config_array'); if (is_array($config)) { if (array_key_exists('config_array', $config)) {

    return $config['config_array']; } } arrayͰ͋Δ͜ͱΛࣔ͢
  46. $logger = $container->get(‘LoggerInterface'); invariant( $logger instanceof LoggerInterface, "Interface '\Psr\Log\LoggerInterface' is

    not implemented by this class", ); invariant͸HackͰ༻ҙ͞Ε͍ͯΔؔ਺ ୈҰҾ਺ͷ৚͕݅falseͷ৔߹ʹType Error
  47. class TypeAssert { const type Tk = LoggerInterface; public static

    function assert<Tk>(Tk $t): this::Tk { invariant( $logger instanceof LoggerInterface, "Interface '\Psr\Log\LoggerInterface' is not implemented by this class", ); return $t; } }
  48. <?hh mixed • ༷ʑͳ஋͕ฦ٫͞ΕΔͨΊɺmixed͸࢖͏΂͖Ͱ͸ͳ͍
 • PSRʹͩ͜ΘΔඞཁ΋ͳ͍
 (४ڌ͠ͳ͍͜ͱΛݕ౼த)
 • େن໛ͳ։ൃͳͲͰ͸ͳΔ΂͘Strict(ฐࣾஊ)


  49. Forget PHP! Facebook’s HHVM engine switches to Hack instead

  50. <?hh hhvm/type-assert

  51. 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, true); return TypeAssert\matches_type_structure( type_structure(self::class, 'TAPIResponse'), $array, ); } } ShapeͰϑΟʔϧυࢦఆ ͍͋·͍ͳܕฦ٫APIΛݕࠪ
  52. RouterͱContainerɺ ؆୯ͳValidation͕͋Ε͹ ࠷௿ݶͷಈ࡞͕Մೳʹ

  53. Dependency / Container Build Application Routing/Dispatcher Middleware

  54. type TResponder = ImmVector<classname<\Psr\Http\Server\MiddlewareInterface>>; hack routerͷTResponder ϦΫΤετʹରԠ͢Δ΋ͷ͕ొ࿥͞ΕͯೖΕ͹ɺ TResponderͰࢦఆͨ͠ܕΛฦ٫͢Δ

  55. \Nazg\Http\HttpMethod::GET => ImmMap { '/' => ImmVector {App\Action\IndexAction::class}, }, GETʹରԠͤ͞ΔRouteΛهड़

    ‘/’ ʹΞΫηε࣌ʹىಈ͢Δϛυϧ΢ΣΞΛࢦఆ ImmVector<classname<\Psr\Http\Server\MiddlewareInterface>> هड़ͨ͠௨Γʹ࣮ߦ͞ΕΔ
 ActionΫϥε΋ಉΠϯλʔϑΣʔε࣮૷ͷͨΊ۠ผͳ͠
  56. public function run(ServerRequestInterface $serverRequest): void { $container = $this->getContainer(); $this->bootstrap($container);

    $router = $container->get(BaseRouter::class); invariant( $router instanceof BaseRouter, "%s class must extend %s", get_class($router), BaseRouter::class, ); list($middleware, $attributes) = $router->routePsr7Request($serverRequest); hack-routerܧঝΫϥεΛऔΓग़͠ from PSR-7
  57. public function __construct( Traversable<classname<MiddlewareInterface>> $queue, ?Resolvable $resolver = null, )

    { $this->queue = new Vector($queue); $this->resolver = (is_null($resolver)) ? new InstanceResolver() : $resolver; } public function shift(): MiddlewareInterface { $this->queue->reverse(); $current = $this->queue->pop(); return $this->resolver->resolve($current); } public function cancel(int $index): Vector<classname<MiddlewareInterface>> { return $this->queue->removeKey($index); } Collection (Vector) Vector ૢ࡞ ҰͭͣͭऔΓग़࣮ͯ͠ߦ Vector ૢ࡞ ࢦఆͨ͠ΠϯσοΫεͷΞΠςϜΛ࡟আ
  58. https://github.com/ytake/nazg-skeleton https://github.com/nazg-hack/framework

  59. <?hh খ͞ͳϥΠϒϥϦͷू߹ମ΁ • ࠷ۙͷPHPͱಉ༷ʹૄ݁߹ʹ • PSR-11, PSR-7, PSR-15Λར༻ͨͨ͠Ίɺ
 PHPͷϥΠϒϥϦ΋HackͰ࣮ߦՄೳͳ΋ͷ͸OK •

    router͸hack-routerҎ্ͷ΋ͷ͕ͳ͍ͨΊɺ
 hack-routerʹͷΈґଘ • PSR-7͸zend-diactorosΛσϑΥϧτʹ
 (Symfony Component 4Ҏ߱͸Hack ͸αϙʔτ͞Εͳ͍)
  60. <?hh খ͞ͳϥΠϒϥϦͷू߹ମ • Request BodyͳͲͷValidationʹtype-assert
 • mixedΛέΞ͢Δ


  61. <?hh • Hack͔ͩΒԿ͔ಛผͳ΋ͷɺͱ͍͏ͷ͸ແ͍
 *ϥϯλΠϜӠʑ͸আ͘
 • όά͕গͳ͍ݎ࣮ͳΞϓϦέʔγϣϯ΁
 • ίʔυϨϏϡʔෛՙܰݮ • ΈΜͳ͕ࢥ͏΄Ͳ೉͘͠ͳ͍ʂ


    • PHPؾ෼Ͱ͔ΜͨΜͳϚΠΫϩϑϨʔϜϫʔΫ։ൃ
  62. <?hh ͓·͚ • GoͰ΍Βͳ͔ͬͨͷ͔ʁ
 -> Go(Goa, Echo)͸ଟ͘ͷAPIͰಋೖࡁΈ
 େ͖͘ݴޠΛม͑ͣʹɺ
 PHPͷ։ൃऀ͕࢖͑Δ΋ͷΛ૿΍͔ͨͬͨ͠
 •

    segmentation faultͭΒ͘ͳ͍Ͱ͔͢ʁ
 hhvm-dbg + straceͰͳΜͱ͔ͳΓ·͢