bluepsyduck/laminas-autowire-factory

一个允许自动装配,类似于Symfony的Laminas工厂实现。

2.1.0 2023-10-03 06:52 UTC

This package is auto-updated.

Last update: 2024-09-03 08:52:49 UTC


README

GitHub release (latest SemVer) GitHub build Codecov

这个库提供了一些工厂和属性,帮助自动装配Laminas ServiceManager的服务类,以减少实际工厂的编写。

轮转策略

该库提供了几种策略来解析类的参数。它们共同的特点是只解析构造函数参数,而不是直接解析属性。每个参数都是独立解析的,因此一个参数使用某种策略不会影响其他参数。

解析策略是通过在构造函数的参数上提供属性来指定的。如果没有指定解析属性,AutoWireFactory将使用默认的解析策略。

默认策略

如果没有指定其他策略,则AutoWireFactory将使用默认策略,尝试从参数类型和名称中推导服务。

默认策略采用Symfony处理自动装配的方法,特别是处理相同类型的多个实现

以下情况可以通过默认策略处理

具有默认值的参数

示例:__construct(string $fancyParameter = 'fancy-value')

如果参数具有默认值,则将使用该值,跳过所有其他解析步骤。

具有类类型提示的参数

示例:__construct(FancyClass $fancy)

如果参数具有类名作为类型提示,则在容器中检查以下别名

  1. FancyClass $fancy:类名和参数名的组合。这允许有多个实现同一接口,如Symphony文档中所述。
  2. FancyClass:“默认”情况,将类以其名称注册到容器中。
  3. $fancy:使用参数名本身的回退,主要是为了使别名在不同情况下保持一致。

容器将使用可以提供的第一个别名。

具有标量类型提示的参数

示例:__construct(array $fancyConfig)

如果参数用标量类型提示,例如将配置值拉入服务,则检查以下别名

  1. array $fancyConfig:类型和参数名的组合,与类类型提示相同。
  2. $fancyConfig:仅使用参数名的回退。

请注意,仅类型本身(array)不作为别名使用。

没有类型提示的参数

示例:__construct($fancyParameter)

在这种情况下,由于信息缺失,只能检查一个别名

  1. $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" 配置键中提供的别名,并使用这些别名从容器中请求服务,这里分别是 FancyResolverNotSoFancyResolver 的类名。然后,将服务数组传递给服务作为 $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:使用额外的工厂

以下示例展示了如何使用AutoWireFactoryConfigReaderFactory来自动连接一个服务类。

假设我们有一个以下的应用程序配置,我们想要从中获取一个值

[
    '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'),
        ],
    ],
];