bluepsyduck / laminas-autowire-factory
一个允许自动装配,类似于Symfony的Laminas工厂实现。
Requires
- php: ^8.0
- laminas/laminas-servicemanager: ^3.0
Requires (Dev)
- bluepsyduck/test-helper: ^2.0
- laminas/laminas-config-aggregator: ^1.1
- mikey179/vfsstream: ^1.6
- phpstan/phpstan: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpstan/phpstan-strict-rules: ^1.0
- phpunit/phpunit: ^9.0
- rregeer/phpunit-coverage-check: ^0.3
- squizlabs/php_codesniffer: ^3.4
This package is auto-updated.
Last update: 2024-09-03 08:52:49 UTC
README
这个库提供了一些工厂和属性,帮助自动装配Laminas ServiceManager的服务类,以减少实际工厂的编写。
轮转策略
该库提供了几种策略来解析类的参数。它们共同的特点是只解析构造函数参数,而不是直接解析属性。每个参数都是独立解析的,因此一个参数使用某种策略不会影响其他参数。
解析策略是通过在构造函数的参数上提供属性来指定的。如果没有指定解析属性,AutoWireFactory将使用默认的解析策略。
默认策略
如果没有指定其他策略,则AutoWireFactory将使用默认策略,尝试从参数类型和名称中推导服务。
默认策略采用Symfony处理自动装配的方法,特别是处理相同类型的多个实现。
以下情况可以通过默认策略处理
具有默认值的参数
示例:__construct(string $fancyParameter = 'fancy-value')
如果参数具有默认值,则将使用该值,跳过所有其他解析步骤。
具有类类型提示的参数
示例:__construct(FancyClass $fancy)
如果参数具有类名作为类型提示,则在容器中检查以下别名
FancyClass $fancy
:类名和参数名的组合。这允许有多个实现同一接口,如Symphony文档中所述。FancyClass
:“默认”情况,将类以其名称注册到容器中。$fancy
:使用参数名本身的回退,主要是为了使别名在不同情况下保持一致。
容器将使用可以提供的第一个别名。
具有标量类型提示的参数
示例:__construct(array $fancyConfig)
如果参数用标量类型提示,例如将配置值拉入服务,则检查以下别名
array $fancyConfig
:类型和参数名的组合,与类类型提示相同。$fancyConfig
:仅使用参数名的回退。
请注意,仅类型本身(array
)不作为别名使用。
没有类型提示的参数
示例:__construct($fancyParameter)
在这种情况下,由于信息缺失,只能检查一个别名
$fancyParameter
:回退是唯一可能的别名。
通过别名解析
通过指定从容器请求的确切别名来解析参数。这是通过使用Alias
属性来完成的
use BluePsyduck\LaminasAutoWireFactory\Attribute\Alias; class ClassWithAliasParameter { public function __construct( #[Alias('alias-for-fancy-class')] private FancyClass $fancy, ) {} }
在这种情况下,AutoWireFactory将使用容器中注册为“alias-for-fancy-class”的服务来解析$fancy
。
使用配置中的标量值
AutoWireFactory还可以使用ReadConfig
属性将应用程序配置中的值注入到服务中。该属性期望配置键。
use BluePsyduck\LaminasAutoWireFactory\Attribute\ReadConfig; class ClassWithConfigValue { public function __construct( #[ReadConfig('foo', 'bar')] private string $timeout, ) {} }
在这种情况下,$config['foo']['bar']
的值被注入到服务中。请注意,类型必须匹配。
默认情况下,解析器使用别名 config
从容器中获取应用程序配置。如果您的配置可以通过另一个别名访问,请通过 ReadConfig::$configAlias = 'fancy-config'
设置别名。所有基于配置的解析器都将使用此别名。
通过别名注入服务数组
可能存在配置指定了服务所需的别名列表的情况。为此,可以使用 InjectAliasArray
属性。同样,该属性期望读取配置键以获取别名。
use BluePsyduck\LaminasAutoWireFactory\Attribute\InjectAliasArray; /* Config: [ 'resolvers' => [ FancyResolver::class, NotSoFancyResolver::class, ], ] */ class ClassWithAliasArray { public function __construct( #[InjectAliasArray('resolvers')] private array $resolvers, ) {} }
在此示例中,解析器将读取 "resolvers" 配置键中提供的别名,并使用这些别名从容器中请求服务,这里分别是 FancyResolver
和 NotSoFancyResolver
的类名。然后,将服务数组传递给服务作为 $resolvers
。
自动接线工厂
AutoWireFactory
使用反射来确定如何解析依赖项并创建实际服务。它将检查上述提到的任何属性以选择策略,或者在没有找到任何属性的情况下回退到默认策略。
将 AutoWireFactory 作为抽象工厂使用
除了使用 FactoryInterface
在容器配置中显式使用 AutoWireFactory
之外,AutoWireFactory 还实现了 AbstractFactoryInterface
:如果您将此工厂作为抽象工厂添加,它将尝试自动连接所有内容。这将使配置容器大多成为冗余,除非仍有服务需要自定义工厂。
缓存
AutoWireFactory
使用反射来解析依赖项。为了使事情更快,工厂提供在文件系统上构建缓存的功能,以避免在每次脚本调用时使用反射。要启用缓存,例如在 config/container.php
文件中添加以下行:
\BluePsyduck\LaminasAutoWireFactory\AutoWireFactory::setCacheFile('data/cache/autowire-factory.cache');
其他工厂
库提供其他工厂,您可以使用它们指定某些参数应该如何解析。所有这些工厂与上述提到的相应属性具有相同的功能。工厂旨在直接在容器配置中使用,而不是在构造函数上使用属性。请注意,属性将被优先考虑,因为额外的配置仅用于默认策略(在没有属性的情况下)。
配置读取器工厂
此工厂可以替代 ReadConfig
属性,再次使用配置键来读取值。
有两种使用工厂的方法
// dependencies.php use BluePsyduck\LaminasAutoWireFactory\Factory\ConfigReaderFactory; use BluePsyduck\LaminasAutoWireFactory\AutoWireUtils; 'factories' => [ // Either instantiate the factory directly: 'int $timeout' => new ConfigReaderFactory('fancy-service', 'timeout'), // Or use the Utils class instead: 'int $timeout' => AutoWireUtils::readConfig('fancy-service', 'timeout'), ]
在两种情况下,$config['fancy-service']['timeout']
都会被注册到容器中,以便在默认解析策略中使用。
别名数组注入器工厂
此工厂可以替代 InjectAliasArray
属性,再次使用配置键来读取别名。
又有两种使用工厂的方法
// dependencies.php use BluePsyduck\LaminasAutoWireFactory\Factory\AliasArrayInjectorFactory; use BluePsyduck\LaminasAutoWireFactory\AutoWireUtils; 'factories' => [ // Either instantiate the factory directly: 'array $resolvers' => new AliasArrayInjectorFactory('resolvers'), // Or use the Utils class instead: 'array $resolvers' => AutoWireUtils::injectAliasArray('resolvers'), ]
在两种情况下,$config['resolvers']
都被用作容器的别名,收到的实例可以在默认解析策略中使用。
示例
为了更好地了解 AutoWireFactory 的工作原理,应给出两个完整的示例。这两个示例执行相同的功能,第一个使用属性,第二个使用附加工厂。您可以根据自己的需要决定使用哪种变体。
虽然示例使用构造函数属性提升来同时指定属性和参数,但所有功能也适用于非提升参数。
示例 1:使用属性
以下示例展示了如何使用 AutoWireFactory
和属性来自动连接服务类。
假设我们有一个以下的应用程序配置,我们想要从中获取一个值
[ 'fancy-service' => [ 'fancy-property' => 'Hello World!', 'fancy-adapters' => [ FancyAdapterAlpha::class, FancyAdapterOmega::class, ], ], ]
我们想要自动连接以下服务类
use BluePsyduck\LaminasAutoWireFactory\Attribute\ReadConfig; use BluePsyduck\LaminasAutoWireFactory\Attribute\InjectAliasArray; class FancyService { public function __construct( private FancyComponent $component, #[ReadConfig('fancy-service', 'fancy-property')] private string $fancyProperty, #[InjectAliasArray('fancy-service', 'fancy-adapters')] private array $fancyAdapters, ) {} } class FancyComponent {} class FancyAdapterAlpha {} class FancyAdapterOmega {}
构造函数的第一个参数没有指定属性,因此使用默认的类型解析策略。对于其他两个参数,已指定了属性,因此将相应地解析它们。
以下配置可以在不编写任何工厂的情况下用于容器
<?php use BluePsyduck\LaminasAutoWireFactory\AutoWireFactory; use Laminas\ServiceManager\Factory\InvokableFactory; use function BluePsyduck\LaminasAutoWireFactory\injectAliasArray; use function BluePsyduck\LaminasAutoWireFactory\readConfig; return [ 'dependencies' => [ 'factories' => [ // Enable auto-wiring for the service itself. FancyService::class => AutoWireFactory::class, // FancyComponent and the other classes do not need any factory as they do not have a constructor. // Both InvokableFactory and AutoWireFactory are usable here. FancyComponent::class => InvokableFactory::class, FancyAdapterAlpha::class => InvokableFactory::class, FancyAdapterOmega::class => InvokableFactory::class, ], ], ];
如果我们使用AutoWireFactory
作为抽象工厂,则此配置可以更简短
<?php use BluePsyduck\LaminasAutoWireFactory\AutoWireFactory; use function BluePsyduck\LaminasAutoWireFactory\injectAliasArray; use function BluePsyduck\LaminasAutoWireFactory\readConfig; return [ 'dependencies' => [ 'abstract_factories' => [ // Will auto-wire everything possible to be auto-wired, in our case the FancyService, FancyComponent, // and the adapters. AutoWireFactory::class, ], ], ];
示例2:使用额外的工厂
以下示例展示了如何使用AutoWireFactory
和ConfigReaderFactory
来自动连接一个服务类。
假设我们有一个以下的应用程序配置,我们想要从中获取一个值
[ 'fancy-service' => [ 'fancy-property' => 'Hello World!', 'fancy-adapters' => [ FancyAdapterAlpha::class, FancyAdapterOmega::class, ], ], ]
我们想要自动连接以下服务类
class FancyService { public function __construct( private FancyComponent $component, private string $fancyProperty, private array $fancyAdapters ) {} } class FancyComponent {} class FancyAdapterAlpha {} class FancyAdapterOmega {}
FancyService在其构造函数上没有指定任何属性,这意味着为它的所有参数使用默认的类型解析策略。
以下配置可以在不编写任何工厂的情况下用于容器
<?php use BluePsyduck\LaminasAutoWireFactory\AutoWireFactory; use BluePsyduck\LaminasAutoWireFactory\AutoWireUtils; use Laminas\ServiceManager\Factory\InvokableFactory; return [ 'dependencies' => [ 'factories' => [ // Enable auto-wiring for the service itself. FancyService::class => AutoWireFactory::class, // FancyComponent and the other classes do not need any factory as they do not have a constructor. // Both InvokableFactory and AutoWireFactory are usable here. FancyComponent::class => InvokableFactory::class, FancyAdapterAlpha::class => InvokableFactory::class, FancyAdapterOmega::class => InvokableFactory::class, // Enable the scalar property for auto-wiring into the service. // In this example, the factory would fetch "Hello World!" from the config. 'string $fancyProperty' => AutoWireUtils::readConfig('fancy-service', 'fancy-property'), // Inject an array of other services through their aliases into the service. // In this example, instances of FancyAdapterAlpha and FancyAdapterOmega would be injected. 'array $fancyAdapters' => AutoWireUtils::injectAliasArray('fancy-service', 'fancy-adapters'), ], ], ];
如果我们使用AutoWireFactory
作为抽象工厂,则此配置可以更简短
<?php use BluePsyduck\LaminasAutoWireFactory\AutoWireFactory; use BluePsyduck\LaminasAutoWireFactory\AutoWireUtils; return [ 'dependencies' => [ 'abstract_factories' => [ // Will auto-wire everything possible to be auto-wired, in our case the FancyService, FancyComponent, // and the adapters. AutoWireFactory::class, ], 'factories' => [ // Any additional factories must still be specified in the config to make the corresponding parameters // resolvable by the AutoWireFactory. // Any aliases using property names cannot be handled by the AutoWireFactory and must still get listed. 'string $fancyProperty' => AutoWireUtils::readConfig('fancy-service', 'fancy-property'), 'array $fancyAdapters' => AutoWireUtils::injectAliasArray('fancy-service', 'fancy-adapters'), ], ], ];