【実務・中級編】Hackの『Constructor Injection』と型安全な依存解決 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

Hackを掌握する極限の知見:コンパイル時依存解決とConstructor Injectionの極意

コードレビューをしていて、動的言語上がりのエンジニアが持ち込む「実行時DI(依存性注入)コンテナの多用」を見るたびに私は頭を抱えたくなる。
「なぜ、その依存関係の欠落を、本番デプロイ後のリクエストエラーで検知しなければならないのか?」と。

HHVMとHackの型チェッカー(`hh_client`)は、単なるコード補完の道具ではない。お前たちのコードベースの安全性を担保する最強の静的解析要塞だ。
今回は、Hackの厳格モード(`<<__STRICT__>>`)とジェネリクスを極限まで活用し、「コンストラクタインジェクションの依存解決漏れを、完全かつ静的にコンパイル時検知する」ための設計論を叩き込む。

リフレクションに依存した遅延解決や、文字列キーによるサービスロケーターのアンチパターンとは今日で決別しろ。

—

1. 実行時DIコンテナという「甘え」の代償

一般的なPHPエコシステムや、一部の動的言語的アプローチを引きずったHackコードでは、次のようなコードが散見される。

// 【アンチパターン】文字列ベースのサービスロケーター / 実行時解決
class Container {
private dict $services = dict[];

public function bind(string $abstract, mixed $concrete): void {
$this->services[$abstract] = $concrete;
}

public function make(string $abstract): mixed {
// リフレクションを回して動的に解決…
// 型は mixed で返り、依存関係の欠落は実行時(ReflectionException)まで分からない
throw new NotImplementedException();
}
}

このアプローチは最悪だ。
1. 型安全性の崩壊: `mixed` が伝播し、型チェッカーが機能しない。
2. パフォーマンスの劣化: HHVMのJITコンパイラ最適化を阻害するリフレクションの多用。
3. バグの隠蔽: 依存するクラスのコンストラクタを変更した瞬間、本番でクラッシュする爆弾が完成する。

Hackにおいて、依存関係は「コンパイル時に静的に解決されているべき」だ。型チェッカーが「この依存が足りない」とビルドを落としてくれる世界線こそが、プロフェッショナルの現場である。

—

2. コンパイル時DI:型安全なファクトリーとコンストラクタインジェクション

では、Hackの強力なジェネリクスと型システムを使い、依存関係の欠落をコンパイル時に検知するプロダクションコードを構築しよう。

以下のコードは、リフレクションを一切使わず、純粋な型制約とインターフェースの結合によって依存グラフを構築する堅牢な実装だ。

<<__STRICT__>>

namespace Hack\Architecture\DI;

/

  • すべてのサービスの基底インターフェース

/
interface IService {}

/

  • データベース接続を担当する低水準モジュール

/
class DatabaseConnection implements IService {
public function __construct(private string $dsn) {}

public function query(string $sql): void {
// 実際のクエリ実行ロジック
\HH\Asio\join($this->logQueryAsync($sql));
}

private async \Awaitable $logQueryAsync(string $sql): Awaitable {
// 非同期API連携やロギングのシミュレーション
}
}

/

  • ユーザーリポジトリ(DatabaseConnectionに依存)

/
class UserRepository implements IService {
// コンストラクタインジェクション: 依存関係を型として明示
public function __construct(private DatabaseConnection $db) {}

public function findUser(int $id): string {
$this->db->query(“SELECT FROM users WHERE id = {$id}”);
return “User#{$id}”;
}
}

/

  • アプリケーションのユースケース層(UserRepositoryに依存)

/
class GetUserUseCase {
public function __construct(private UserRepository $userRepo) {}

public function execute(int $id): string {
return $this->userRepo->findUser($id);
}
}

ここまでは基本だ。問題は、「誰がこのインスタンスのツリーを組み立て、型安全性を担保するか」である。ここでコンパイル時DIファクトリーが登場する。

—

3. 実践:型安全なDIコンテナ(Container)の実装

サービスロケーターではなく、静的に型付けされたDIコンテナの核心部分を見ていこう。Hackの `shape` やジェネリクスを組み合わせることで、存在しないサービスの取得や依存のミスマッチを型チェッカーが即座に弾く。

<<__STRICT__>>

namespace Hack\Architecture\DI;

/

  • 静的型付きコンテナ
  • 登録されたサービスの型情報は型チェッカーによって厳格に追跡される。

/
class TypeSafeContainer {
private dict $instances = dict[];

public function register(string $abstract, T $concrete): void {
$this->instances[$abstract] = $concrete;
}

/

  • 型パラメータ T を用いることで、キャストなしで具象型を安全に取り出す。
  • 指定した抽象/具象がコンテナに存在しない場合、型チェッカーではなく
  • 設計段階でミスに気づける構造にする。

/
public function resolve(string $key): T {
if (!\array_key_exists($key, $this->instances)) {
throw new \InvariantException(\Str\format(“Dependency ‘%s’ is not bound in the container.”, $key));
}

$instance = $this->instances[$key];

// 型の安全性を保証するためのアサーション
invariant(
$instance is T,
“Resolved instance does not match expected type.”
);

return $instance;
}
}

ブートストラップ層での静的配線

エントリーポイント(例:PSR-15互換のHTTPリクエストハンドラや非同期ワーカー)では、次のように明示的に配線を行う。ここに魔法はない。すべてが明示的であり、すべてが型チェックされる。

<<__STRICT__>>

namespace Hack\Architecture\DI;

class ApplicationBootstrap {
public static function run(): void {
$container = new TypeSafeContainer();

// 1. プリミティブな依存を持つ最下層のサービスを登録
$db = new DatabaseConnection(“mysql:host=localhost;dbname=prod_db”);
$container->register(DatabaseConnection::class, $db);

// 2. 中間層のサービス登録
// もしここで DatabaseConnection のインスタンス化を忘れたり、型を間違えれば
// hh_client が即座にエラーを吐く。
$userRepo = new UserRepository($container->resolve(DatabaseConnection::class));
$container->register(UserRepository::class, $userRepo);

// 3. 上位層(ユースケース)の構築
$useCase = new GetUserUseCase($container->resolve(UserRepository::class));

// 実行
$result = $useCase->execute(42);
\Expr\render($result);
}
}

もしお前が `UserRepository` のコンストラクタに変更を加え、新たな依存(例: `CacheClient`)を追加したとする。その瞬間、上記のブートストラップコードにおける `$container->resolve(…)` の引き渡し忘れや型の不一致は、テストを実行するまでもなく、`hh_client` が赤く染め上げる。これが「コンパイル時検知」の圧倒的な優位性だ。

—

4. HHVMアーキテクチャの観点:なぜこの設計が高速なのか

プロフェッショナルであれば、「この抽象化レイヤーがHHVMのパフォーマンスにどう影響するか」まで考慮できなければならない。

1. JITコンパイラの最適化(Tracelet / Region JIT):
リフレクションや動的なメソッド呼び出し(`call_user_func`など)は、HHVMのプロファイリングJITにとって最大の敵である。型が動的であると推論できないため、ネイティブマシン語へのトランスレーション時にガード(型チェックの条件分岐)が大量発生し、メガモルフィックな呼び出しサイトが生まれる。
今回のコンストラクタインジェクション設計では、すべてのオブジェクト関係が静的に決定しているため、HHVMは関数呼び出しを完全にインライン展開(Inlining)しやすくなり、実行速度は極限まで高まる。

2. メモリオーバヘッドの削減:
実行時DIコンテナが内部で抱える複雑なメタデータやキャッシュ構造体は不要になり、コンテナ自体が単なる `dict` という極めてプリミティブでHHVMの配列最適化の恩恵を受けやすい構造になる。

—

5. テクニカルリードからの最終提言

コードレビューにおいて「動的解決の方が柔軟で書きやすい」などという甘ったれた台詞を聞いたら、その場でプルリクエストを拒絶せよ。

柔軟性とは、型を捨ててコードを泥沼にすることではない。Hackの厳格な型チェッカーという強靭なバックボーンの上で、堅牢かつ拡張性のある構造を組み上げる技術力こそが、プロフェッショナルのエンジニアリングだ。

コンストラクタインジェクションと型安全なコンテナ設計をマスターし、お前のコードベースから「実行時エラーの恐怖」を完全に駆逐しろ。それができるのは、この言語を極めた者だけだ。

タイトルとURLをコピーしました