4.3. PHP definitions

未匹配的标注

PHP definitions

除了 自动装配注解, 你还可以使用 PHP 配置格式 来定义注入

你可以将配置注册为数组:

$containerBuilder->addDefinitions([
    // 将定义写在这里
]);

或将其放入返回数组的文件中

<?php
return [
    // 将定义写在这里
];
$containerBuilder->addDefinitions('config.php');

关于懒加载的一句话

PHP-DI 加载你编写的定义,并将其用作关于如何创建对象的说明。

但是,仅当从容器中请求了这些对象,例如通过 $container->get(…)或需要将它们注入另一个对象时才创建这些对象。这意味着你可以拥有大量的定义,在需要的时候,PHP-DI才会创建对象。

唯一的例外是将对象定义为 ,但不建议这样做 (在 "值" 部分对此进行了说明)。

语法

PHP-DI的定义使用 DSL (领域特定语言,用PHP并基于辅助函数编写) 来编写。

此页面上显示的所有示例均使用与PHP 7.0兼容的语法。鼓励您使用以下功能:

  • PHP 5.5 ::class 魔术常数:

    use Psr\Log\LoggerInterface;
    use Monolog\Logger;
    
    return [
        LoggerInterface::class => DI\create(Logger::class)
    ];
  • PHP 5.6 函数导入:

    use function DI\create;
    use function DI\get;
    
    return [
        'Foo' => create()
            ->constructor(get('Bar')),
    ];

注意: 请记住辅助函数 (例如DI\create()) 是命名空间函数,而不是类。不要使用 new (例如 new DI\create()) ,否则会出现致命错误"Class 'DI\create' not found"

定义类型

此定义格式是所有功能中最强大的。您可以定义以下几种 条目类型

值 (即 Symfony中的parameters ) 是简单的PHP值。

return [
    'database.host'     => 'localhost',
    'database.port'     => 5000,
    'report.recipients' => [
        'bob@example.com',
        'alice@example.com',
    ],
];

您还可以通过直接创建对象来定义它们:

return [
    'Foo' => new Foo(),
];

但是 不建议这样做,因为即使没有使用该对象,也会为每个PHP请求创建该对象(不会像本节顶部所述那样被懒加载)。这也将阻止容器被编译。

您应该改用下面的方法之一。

工厂

工厂是可返回实例的PHP可调用对象。 它允许轻松地定义对象, 即仅在实际需要时才创建每个对象(因为可调用对象将在实际需要时被调用)。

就像任何其他定义一样,工厂只需要被调用一次,以后每次需要解析工厂时都会返回相同的结果。

可以使用 DI\factory() 辅助函数来定义工厂, 但可以使用闭包函数来作为快捷方式:

use Psr\Container\ContainerInterface;
use function DI\factory;

return [
    'Foo' => function (ContainerInterface $c) {
        return new Foo($c->get('db.host'));
    },

    // 相当于
    'Foo' => DI\factory(function (ContainerInterface $c) {
        return new Foo($c->get('db.host'));
    }),
];

可以通过类型提示来注入其他服务 (只要它们在容器中注册或启用自动装配即可):

return [
    'LoggerInterface' => DI\create('MyLogger'),

    'Foo' => function (LoggerInterface $logger) {
        return new Foo($logger);
    },
];

DI\factory() 提供了一个 parameter() 方法, 使您可以指定无法通过类型提示自动插入的条目(例如值)。

return [
    'Database' => DI\factory(function ($host) {...})
        ->parameter('host', DI\get('db.host')),
];

如第一个示例所示,这也可以通过注入容器本身来完成。当注入容器时,应该针对接口 Psr\Container\ContainerInterface 而不是实现 DI\Container进行类型提示。

工厂可以是任何 PHP callable, 因此它们也可以是类方法:

class FooFactory
{
    public function create(Bar $bar)
    {
        return new Foo($bar);
    }
}

您可以在定义时加载 FooFactory

return [
    // 不推荐!
    Foo::class => DI\factory([new FooFactory, 'create']),
];

但是即使没有使用,工厂也会在每个请求上创建(new FooFactory) 。此外,使用这种方法很难在工厂中传递依赖关系。

推荐的解决方案是让容器创建工厂:

return [
    Foo::class => DI\factory([FooFactory::class, 'create']),
    // 替代语法:
    Foo::class => DI\factory('Namespace\To\FooFactory::create'),
];

上面的配置等效于以下代码:

$factory = $container->get(FooFactory::class);
return $factory->create(...);

如果工厂是静态方法,则非常简单:

class FooFactory
{
    public static function create()
    {
        return ...
    }
}

return [
    Foo::class => DI\factory([FooFactory::class, 'create']),
];

请注意:

  • factory([FooFactory::class, 'build']): 如果 build()static 方法, 则不会创建该对象: FooFactory::build() 将会被静态调用 (正如你想到的一样)
  • 您可以在数组中设置任何容器条目名称,例如DI\factory(['foo_bar_baz', 'build']) (或者: DI\factory('foo_bar_baz::build')),与其他任何对象一样,允许你配置 foo_bar_baz 及其依赖项
  • 因为工厂可以是任何PHP callable,所以您也可以使用可调用对象: DI\factory(InvokableFooFactory::class) (或者: DI\factory('invokable_foo_factory'),前提已经在容器中定义。)
  • 所有闭包都将被PHP-DI视为 工厂,即使它们嵌套在其他定义中,例如 create(), env(), 等。 (请参阅 Nesting definitions 部分)

检索请求条目的名称

如果要重复使用同一工厂来创建不同的条目,则可能要检索当前正在解析的条目的名称。你可以通过类型提示注入DI\Factory\RequestedEntry 对象来做到这一点:

use DI\Factory\RequestedEntry;

return [
    'Foo' => function (RequestedEntry $entry) {
        // $entry->getName() 包含请求的名称
        $class = $entry->getName();
        return new $class();
    },
];

由于 RequestedEntry 是使用类型提示进行注入的,因此可以将其与注入容器或任何其他服务结合使用。工厂参数的顺序无关紧要。

装饰器

当使用具有多个定义文件的系统时,可以使用装饰器覆盖先前的条目:

return [
    // 装饰先前在另一个文件中定义的条目
    'WebserviceApi' => DI\decorate(function ($previous, ContainerInterface $c) {
        return new CachedApi($previous, $c->get('cache'));
    }),
];

请阅读 definition overriding guide 以了解有关此内容的更多信息。

对象

使用工厂创建对象非常强大(因为我们可以使用PHP进行任何操作), 但是 DI\create() 有时会更简单。一些例子:

return [
    // 实例化Logger类以创建对象
    'Logger' => DI\create(),
    // 将接口映射到实现
    'LoggerInterface' => DI\create('MyLogger'),
    // 对条目使用任意名称
    'logger.for.backend' => DI\create('Logger'),
];

DI\create() 可用于定义构造函数参数:

return [
    'Logger' => DI\create()
        ->constructor('app.log', DI\get('log.level'), DI\get('FileWriter')),
];

setter/method 注入:

return [
    'Database' => DI\create()
        ->method('setLogger', DI\get('Logger')),
    // 您可以两次调用一个方法
    'Logger' => DI\create()
        ->method('addBackend', 'file')
        ->method('addBackend', 'syslog'),
];

属性注入:

return [
    'Foo' => DI\create()
        ->property('bar', DI\get('Bar')),
];

每个条目只需解析一次,即会在任何使用到的地方注入相同的实例。 就像其他定义一样, 使用 create()进行对象定义将在需要的时候才加载 (懒加载)。

自动装配对象

如果你启用了 自动装配 ,你可以使用 DI\autowire() 来定制自动装配的方式。

除了不是从头开始配置对象的构建方式,DI\autowire() 的行为与 DI\create()相似, 我们仅覆盖自动装配所需的内容。

return [
    // 当不使用任何选项时,无需将其写入配置文件
    'MyLogger' => DI\autowire(),

    // 将接口映射到实现(自动装配MyLogger类)
    'LoggerInterface' => DI\autowire('MyLogger'),

    // 对条目使用任意名称
    'logger.for.backend' => DI\autowire('MyLogger'),
];

就像 DI\create()一样,你可以显示设置构造函数参数:

return [
    'Logger' => DI\autowire()
        ->constructor('app.log', DI\get('log.level'), DI\get('FileWriter')),
];

setter/method 注入:

return [
    'Database' => DI\autowire()
        ->method('setLogger', DI\get('Logger')),
];

属性注入:

return [
    'Foo' => DI\autowire()
        ->property('bar', DI\get('Bar')),
];

您也可以只定义特定的参数: 它允许使用类型提示来定义自动装配无法猜测的参数。

return [
    'Logger' => DI\autowire()
        // 设置$ filename参数
        ->constructorParameter('filename', 'app.log')
        // 设置$ handler参数
        ->methodParameter('setHandler', 'handler', DI\get('SyslogHandler')),
];

别名

您可以使用 DI\get() 为另一个条目设置别名:

return [
    'doctrine.entity_manager' => DI\get('Doctrine\ORM\EntityManager'),
];

环境变量

你可以通过使用 DI\env() 来获取环境变量的值:

return [
    'db1.url' => DI\env('DATABASE_URL'),
    // 具有默认值
    'db2.url' => DI\env('DATABASE_URL', 'postgresql://user:pass@localhost/db'),
    // 默认值为另一个条目
    'db2.host' => DI\env('DATABASE_HOST', DI\get('db.host')),
];

字符串表达式

你可以使用 DI\string() 来连接字符串条目:

return [
    'path.tmp' => '/tmp',
    'log.file' => DI\string('{path.tmp}/app.log'),
];

数组

条目可以是包含简单值或其他条目的数组:

return [
    'report.recipients' => [
        'bob@example.com',
        'alice@example.com',
    ],
    'log.handlers' => [
        DI\get('Monolog\Handler\StreamHandler'),
        DI\get('Monolog\Handler\EmailHandler'),
    ],
];

如果您有多个定义文件,则数组还具有其他功能:详情请阅读 definition overriding

通配符

你可以使用通配符批量定义条目。将接口绑定到实现可能非常有用:

return [
    'Blog\Domain\*RepositoryInterface' => DI\create('Blog\Architecture\*DoctrineRepository'),
];

在我们的示例中,通配符将匹配 Blog\Domain\UserRepositoryInterface, 并将其映射到
Blog\Architecture\UserDoctrineRepository.

您最好清楚并了解:

  • 通配符在命名空间之间不匹配
  • 优先选择完全匹配(即不使用*),而不是通配符的匹配(首先,PHP-DI查找完全匹配,然后在通配符中搜索)
  • 如果出现“冲突” (即2个不同的通配符匹配),则以第一个匹配为准

嵌套定义

您可以将定义嵌套在其他内部,以避免不必要的条目污染容器。例如:

return [
    'Foo' => DI\create()
        ->constructor(DI\string('{root_directory}/test.json'), DI\create('Bar')),
];

请记住,闭包等效于“工厂”定义。 因此, 闭包始终会解释为工厂,即使嵌套在其他定义中也是如此。 如果将匿名函数用于工厂以外的其他内容,则需要将其包装在 DI\value() 中:

return [
    'router' => DI\create(Router::class)
        ->method('setErrorHandler', DI\value(function () {
            ...
        })),
];

当然,这仅适用于配置文件中的闭包。

直接在容器中设置定义

除了在数组中定义条目外,还可以直接在容器中设置它们,如下所示。

$container->set('db.host', 'localhost');
$container->set('My\Class', \DI\create()
    ->constructor('some raw value')));

但是建议使用数组定义,因为它允许 编译容器. 所有 Container::set()配置的条目将不会被编译。

另请注意,使用编译容器时,不可能在运行中向容器中添加定义:

$builder = new ContainerBuilder();
$builder->setDefinitionCache(new ApcCache());
$container = $builder->build();

//  您可以设置值
$container->set('foo', 'hello');
$container->set('bar', new MyClass());

// 报错: 使用缓存时,无法使用-> set()设置定义
$container->set('foo', DI\create('MyClass'));

这样做的原因是定义被缓存(而不是值)。如果动态设置定义,则会将其缓存,,这可能会导致非常奇怪的错误(因为动态定义当然是动态的,因此不应缓存它们)。

在这种情况下,将您的定义放在如本文上面所示的数组或文件中。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/php-di/6.0/php-...

译文地址:https://learnku.com/docs/php-di/6.0/php-...

上一篇 下一篇
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~