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.6k
Hackで作るマイクロフレームワーク
For PHPerKaigi 2018
PHPからHackへ移行するときに気をつけるもの
PHPのライブラリを使ってフレームワークなどを作るにはどうしたらいいか、という内容です
yuuki takezawa
March 06, 2018
Tweet
Share
More Decks by yuuki takezawa
See All by yuuki takezawa
PHPでアクターモデルを理解・体験しよう / Understand and experience the actor model in PHP
ytake
2
230
再考 アクターモデル/ reconsider actor model
ytake
0
920
GoとアクターモデルでES+CQRSを実践! / proto_actor_es_cqrs
ytake
1
370
Phluxorでアクターモデルを 理解・体験しよう / toolkit-for-flexible-actor-models-in-php-phluxor
ytake
1
240
オブジェクトのおしゃべり大失敗 メッセージングアンチパターン集 / messaging anti-pattern collection
ytake
2
1k
DRE/SREのプラクティス融合によるクラウドネイティブなデータ基盤作り / dre_sre
ytake
0
750
技術的負債と向き合う取り組みでよかったもの / positive_efforts_to_tackle_technical_debt
ytake
10
3.8k
アプリケーションエンジニアから強いデータエンジニアへの歩き方 / How to transition and become a Data Engineer from an Application Engineer
ytake
1
530
入門 境界づけられたコンテキスト
ytake
6
4.1k
Other Decks in Technology
See All in Technology
ソフトウェア開発における「パーフェクトな意思決定」/Perfect Decision-Making in Software Development
yayoi_dd
2
2.7k
大規模言語モデルとそのソフトウェア開発に向けた応用 (2024年版)
kazato
2
500
AIエージェントに脈アリかどうかを分析させてみた
sonoda_mj
2
140
Zero Data Loss Autonomous Recovery Service サービス概要
oracle4engineer
PRO
1
5k
EMConf JP の楽しみ方 / How to enjoy EMConf JP
pauli
2
120
OPENLOGI Company Profile for engineer
hr01
1
18k
エンジニアリングマネージャー視点での、自律的なスケーリングを実現するFASTという選択肢 / RSGT2025
yoshikiiida
4
3.3k
Copilotの力を実感!3ヶ月間の生成AI研修の試行錯誤&成功事例をご紹介。果たして得たものとは・・?
ktc_shiori
0
270
最近のSfM手法まとめ - COLMAP / GLOMAPを中心に -
kwchrk
8
1.8k
When Windows Meets Kubernetes…
pichuang
0
270
チームが毎日小さな変化と適応を続けたら1年間でスケール可能なアジャイルチームができた話 / Building a Scalable Agile Team
kakehashi
1
180
実践! ソフトウェアエンジニアリングの価値の計測 ── Effort、Output、Outcome、Impact
nomuson
0
1.8k
Featured
See All Featured
Adopting Sorbet at Scale
ufuk
74
9.2k
The Language of Interfaces
destraynor
155
24k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
950
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
3
340
Side Projects
sachag
452
42k
Making the Leap to Tech Lead
cromwellryan
133
9k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
Facilitating Awesome Meetings
lara
50
6.2k
Agile that works and the tools we love
rasmusluckow
328
21k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
29
2.1k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
120k
Done Done
chrislema
182
16k
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ͰͳΜͱ͔ͳΓ·͢