0

我正在将我的应用程序从 Slim/3 迁移到 Slim/4。也许我很困惑,因为相同的东西有无穷无尽的语法,但我写了这个:

use DI\Container;
use Slim\Factory\AppFactory;
use Slim\Psr7\Request;
use Slim\Psr7\Response;

require dirname(__DIR__) . '/vendor/autoload.php';

class Config extends Container
{
}

class Foo
{
    protected $config;

    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    public function __invoke(Request $request, Response $response, array $args): Response {
        var_dump($this->config->get('pi'));
        return $response;
    }
}

$config = new Config();
$config->set('pi', M_PI);
var_dump($config->get('pi'));
AppFactory::setContainer($config);
$app = AppFactory::create();
$app->get('/', \Foo::class);
$app->run();

...它没有像我预期的那样工作,因为我得到了两个完全不同的容器实例(通过在 中设置断点来验证\DI\Container::__construct()):

  1. 我自己用$config = new Config();.
  2. 一个在 处自动创建,$app->run();然后作为参数传递给\Foo::__construct().

我做错了什么?

4

3 回答 3

3

容器尝试解析(并创建)\DI\Container该类的新实例,因为这不是 Slim 使用的接口。相反,请尝试声明 PSR-11 ContainerInterface。然后 DIC 应该传递正确的容器实例。

例子

use Psr\Http\Message\ServerRequestInterface;

public function __construct(ContainerInterface $container)
{
    $this->container = $container;
}

相同的“规则”适用于请求处理程序接口。

完整示例:

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class Foo
{
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function __invoke(
        Request $request,
        Response $response,
        array $args = []
    ): Response {
        var_dump($this->container);
    }
}

最后一点:注入容器是一种反模式。请改为在构造函数中显式声明所有类依赖项。

为什么注入容器(在大多数情况下)是一种反模式?

在 Slim 3 中,“服务定位器”(反模式)是注入整个(Pimple)容器并从中获取依赖项的默认“样式”。

服务定位器(反模式)隐藏了类的真正依赖关系

服务定位器(反模式)也违反了 SOLID 的控制反转(IoC)原则。

问:我怎样才能让它变得更好?

A:使用composition和(显式)构造函数依赖注入

依赖注入是一种将协作者传递给对象的编程实践,而不是对象本身创建它们。

Slim 4开始,您可以像使用现代 DIC 一样PHP-DI使用league/container出色的“自动连线”功能。这意味着:现在您可以在构造函数中显式声明所有依赖项,并让 DIC 为您注入这些依赖项。

更清楚一点:“Composition”与 DIC 的“Autowire”功能无关。您可以将组合与纯类一起使用,而无需容器或其他任何东西。自动装配功能仅使用PHP 反射类为您自动解析和注入依赖项。

于 2019-08-22T19:02:38.647 回答
1

这是 PHP-DI 自动注册自身的结果。在撰写此答案时,PHP-DI 容器在创建时自动将自身注册到 keyDI\Container以及三个实现的接口(请参阅Container.php 的这些行)。因此,如果您针对构造函数参数DI\Container或它实现的三个接口之一(包括Psr\Container\ContainerInterface)键入提示,PHP-DI 能够自行解析。

ُ问题是使用self::class(该文件的第 110 行)使密钥以某种方式硬编码,因此尽管您正在创建( ) 的DI\Container子类,但容器仍然注册到与以前相同的密钥。克服这个问题的一种方法是让容器知道也应该解决它自己。我看到了两种选择:DI\ContainerConfigConfig

  1. 将容器注册到与其类名相同的键,就像做什么DI\Container(这似乎是正确的方法)
  2. 实例化后手动注册容器

这是一个完整的工作示例:

<?php
require '../vendor/autoload.php';
use DI\Container;
use Slim\Factory\AppFactory;

use Psr\Container\ContainerInterface;
use DI\Definition\Source\MutableDefinitionSource;
use DI\Proxy\ProxyFactory;

class Config extends Container
{
    public function __construct(
        MutableDefinitionSource $definitionSource = null,
        ProxyFactory $proxyFactory = null,
        ContainerInterface $wrapperContainer = null
    ) {
        parent::__construct($definitionSource, $proxyFactory, $wrapperContainer);
        // Register the container to a key with current class name
        $this->set(static::class, $this);
    }
}

class Foo
{
    public function __construct(Config $config)
    {
        die($config->get('custom-key'));
    }
}

$config = new Config();
$config->set('custom-key', 'Child container can resolve itself now');
// Another option is to not change Config constructor,
// but manually register the container in intself with new class name
//$config->set(Config::class, $config);
AppFactory::setContainer($config);
$app = AppFactory::create();
$app->get('/', \Foo::class);
$app->run();

请注意正如最佳实践所建议的那样,您不应针对具体类(DI\Container或您的Config类)键入提示,而应考虑针对接口(Psr\Container\ContainerInterface)进行类型提示。

于 2019-08-23T21:18:58.153 回答
0

问题是滥用名为autowiring的 PHP-DI 功能:

自动装配是一个陌生的词,它代表了非常简单的东西:容器自动创建和注入依赖项的能力。

为了实现这一点,PHP-DI 使用 PHP 的反射来检测构造函数需要哪些参数。

如果您使用工厂方法创建容器,您可以禁用自动装配并且“奇怪”行为停止:

$builder = new ContainerBuilder(Config::class);
$builder->useAutowiring(false);
$config = $builder->build();

但我想更好的解决方案是学习如何正确使用自动装配:)

我忽略了所有这些细节,因为我的代码最初是为 Slim/3 编写的,它使用Pimple作为硬编码默认容器。我错误地认为它们的工作方式相似,但尽管是容器解决方案,但两个库却截然不同。

于 2019-08-24T07:33:53.780 回答