mindplay / stockpile
Requires
- php: >=5.3.0
- mindplay/filereflection: >=1.2.1, <2
Requires (Dev)
- mindplay/benchpress: *@dev
- phpunit/php-code-coverage: 2.*@dev
README
https://github.com/mindplay-dk/stockpile
Stockpile提供了一个基类,可以轻松实现服务定位器模式,并提供简单的实现简单、高效的依赖注入的方法。
⚠️ 服务定位器通常被认为是一种反模式 - 如果您正在寻找一个不鼓励或优化该模式,但仍然提供出色IDE支持的现代DI容器,请查看 Unbox。
已测试并设计适用于PHP 5.3及以上版本。
请参阅根目录中的 "example.php",了解如何使用此类的示例。
概述
Container
基类将解析您的类上的@property
注释 - 这些提供了设计时IDE支持,同时您的类级别doc-block中的类型提示和属性名也将被基类拾取并解析,然后能够提供运行时类型检查和额外安全性。
API概述
Container
类的生命周期有两个阶段:最初是开放注册和配置,然后通过seal()
方法进行密封(防止进一步修改)。换句话说,最初是只写,然后变为只读。
在调用seal()
之前可用的配置方法
register(string $name, Closure $init) # register component creation function
unregister(string $name) # unregister a component
configure(Closure|Closure[] $config) # configure a registered component
shutdown(Closure $function) # dispose of components after use
load(string $path) # load an external configuration file
始终可用的其他方法
getRootPath() # get configuration files root path
invoke(callable $function, $params) # invoke a function with components as arguments
isActive(string $name) # check if a component has been initialized
isDefined(string $name) # check if a component has been defined
isRegistered(string $name) # check if a component has been registered
一个“已定义”组件是使用@property
注释定义的容器属性。一个“已注册”组件已通过register()
方法注册,或者已通过直接设置属性初始化。一个“活动”组件已初始化,例如,通过在容器密封后访问属性。
使用方法
通过将此类用作您应用程序或模块的全局入口点的基类,您的容器将获得适当的IDE支持和运行时类型检查,例如服务接口和配置值。
一个基本的容器类可能看起来像这样
use mindplay\stockpile\Container; /** * @property string $db_username * @property string $db_password * * @property-read PDO $db application-wide database connection */ class MyApp extends Container { ... }
类的使用可能如下所示
$container = new MyApp(__DIR__ . '/config'); $container->load('default.php'); // load and execute "config/default.php"
请注意,故意不支持通过嵌套数组、XML/JSON/YAML数据文件或其他无模式配置方式进行配置 - 这些增加了复杂性,它们在现代IDE中不提供设计时检查的支持,它们是不必要的,并且没有提供明确的好处。
配置文件
示例中加载的 config/default.php
只是一个PHP脚本,可能看起来像这样
/** @var MyApp $this */ $this->db_username = 'foo'; $this->db_password = 'bar';
注意@var
类型提示,它提供了设计时IDE支持。
容器密封后,当第一次访问 $db
属性时,将调用注册的创建函数。此函数的参数对应于属性名 - 在可能的情况下,您应为此函数添加类型提示以支持IDE;在本例中,这两个属性都是字符串。
依赖解析
通过闭包的参数请求所需组件,使得容器能够级联初始化依赖项(其他组件)。例如,假设有多个不同的组件依赖于缓存组件,下面是一个将视图引擎与缓存组件注册的示例
$container->register( 'view', function (FileCache $cache) { // cache argument injected via $container->cache return new ViewEngine($cache, ...); } );
分层配置
当配置发生在层中(例如,不同环境的多个配置文件)时,您可以通过使用额外的匿名函数进一步配置命名组件,并添加类型提示以支持IDE。
例如,当 $db
组件初始化时,要向MySQL发送 set names utf8
查询,您可能需要添加以下内容
$container->configure( function (PDO $db) { $db->exec("set names utf8"); } );
密封
完成容器配置后,在您可以使用组件之前,需要将其密封 - 这将防止意外进行任何更改,并验证每个已定义组件的配置是否完整。
$container->db_username = '...'; $container->db_password = '...'; $container->seal(); // prevent further changes (exception if incomplete)
注意,如果您有故意缺失的组件,您必须将这些组件显式设置为null - 这迫使您积极做出决定,并导致代码更具自说明性。
立即加载与延迟加载
您不会找到切换组件立即加载/延迟加载的选项 - 假设您希望尽可能晚地初始化所有内容。如果确实有一个立即可用的组件,只需直接注入该组件即可 - 例如
$container->logger = new Logger(...); // eager construction, vs lazy register()
缓存
对于非常大的应用程序(具有许多容器和大量属性),缓存可能有益 - 包含的基准测试显示了这种益处,表明性能提高了约3.5倍,但不要高估这种差异的影响;对于大多数应用程序,实践中可能最多只有几毫秒的差异,因为即使没有缓存,它也已经非常快。
要配置缓存,您需要重写受保护的 getCache()
方法 - 例如,您可以使用容器根路径下的子文件夹
class MyContainer extends Container { /** * @return CacheProvider */ protected function getCache() { return new FileCache($this->getRootPath() . '/cache'); } }
请注意,此文件夹必须可由Web服务器的用户账户写入。
高级用法
对于高级用法,例如构建具有专用行为的容器(例如,通过除解析@property
注释之外的其他方式定义组件)的情况下,有一个抽象基类 AbstractContainer
,它提供了一组更多的受保护API方法。如有需要,请自行探索。