Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Hackで作るマイクロフレームワーク
Search
yuuki takezawa
March 06, 2018
Technology
4
6.3k
Hackで作るマイクロフレームワーク
For PHPerKaigi 2018
PHPからHackへ移行するときに気をつけるもの
PHPのライブラリを使ってフレームワークなどを作るにはどうしたらいいか、という内容です
yuuki takezawa
March 06, 2018
Tweet
Share
More Decks by yuuki takezawa
See All by yuuki takezawa
オブジェクトのおしゃべり大失敗 メッセージングアンチパターン集 / messaging anti-pattern collection
ytake
2
770
DRE/SREのプラクティス融合によるクラウドネイティブなデータ基盤作り / dre_sre
ytake
0
550
技術的負債と向き合う取り組みでよかったもの / positive_efforts_to_tackle_technical_debt
ytake
10
3.5k
アプリケーションエンジニアから強いデータエンジニアへの歩き方 / How to transition and become a Data Engineer from an Application Engineer
ytake
1
340
入門 境界づけられたコンテキスト
ytake
6
3.8k
時間軸とドメインイベントとデータ処理
ytake
1
2k
事業のスケールアウトを支える PHPで作る分散アーキテクチャ
ytake
4
4.5k
Hack HTTP Request and Response Interfaces
ytake
0
6.6k
Hackで作る堅実な アプリケーションアーキテクチャ / Hack-application-architecture
ytake
3
7.6k
Other Decks in Technology
See All in Technology
【TSkaigi】2024/05/11 当日スライド
kimitashoichi
9
2.7k
Zero Data Loss Autonomous Recovery Service サービス概要
oracle4engineer
PRO
0
1.9k
Babylon.jsと色々なものを組み合わせる:ブラウザのAPIやガジェットや2D描画ライブラリなど / Babylon.js 勉強会 vol.3
you
PRO
0
200
TDD - Test Driven Drupal
opdavies
0
3k
LLM開発・活用の舞台裏@2024.04.25
yushin_n
3
1.4k
AWS学習者向けにAzureの解説スライドを作成した話
handy
3
220
Azureの基本的な権限管理の勉強会
yhana
1
2.2k
QAエンジニアが伝えたい品質保証の羅針盤 / Compass for Quality Assurance
mii3king
1
230
個人的、Kubernetes の最新注目機能! (2024年5月版) / TechFeed Experts Night#28 〜 コンテナ技術最前線
pfn
PRO
3
170
競技としてのKaggle、役に立つKaggle
yu4u
7
2.5k
成長をサポートするピープルマネジメントのやり方
sioncojp
9
1.4k
Taking Flight with Tailwind CSS
opdavies
0
4.3k
Featured
See All Featured
The Power of CSS Pseudo Elements
geoffreycrofte
62
5k
What's new in Ruby 2.0
geeforr
337
31k
Web development in the modern age
philhawksworth
203
10k
Raft: Consensus for Rubyists
vanstee
133
6.3k
Imperfection Machines: The Place of Print at Facebook
scottboms
261
12k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
352
28k
Product Roadmaps are Hard
iamctodd
45
9.8k
Git: the NoSQL Database
bkeepers
PRO
423
63k
Designing the Hi-DPI Web
ddemaree
276
33k
Creatively Recalculating Your Daily Design Routine
revolveconf
211
11k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
358
22k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
41
4.4k
Transcript
HackͰ࡞Δ ϚΠΫϩϑϨʔϜϫʔΫ yuuki takezawa (ytake) PHPerKaigi 2018
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
author
None
None
ͳ͢͜ͱ • ։ൃ͢ΔͨΊͷࣝ .hhconfigɺdecl / strictɺhhi • ڧྗͳϥΠϒϥϦ hhvm/hhvm-autoloadɺhhvm/hack-router
• PHPϥΠϒϥϦͱͷޓΛอͭ։ൃ
<?hh ։ൃ͢ΔͨΊͷࣝ
<?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 ࢀর
<?hh Type checker • Ϟʔυ3ͭ Partial / Strict / Decl
• σϑΥϧτͰPartial
<?hh Type Check: Partial • PHPͷܕએݴ strictͱಉఔ • ඞཁҎ্ʹܕνΣοΫ͠ͳ͍ •
ओʹPHPͷίʔυΛ Hackͱ࣮ͯ͠ߦ͢Δ ίʔυҠ২தͷϑΝΠϧͰར༻ • ࢀর͠ ར༻Մೳ
<?hh Type Check: Decl • <?hh // decl • ܕνΣοΫ͠ͳ͍
• ଞͷίʔυνΣοΫ࣌ʹࢀর͞ΕΔ • New Hack code should never be written in decl mode
<?hh Type Check: Strict • <?hh // strict • PHPґଘ͕ͳ͘ɺ100%
HackͰ࣮͢Δ߹ʹબ • ݫ֨ͳܕνΣοΫΛߦ͏ϞʔυͷͨΊɺ ίʔυϨϏϡʔ࣌ͷܕએݴʹ͍ͭͯͷٞͳ͠ • ఆ֎ͷܕมͳͲߦΘΕͳ͍ͨΊɺ ϨϏϡʔΫϥεઃܭɾΞʔΩςΫνϟͳͲʹ ϑΥʔΧεͰ͖Δ • Type Checkerʹڭ͑ΔͨΊͷίʔυʹ
<?hh Type Check: Strict • ࠷ݫ֨ͳϞʔυͷͨΊɺ PHPϥΠϒϥϦΛ͏ίʔυͰܕએݴΤϥʔൃੜ => hhiϑΝΠϧΛ࡞(ޙड़) •
__invokeͷѻ͍͕PHPͱҟͳΔͨΊܕΤϥʔൃੜ => strictͰ__invokeͰॲཧ͢ΔϛυϧΣΞ(ඇPSR-15) ΛऔΓೖΕΔ߹ҙ • HackͰ࣮͢ΔͷͰ͋ΕStrictΛج४ʹɺ Ұ෦͚ͩPartial͕ϕετ
<?hh callableʹؾΛ͚ͭΔ • PHPͰ callableͰܕએݴ͞Ε͍ͯΔͷClosureɺ ·ͨ__invokeϝιουΛ࣋ͭΫϥεͰ͋Εྑ͍ • HackͰcallableͰͳ͘ɺແ໊ؔͷҾܕએݴ __invokecallableѻ͍Ͱͳ͍ ->
PHPͷ __invoke Λ࣮͢ΔϛυϧΣΞܥΛ ͦͷ··strictͰҠ২Ͱ͖ͳ͍ • inst_meth ͱ͍͏બࢶ
class Example { public function foo<T>(T $t): T { return
$t; } } inst_meth(new Example(), ‘foo’); Must be a constant string.
<?hh hhi
<?hh // strict • TypeScript, flowͱಉ༷ͳܕఆٛϑΝΠϧ • ୯ମͰ࣮ߦͰ͖ͳ͍ • ܕఆٛϑΝΠϧΛ࡞Δ͜ͱͰɺܕએݴΛߦ͍ɺ
ݫ֨ϞʔυͰ࣮ߦ͢Δ͜ͱ͕Մೳ • ͍͍ͨPHPͷίʔυิͯ͠΄͍͠ʂʂ ͱ͍͏ํ࡞ͯ͠packagistͰެ։͍ͯͩ͘͠͞ʂ
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 ʹؚ·Ε͍ͯ·͢
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.
<?hh hhi PSR-11 Container Interface : Example
<?hh // strict namespace Psr\Container; interface ContainerInterface { public function
get(string $id): mixed; public function has(string $id): bool; } ΓͷܕએݴΛՃ͚ͨͩ͠
<?hh .hhconfig
<?hh .hhconfig • HackͰ࣮ߦڥʹઃஔ͢ΔϑΝΠϧ • ࣮༷ʑͳઃఆΛهड़Ͱ͖Δ • PHPར༻Λఆ͠ͳ͍(PHPࠞࡏෆՄ) assume_php
= false(default: true) • Type Checker Ұ෦ແࢹ ignored_paths = [ "vendor/hhvm/hhast/.+" ]
<?hh ڧྗͳϥΠϒϥϦ
<?hh hhvm/hhvm-autoload
<?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Λઃஔ
<?hh HHVM-Autoload • લड़ͨ͠hhiϑΝΠϧͳͲΛvendorσΟϨΫτϦ͔Β ݟ͚ͭग़͠ɺTypeCheckerରԠ͚ͩͰͳ͘ɺ Nuclide, VSCͷิͱͯ͠࡞༻ • HackͰ࣮͢Δ߹ඞͣೖΕ͓͖ͯ·͠ΐ͏
<?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
Zend Expressive with HHVM/Hack
<?hh Zend Expressive 1.0 • HHVM/HackͰɺԿؾʹͤͣར༻Մೳ • Zend ServiceManager +
HHVM/HackͰ࣮ɾՔಇ • PHPײ֮ͰखܰʹHHVM/HackΞϓϦέʔγϣϯΛ։ൃ ͢Δ߹ʹΦεεϝ
<?hh Zend Expressive >= 2.0 • zend-config-aggregator ͷҰ෦ͷίʔυ͕ಈ࡞ͤͣ __invoke, yield͕ಈ͔ͣ
(Generator<Tk, +Tv, -Ts>) • fast-routeͷhhi͕ϝϯς͞Ε͍ͯͳ͍ͨΊType Error PHPͱͷ͕ਐΉͨΊPRͤͣ • ಈ͔ͳ͍෦Λ Hack࣮ͷϥΠϒϥϦͱೖΕସ͑Δ͜ͱʹ
<?hh • PHPϥΠϒϥϦͷ࣮Λؾʹ͢Δͷɾɾɾ • PHPฒΈʹίʔυิ͕Ͱ͖ɺ HackͳΒͰͷ࣮Λ͢ΔʹHackͰ࡞Δ͔͠ͳ͍
<?hh hhvm/hack-router
<?hh hack-router • PSR-7ରԠͷHackઐ༻RouterϥΠϒϥϦ • Ҏલfast-route֦ு͕ͩͬͨআ֎ • GenericsΛ͍ɺ Routerͷ࣮ΛΒͣʹར༻Ͱ͖ΔϥΠϒϥϦ •
PHPαϙʔτΛΊΔ͜ͱʹ͋ͨΓɺ ͜ͷϥΠϒϥϦϕʔεʹҠߦ
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Λฦ٫
<?hh ytake/hh-container
<?hh PSR-11 • PHPϥΠϒϥϦΛ͍ଓ͚Δҙຯ͋·Γͳ͍ͨΊɺ Hackઐ༻ͷPimpleϥΠΫͳίϯςφΛ։ൃ • PSR-11 hhiΛ༻ҙ͠ɺHackͰ࣮ • HackͳΒͰͷํ๏ͰSingleton
<<__Memoize>> • Πϯελϯεղܾํ๏ఆٛࣗ༝
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
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࣮
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෩ Πϯελϯεੜ࣌ʹ࣮ߦ͢Δ
<?hh PSR-11 • Zend ServiceManagerޓΛࢦͨ͠ϥΠτ൛ • Zend ExpressiveରԠͨ͠ͱ͜Ζɺ ಈతϝιουίʔϧਪ͞Ε͍ͯͳ͍ͨΊɺ Type
Error • /* UNSAFE_EXPR */
հͳmixed PSR-11ͷྫ(strict)
<?hh // strict namespace Psr\Container; interface ContainerInterface { public function
get(string $id): mixed; public function has(string $id): bool; } PSR-11 get (): mixed
$container = require 'config/services.php'; $app = $container->get('Zend\Expressive\Application'); mixedͳͨΊɺԿ͕ฦ٫͞ΕΔ͔Θ͔Βͳ͍
$config = $container->get('config_array'); if (is_array($config)) { if (array_key_exists('config_array', $config)) {
return $config['config_array']; } } arrayͰ͋Δ͜ͱΛࣔ͢
$logger = $container->get(‘LoggerInterface'); invariant( $logger instanceof LoggerInterface, "Interface '\Psr\Log\LoggerInterface' is
not implemented by this class", ); invariantHackͰ༻ҙ͞Ε͍ͯΔؔ ୈҰҾͷ͕݅falseͷ߹ʹType Error
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; } }
<?hh mixed • ༷ʑͳ͕ฦ٫͞ΕΔͨΊɺmixed͏͖Ͱͳ͍ • PSRʹͩ͜ΘΔඞཁͳ͍ (४ڌ͠ͳ͍͜ͱΛݕ౼த) • େنͳ։ൃͳͲͰͳΔ͘Strict(ฐࣾஊ)
Forget PHP! Facebook’s HHVM engine switches to Hack instead
<?hh hhvm/type-assert
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Λݕࠪ
RouterͱContainerɺ ؆୯ͳValidation͕͋Ε ࠷ݶͷಈ࡞͕Մೳʹ
Dependency / Container Build Application Routing/Dispatcher Middleware
type TResponder = ImmVector<classname<\Psr\Http\Server\MiddlewareInterface>>; hack routerͷTResponder ϦΫΤετʹରԠ͢Δͷ͕ొ͞ΕͯೖΕɺ TResponderͰࢦఆͨ͠ܕΛฦ٫͢Δ
\Nazg\Http\HttpMethod::GET => ImmMap { '/' => ImmVector {App\Action\IndexAction::class}, }, GETʹରԠͤ͞ΔRouteΛهड़
‘/’ ʹΞΫηε࣌ʹىಈ͢ΔϛυϧΣΞΛࢦఆ ImmVector<classname<\Psr\Http\Server\MiddlewareInterface>> هड़ͨ͠௨Γʹ࣮ߦ͞ΕΔ ActionΫϥεಉΠϯλʔϑΣʔε࣮ͷͨΊ۠ผͳ͠
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
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 ૢ࡞ ࢦఆͨ͠ΠϯσοΫεͷΞΠςϜΛআ
https://github.com/ytake/nazg-skeleton https://github.com/nazg-hack/framework
<?hh খ͞ͳϥΠϒϥϦͷू߹ମ • ࠷ۙͷPHPͱಉ༷ʹૄ݁߹ʹ • PSR-11, PSR-7, PSR-15Λར༻ͨͨ͠Ίɺ PHPͷϥΠϒϥϦHackͰ࣮ߦՄೳͳͷOK •
routerhack-routerҎ্ͷͷ͕ͳ͍ͨΊɺ hack-routerʹͷΈґଘ • PSR-7zend-diactorosΛσϑΥϧτʹ (Symfony Component 4Ҏ߱Hack αϙʔτ͞Εͳ͍)
<?hh খ͞ͳϥΠϒϥϦͷू߹ମ • Request BodyͳͲͷValidationʹtype-assert • mixedΛέΞ͢Δ
<?hh • Hack͔ͩΒԿ͔ಛผͳͷɺͱ͍͏ͷແ͍ *ϥϯλΠϜӠʑআ͘ • όά͕গͳ͍ݎ࣮ͳΞϓϦέʔγϣϯ • ίʔυϨϏϡʔෛՙܰݮ • ΈΜͳ͕ࢥ͏΄Ͳ͘͠ͳ͍ʂ
• PHPؾͰ͔ΜͨΜͳϚΠΫϩϑϨʔϜϫʔΫ։ൃ
<?hh ͓·͚ • GoͰΒͳ͔ͬͨͷ͔ʁ -> Go(Goa, Echo)ଟ͘ͷAPIͰಋೖࡁΈ େ͖͘ݴޠΛม͑ͣʹɺ PHPͷ։ൃऀ͕͑ΔͷΛ૿͔ͨͬͨ͠ •
segmentation faultͭΒ͘ͳ͍Ͱ͔͢ʁ hhvm-dbg + straceͰͳΜͱ͔ͳΓ·͢