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

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

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

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

yuuki takezawa

March 06, 2018
Tweet

More Decks by yuuki takezawa

Other Decks in Technology

Transcript

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

    View Slide

  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

    View Slide

  3. author

    View Slide

  4. View Slide

  5. View Slide

  6. ͸ͳ͢͜ͱ
    • ։ൃ͢ΔͨΊͷ஌ࣝ 

    .hhconfigɺdecl / strictɺhhi
    • ڧྗͳϥΠϒϥϦ

    hhvm/hhvm-autoloadɺhhvm/hack-router
    • PHPϥΠϒϥϦͱͷޓ׵Λอͭ։ൃ

    View Slide

  7. ։ൃ͢ΔͨΊͷ஌ࣝ

    View Slide

  8. • 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 ࢀর

    View Slide

  9. • Ϟʔυ͸3ͭ Partial / Strict / Decl 

    • σϑΥϧτͰ͸Partial

    View Slide

  10. • PHPͷܕએݴ strictͱಉఔ౓
    • ඞཁҎ্ʹܕνΣοΫ͸͠ͳ͍
    • ओʹPHPͷίʔυΛ Hackͱ࣮ͯ͠ߦ͢Δ

    ίʔυҠ২தͷϑΝΠϧ౳Ͱར༻
    • ࢀর౉͠ ར༻Մೳ

    View Slide

  11. • • ܕνΣοΫ͸͠ͳ͍
    • ଞͷίʔυνΣοΫ࣌ʹ͸ࢀর͞ΕΔ
    • New Hack code should never be written 

    in decl mode

    View Slide

  12. • • PHPґଘ͕ͳ͘ɺ100% HackͰ࣮૷͢Δ৔߹ʹબ୒
    • ݫ֨ͳܕνΣοΫΛߦ͏ϞʔυͷͨΊɺ

    ίʔυϨϏϡʔ࣌ͷܕએݴʹ͍ͭͯͷٞ࿦͸ͳ͠
    • ૝ఆ֎ͷܕม׵ͳͲ͸ߦΘΕͳ͍ͨΊɺ

    ϨϏϡʔ͸ΫϥεઃܭɾΞʔΩςΫνϟͳͲʹ

    ϑΥʔΧεͰ͖Δ
    • Type Checkerʹڭ͑ΔͨΊͷίʔυʹ

    View Slide

  13. • ࠷΋ݫ֨ͳϞʔυͷͨΊɺ

    PHPϥΠϒϥϦΛ࢖͏ίʔυͰ͸ܕએݴΤϥʔൃੜ

    => hhiϑΝΠϧΛ࡞੒(ޙड़)
    • __invokeͷѻ͍͕PHPͱҟͳΔͨΊܕΤϥʔൃੜ

    => strictͰ__invokeͰॲཧ͢Δϛυϧ΢ΣΞ(ඇPSR-15)

    ΛऔΓೖΕΔ৔߹͸஫ҙ

    • HackͰ࣮૷͢ΔͷͰ͋Ε͹StrictΛج४ʹɺ

    Ұ෦͚ͩPartial͕ϕετ

    View Slide

  14. • PHPͰ͸ callableͰܕએݴ͞Ε͍ͯΔ΋ͷ͸Closureɺ

    ·ͨ͸__invokeϝιουΛ࣋ͭΫϥεͰ͋Ε͹ྑ͍

    • HackͰ͸callableͰ͸ͳ͘ɺແ໊ؔ਺ͷҾ਺΋ܕએݴ

    __invoke͸callableѻ͍Ͱ͸ͳ͍

    -> PHPͷ __invoke Λ࣮૷͢Δϛυϧ΢ΣΞܥΛ

    ͦͷ··strictͰҠ২͸Ͱ͖ͳ͍

    • inst_meth ͱ͍͏બ୒ࢶ

    View Slide

  15. class Example {
    public function foo(T $t): T {
    return $t;
    }
    }
    inst_meth(new Example(), ‘foo’);
    Must be a constant string.

    View Slide

  16. hhi

    View Slide

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

    ݫ֨ϞʔυͰ࣮ߦ͢Δ͜ͱ͕Մೳ

    • ࢖͍͍ͨPHPͷίʔυ΋ิ׬ͯ͠΄͍͠ʂʂ

    ͱ͍͏ํ͸࡞੒ͯ͠packagistͰެ։͍ͯͩ͘͠͞ʂ

    View Slide

  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 ʹ΋ؚ·Ε͍ͯ·͢

    View Slide

  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.


    View Slide

  20. hhi
    PSR-11 Container Interface : Example

    View Slide

  21. namespace Psr\Container;
    interface ContainerInterface
    {
    public function get(string $id): mixed;
    public function has(string $id): bool;
    }
    ໭ΓͷܕએݴΛ௥Ճ͚ͨͩ͠

    View Slide

  22. .hhconfig

    View Slide

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

    assume_php = false(default: true)
    • Type Checker Ұ෦ແࢹ

    ignored_paths = [ "vendor/hhvm/hhast/.+" ]

    View Slide

  24. ڧྗͳϥΠϒϥϦ

    View Slide

  25. hhvm/hhvm-autoload

    View Slide

  26. • 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Λઃஔ

    View Slide

  27. • લड़ͨ͠hhiϑΝΠϧͳͲΛvendorσΟϨΫτϦ͔Β

    ݟ͚ͭग़͠ɺTypeCheckerରԠ͚ͩͰ͸ͳ͘ɺ

    Nuclide, VSCͷิ׬ͱͯ͠΋࡞༻

    • HackͰ࣮૷͢Δ৔߹͸ඞͣೖΕ͓͖ͯ·͠ΐ͏

    View Slide

  28. • 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


    View Slide

  29. Zend Expressive with HHVM/Hack

    View Slide

  30. • HHVM/HackͰ΋ɺԿ΋ؾʹͤͣར༻Մೳ
    • Zend ServiceManager + HHVM/HackͰ࣮૷ɾՔಇ

    • PHPײ֮ͰखܰʹHHVM/HackΞϓϦέʔγϣϯΛ։ൃ
    ͢Δ৔߹ʹΦεεϝ

    View Slide

  31. = 2.0
    • zend-config-aggregator ͷҰ෦ͷίʔυ͕ಈ࡞ͤͣ

    __invoke, yield͕ಈ͔ͣ (Generator)

    • fast-routeͷhhi͕ϝϯς͞Ε͍ͯͳ͍ͨΊType Error

    PHPͱͷ෼཭͕ਐΉͨΊPRͤͣ

    • ಈ͔ͳ͍෦෼Λ

    Hack࣮૷ͷϥΠϒϥϦͱೖΕସ͑Δ͜ͱʹ

    View Slide

  32. • PHPϥΠϒϥϦͷ࣮૷Λؾʹ͢Δͷ͸ɾɾɾ

    • PHPฒΈʹίʔυิ׬͕Ͱ͖ɺ

    HackͳΒͰ͸ͷ࣮૷Λ͢Δʹ͸HackͰ࡞Δ͔͠ͳ͍

    View Slide

  33. hhvm/hack-router

    View Slide

  34. • PSR-7ରԠͷHackઐ༻RouterϥΠϒϥϦ

    • Ҏલ͸fast-route֦ு͕ͩͬͨআ֎

    • GenericsΛ࢖͍ɺ

    Routerͷ࣮૷Λ஌Βͣʹར༻Ͱ͖ΔϥΠϒϥϦ

    • PHPαϙʔτΛ΍ΊΔ͜ͱʹ͋ͨΓɺ

    ͜ͷϥΠϒϥϦϕʔεʹҠߦ

    View Slide

  35. type TResponder = ImmVector>;
    final class Router extends BaseRouter {

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

    View Slide

  36. ytake/hh-container

    View Slide

  37. • PHPϥΠϒϥϦΛ࢖͍ଓ͚Δҙຯ΋͋·Γͳ͍ͨΊɺ

    Hackઐ༻ͷPimpleϥΠΫͳίϯςφΛ։ൃ

    • PSR-11 hhiΛ༻ҙ͠ɺHackͰ࣮૷

    • HackͳΒͰ͸ͷํ๏ͰSingleton͸ <<__Memoize>>

    • Πϯελϯεղܾํ๏ఆٛ͸ࣗ༝

    View Slide

  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

    View Slide

  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࣮૷

    View Slide

  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෩
    Πϯελϯεੜ੒࣌ʹ࣮ߦ͢Δ

    View Slide

  41. • Zend ServiceManagerޓ׵Λ໨ࢦͨ͠ϥΠτ൛

    • Zend ExpressiveରԠͨ͠ͱ͜Ζɺ

    ಈతϝιουίʔϧ͸ਪ঑͞Ε͍ͯͳ͍ͨΊɺ

    Type Error
    • /* UNSAFE_EXPR */

    View Slide

  42. ໽հͳmixed
    PSR-11ͷྫ(strict)

    View Slide

  43. namespace Psr\Container;
    interface ContainerInterface
    {
    public function get(string $id): mixed;
    public function has(string $id): bool;
    }
    PSR-11 get (): mixed

    View Slide

  44. $container = require 'config/services.php';
    $app = $container->get('Zend\Expressive\Application');
    mixedͳͨΊɺԿ͕ฦ٫͞ΕΔ͔Θ͔Βͳ͍

    View Slide

  45. $config = $container->get('config_array');
    if (is_array($config)) {
    if (array_key_exists('config_array', $config)) {
    return $config['config_array'];
    }
    }
    arrayͰ͋Δ͜ͱΛࣔ͢

    View Slide

  46. $logger = $container->get(‘LoggerInterface');
    invariant(
    $logger instanceof LoggerInterface,
    "Interface '\Psr\Log\LoggerInterface' is not implemented by this class",
    );
    invariant͸HackͰ༻ҙ͞Ε͍ͯΔؔ਺
    ୈҰҾ਺ͷ৚͕݅falseͷ৔߹ʹType Error

    View Slide

  47. class TypeAssert {
    const type Tk = LoggerInterface;
    public static function assert(Tk $t): this::Tk {
    invariant(
    $logger instanceof LoggerInterface,
    "Interface '\Psr\Log\LoggerInterface' is not implemented by this class",
    );
    return $t;
    }
    }

    View Slide

  48. • ༷ʑͳ஋͕ฦ٫͞ΕΔͨΊɺmixed͸࢖͏΂͖Ͱ͸ͳ͍

    • PSRʹͩ͜ΘΔඞཁ΋ͳ͍

    (४ڌ͠ͳ͍͜ͱΛݕ౼த)

    • େن໛ͳ։ൃͳͲͰ͸ͳΔ΂͘Strict(ฐࣾஊ)


    View Slide

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

    View Slide

  50. hhvm/type-assert

    View Slide

  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Λݕࠪ

    View Slide

  52. RouterͱContainerɺ
    ؆୯ͳValidation͕͋Ε͹
    ࠷௿ݶͷಈ࡞͕Մೳʹ

    View Slide

  53. Dependency / Container
    Build Application
    Routing/Dispatcher
    Middleware

    View Slide

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

    View Slide

  55. \Nazg\Http\HttpMethod::GET => ImmMap {
    '/' => ImmVector {App\Action\IndexAction::class},
    },
    GETʹରԠͤ͞ΔRouteΛهड़
    ‘/’ ʹΞΫηε࣌ʹىಈ͢Δϛυϧ΢ΣΞΛࢦఆ
    ImmVector>
    هड़ͨ͠௨Γʹ࣮ߦ͞ΕΔ

    ActionΫϥε΋ಉΠϯλʔϑΣʔε࣮૷ͷͨΊ۠ผͳ͠

    View Slide

  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

    View Slide

  57. public function __construct(
    Traversable> $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> {
    return $this->queue->removeKey($index);
    }
    Collection (Vector)
    Vector ૢ࡞
    ҰͭͣͭऔΓग़࣮ͯ͠ߦ
    Vector ૢ࡞
    ࢦఆͨ͠ΠϯσοΫεͷΞΠςϜΛ࡟আ

    View Slide

  58. https://github.com/ytake/nazg-skeleton
    https://github.com/nazg-hack/framework

    View Slide

  59. • ࠷ۙͷPHPͱಉ༷ʹૄ݁߹ʹ
    • PSR-11, PSR-7, PSR-15Λར༻ͨͨ͠Ίɺ

    PHPͷϥΠϒϥϦ΋HackͰ࣮ߦՄೳͳ΋ͷ͸OK
    • router͸hack-routerҎ্ͷ΋ͷ͕ͳ͍ͨΊɺ

    hack-routerʹͷΈґଘ
    • PSR-7͸zend-diactorosΛσϑΥϧτʹ

    (Symfony Component 4Ҏ߱͸Hack ͸αϙʔτ͞Εͳ͍)

    View Slide

  60. • Request BodyͳͲͷValidationʹtype-assert

    • mixedΛέΞ͢Δ


    View Slide

  61. • Hack͔ͩΒԿ͔ಛผͳ΋ͷɺͱ͍͏ͷ͸ແ͍

    *ϥϯλΠϜӠʑ͸আ͘

    • όά͕গͳ͍ݎ࣮ͳΞϓϦέʔγϣϯ΁

    • ίʔυϨϏϡʔෛՙܰݮ
    • ΈΜͳ͕ࢥ͏΄Ͳ೉͘͠ͳ͍ʂ

    • PHPؾ෼Ͱ͔ΜͨΜͳϚΠΫϩϑϨʔϜϫʔΫ։ൃ

    View Slide

  62. • GoͰ΍Βͳ͔ͬͨͷ͔ʁ

    -> Go(Goa, Echo)͸ଟ͘ͷAPIͰಋೖࡁΈ

    େ͖͘ݴޠΛม͑ͣʹɺ

    PHPͷ։ൃऀ͕࢖͑Δ΋ͷΛ૿΍͔ͨͬͨ͠

    • segmentation faultͭΒ͘ͳ͍Ͱ͔͢ʁ

    hhvm-dbg + straceͰͳΜͱ͔ͳΓ·͢

    View Slide