为了账号安全,请及时绑定邮箱和手机立即绑定

在控制器类中注入容器

在控制器类中注入容器

PHP
qq_笑_17 2021-12-03 15:03:21
我正在将我的应用程序从 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()):我用$config = new Config();.一个在 at 自动创建,$app->run();然后作为参数传递给\Foo::__construct().我做错了什么?
查看完整描述

3 回答

?
绝地无双

TA贡献1946条经验 获得超4个赞

容器尝试解析(并创建)类的新实例\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和(显式)构造函数依赖注入。


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


由于斯利姆4您可以使用现代化的DIC像PHP-DI和league/container与真棒“自动装配”功能。这意味着:现在您可以在构造函数中显式声明所有依赖项,并让 DIC 为您注入这些依赖项。


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


查看完整回答
反对 回复 2021-12-03
?
达令说

TA贡献1821条经验 获得超6个赞

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


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


将容器注册到与其类名相同的键,就像做什么DI\Container一样(这似乎是正确的方法)

实例化后手动注册容器

这是一个完全有效的示例:


<?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)键入提示。


查看完整回答
反对 回复 2021-12-03
?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

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


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


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


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


$builder = new ContainerBuilder(Config::class);

$builder->useAutowiring(false);

$config = $builder->build();

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


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


查看完整回答
反对 回复 2021-12-03
  • 3 回答
  • 0 关注
  • 169 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信