编译容器
零反射的生产环境容器,实现最大性能。
概览
开发环境中,Wirebox 使用反射解析依赖。这对开发足够快,但在生产环境有额外开销。编译容器生成一个纯 PHP 类,为每个服务提供专用工厂方法——运行时零反射。
生成编译容器
use AsceticSoft\Wirebox\ContainerBuilder;
$builder = new ContainerBuilder(projectDir: __DIR__);
// 照常配置
$builder->exclude('Entity/*');
$builder->scan(__DIR__ . '/src');
$builder->bind(LoggerInterface::class, FileLogger::class);
$builder->parameter('db.host', '%env(DB_HOST)%');
$builder->parameter('db.port', '%env(int:DB_PORT)%');
// 生成编译容器
$builder->compile(
outputPath: __DIR__ . '/var/cache/CompiledContainer.php',
className: 'CompiledContainer',
namespace: 'App\Cache',
);
参数
| 参数 | 说明 |
|---|---|
outputPath |
生成的 PHP 类文件路径 |
className |
生成的类名 |
namespace |
生成类的 PHP 命名空间 |
使用编译容器
生产环境中,引入生成的文件并直接实例化:
require_once __DIR__ . '/var/cache/CompiledContainer.php';
$container = new App\Cache\CompiledContainer();
// 使用方式与运行时容器完全相同
$service = $container->get(UserService::class);
$loggers = $container->getTagged('logger');
$host = $container->getParameter('db.host');
编译容器实现 Psr\Container\ContainerInterface,支持与运行时容器相同的 API。
编译内容
生成的类包含:
- 工厂方法 — 每个服务一个专用的
create_*()方法 - 单例缓存 — 服务在首次创建后缓存
- 绑定映射 — 接口到实现的映射
- 参数 — 所有定义的参数及解析后的 env 表达式
- 标签 — 用于
getTagged()的标签服务分组 - 惰性代理 — 通过
ReflectionClass::newLazyProxy()延迟实例化 - Setter 注入 — 通过
call()配置的方法调用
开发 vs 生产工作流
推荐方案
// bootstrap.php
use AsceticSoft\Wirebox\ContainerBuilder;
$compiledPath = __DIR__ . '/var/cache/CompiledContainer.php';
if (file_exists($compiledPath)) {
// 生产:编译容器
require_once $compiledPath;
$container = new App\Cache\CompiledContainer();
} else {
// 开发:运行时容器
$builder = new ContainerBuilder(projectDir: __DIR__);
$builder->scan(__DIR__ . '/src');
$builder->bind(LoggerInterface::class, FileLogger::class);
$builder->parameter('db.host', '%env(DB_HOST)%');
$container = $builder->build();
}
构建脚本
创建部署脚本:
// bin/compile-container.php
use AsceticSoft\Wirebox\ContainerBuilder;
require_once __DIR__ . '/../vendor/autoload.php';
$builder = new ContainerBuilder(projectDir: dirname(__DIR__));
$builder->scan(dirname(__DIR__) . '/src');
$builder->bind(LoggerInterface::class, FileLogger::class);
$builder->parameter('db.host', '%env(DB_HOST)%');
$builder->compile(
outputPath: dirname(__DIR__) . '/var/cache/CompiledContainer.php',
className: 'CompiledContainer',
namespace: 'App\Cache',
);
echo "容器编译成功。\n";
部署时运行:
php bin/compile-container.php
限制
编译容器不支持工厂闭包。闭包无法序列化为 PHP 代码。请改用带属性的自动装配。
// 这不会包含在编译容器中
$builder->register(PDO::class, function ($c) {
return new PDO(...);
});
// 改用这种方式
class PdoFactory
{
public function __construct(
#[Param('DB_HOST')] private string $host,
#[Param('DB_PORT')] private int $port,
) {
}
public function create(): PDO
{
return new PDO("mysql:host={$this->host};port={$this->port}");
}
}
编译容器中的惰性代理
惰性代理完全支持。生成的工厂方法使用 ReflectionClass::newLazyProxy():
protected function createAppHeavyService(): \App\HeavyService
{
$ref = new \ReflectionClass(\App\HeavyService::class);
return $ref->newLazyProxy(function () {
return new \App\HeavyService(
$this->get(\App\Database::class),
);
});
}
代理行为与运行时容器的惰性行为完全一致。
最佳实践
- 服务变更后务必重新生成编译容器
- 添加到
.gitignore— 不要提交生成的文件:/var/cache/CompiledContainer.php - 部署时编译 — 添加到 CI/CD 流水线:
php bin/compile-container.php composer dump-env prod - 编译前验证 —
compile()执行与build()相同的检查,包括循环依赖检测 - 共享配置 — 将构建器配置提取到函数或配置文件中,避免开发和编译之间重复