在Symfony 3.3中使用DI在抽象类中自动装配,是否可能?

时间:2017-08-25 18:58:32

标签: php symfony dependency-injection symfony-3.3 symfony3.x

我正在将Symfony 3.2项目移至Symfony 3.3,我想使用DI new features。我有read the docs但到目前为止我可以让它发挥作用。请参阅以下类定义:

use Http\Adapter\Guzzle6\Client;
use Http\Message\MessageFactory;

abstract class AParent
{
    protected $message;
    protected $client;
    protected $api_count_url;

    public function __construct(MessageFactory $message, Client $client, string $api_count_url)
    {
        $this->message       = $message;
        $this->client        = $client;
        $this->api_count_url = $api_count_url;
    }

    public function getCount(string $source, string $object, MessageFactory $messageFactory, Client $client): ?array
    {
        // .....
    }

    abstract public function execute(string $source, string $object, int $qty, int $company_id): array;
    abstract protected function processDataFromApi(array $entities, int $company_id): array;
    abstract protected function executeResponse(array $rows = [], int $company_id): array;
}

class AChildren extends AParent
{
    protected $qty;

    public function execute(string $source, string $object, int $qty, int $company_id): array
    {
        $url      = $this->api_count_url . "src={$source}&obj={$object}";
        $request  = $this->message->createRequest('GET', $url);
        $response = $this->client->sendRequest($request);
    }

    protected function processDataFromApi(array $entities, int $company_id): array
    {
        // ....
    }

    protected function executeResponse(array $rows = [], int $company_id): array
    {
        // ....
    }
}

这是我的app/config/services.yml文件的样子:

parameters:
    serv_api_base_url: 'https://url.com/api/'

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    CommonBundle\:
        resource: '../../src/CommonBundle/*'
        exclude: '../../src/CommonBundle/{Entity,Repository}'

    CommonBundle\Controller\:
        resource: '../../src/CommonBundle/Controller'
        public: true
        tags: ['controller.service_arguments']

    # Services that need manually wiring: API related
    CommonBundle\API\AParent:
        arguments:
            $api_count_url: '%serv_api_base_url%'

但是我收到以下错误:

  

AutowiringFailedException无法自动装配服务   " CommonBundle \ API \ AChildren":参数" $ api_count_url"方法   " __构建体()"必须有一个类型提示或明确给出一个值。

当然我在这里遗漏了一些东西,或者仅仅这是不可能的,这导致我接下来的问题:这是一个糟糕的OOP设计还是它缺少Symfony 3.3 DI功能的功能?

当然,我不想让AParent类成为一个接口,因为我不想重新定义实现这种接口的类的方法。

此外,我不想重复自己,并在孩子们身上复制/粘贴相同的功能。

想法?线索?建议吗?这可能吗?

更新

阅读" How to Manage Common Dependencies with Parent Services"我在我的场景中尝试了以下内容:

CommonBundle\API\AParent:
    abstract: true
    arguments:
        $api_count_url: '%serv_api_base_url%'

CommonBundle\API\AChildren:
    parent: CommonBundle\API\AParent
    arguments:
        $base_url: '%serv_api_base_url%'
        $base_response_url: '%serv_api_base_response_url%' 

但错误变成了:

  

属性" autowire"在服务" CommonBundle \ API \ AChildren"不可能是   继承自" _defaults"当一个父母"已设定。移动你的孩子   定义到单独的文件或明确定义此属性   /var/www/html/oneview_symfony/app/config/services.yml(正在   从" /var/www/html/oneview_symfony/app/config/config.yml")导入。

但是我可以使用以下设置:

CommonBundle\API\AParent:
    arguments:
        $api_count_url: '%serv_api_base_url%'

CommonBundle\API\AChildren:
    arguments:
        $api_count_url: '%serv_api_base_url%'
        $base_url: '%serv_api_base_url%'
        $base_response_url: '%serv_api_base_response_url%'

这是正确的方法吗?这有道理吗?

更新#2

按照@Cerad说明,我做了一些mods(参见上面的代码,看下面的定义),现在对象来了NULL?任何想法为什么会这样?

// services.yml
services:
    CommonBundle\EventListener\EntitySuscriber:
        tags:
            - { name: doctrine.event_subscriber, connection: default}

    CommonBundle\API\AParent:
        abstract: true
        arguments:
            - '@httplug.message_factory'
            - '@httplug.client.myclient'
            - '%ser_api_base_url%'

// services_api.yml
services:
    CommonBundle\API\AChildren:
        parent: CommonBundle\API\AParent
        arguments:
            $base_url: '%serv_api_base_url%'
            $base_response_url: '%serv_api_base_response_url%'

// config.yml
imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: services.yml }
    - { resource: services_api.yml }

为什么对象在子类中是NULL

4 个答案:

答案 0 :(得分:3)

有趣。您似乎希望autowire了解AChild扩展了AParent,然后使用AParent服务定义。我不知道这种行为是故意被忽视还是设计不支持。 autowire仍处于初期阶段,并且正处于高度发达阶段。

我建议前往di github存储库,检查问题,然后打开一个(如果适用)。开发人员会告诉您这是否符合设计要求。

与此同时,如果将子定义移动到其他服务文件,则可以使用父服务功能。它会起作用,因为_defaults东西只适用于当前的服务文件。

# services.yml
AppBundle\Service\AParent:
    abstract: true
    arguments:
        $api_count_url: '%serv_api_base_url%'

# services2.yml NOTE: different file, add to config.yml
AppBundle\Service\AChild:
    parent: AppBundle\Service\AParent
    arguments:
        $base_url: 'base url'

最后一个略微偏离主题的说明:没有必要公开:假,除非你愚弄自动配置的东西。默认情况下,除非您明确声明它们是公共的,否则所有服务都被定义为私有。

更新 - 评论提到有关对象为空的内容。不完全确定这意味着什么,但我去了我的测试类添加了一个记录器。所以:

use Psr\Log\LoggerInterface;

abstract class AParent
{
    protected $api_count_url;

    public function __construct(
        LoggerInterface $logger, 
        string $api_count_url)
    {
        $this->api_count_url = $api_count_url;
    }
}    
class AChild extends AParent
{
    public function __construct(LoggerInterface $logger, 
        string $api_count_url, string $base_url)
    {
        parent::__construct($logger,$api_count_url);
    }

由于只有一个psr7记录器实现,因此记录器自动装配并注入而不更改服务定义。

更新2 我更新到S3.3.8并开始获取:

[Symfony\Component\DependencyInjection\Exception\RuntimeException]                                                                         
Invalid constructor argument 2 for service "AppBundle\Service\AParent": argument 1 must be defined before. Check your service definition.  

Autowire仍处于重大发展阶段。在这一点上不会花费精力来弄清楚原因。与参数的顺序有关。一旦LTS版本发布,我就会复活。

答案 1 :(得分:1)

嗯,我遇到了同样的问题。出于某些原因,我不得不创建一个 Symfony 3.3.17 项目作为开始,因为在我的公司中,我不得不使用仍然捆绑在SF 3.3中的两个捆绑软件(我知道你会说什么关于此问题,但我打算很快将所有这些都升级。

我的问题有点不同,这就是为什么Cerad's solution在我的情况下使用起来有点复杂。但是我也可以(可能)为问题中提到的问题提供解决方案。就我而言,我正在使用Symfony Flex来管理我的应用程序,即使它是SF 3.3。因此,了解flex的人都知道 config.yml 不再存在,而是在应用程序的根目录下有一个 config 文件夹。在其中,您只有一个 services.yaml 文件。因此,如果要添加 services2.yaml 文件,除非将其重命名为 services_2.yaml ,否则将不会检测到该文件。但是在这种情况下,这意味着您拥有另一个名为 2 的环境,例如 dev test 。不好吧?

我从xabbuh's answer in this issue找到了解决方案。

因此,为了能够在Symfony 3.3中的具有DI的抽象类中使用自动装配,您仍然可以将服务定义保存在一个文件中:

CommonBundle\API\AParent:
    abstract: true
    autoconfigure: false
    arguments:
        $api_count_url: '%serv_api_base_url%'

CommonBundle\API\AChildren:
    parent: CommonBundle\API\AParent
    autoconfigure: false
    autowire: true
    public: false
    arguments:
        $base_url: '%serv_api_base_url%'
        $base_response_url: '%serv_api_base_response_url%'

这里的重点是

  

您需要更加明确

如xabbuh所说。如果您使用的是父级,则不能继承 _default 来设置公共自动装配自动配置。您的服务声明中的“ strong>”键。将子服务中的 autoconfigure 值设置为 false 很重要,因为如果未设置,您将拥有

  

服务“ CommonBundle \ API \ AChildren”不能具有“父”,也不能具有“自动配置”。实际上有道理...

最后一件事,如果问题与Symfony Console命令有关(例如我的情况),请不要忘记使用

tags:
    - { name: console.command }

为您的孩子服务,因为自动配置设置为

答案 2 :(得分:0)

我认为答案就在错误信息中,与使用抽象类无关。 您是否以这样的方式启用自动装配,即AChildren将在没有您明确指示的情况下接通电源?如果是这样,您可能需要为每个子类指定构造函数参数。 有关可能的帮助,请参阅https://symfony.com/doc/current/service_container/parent_services.html

答案 3 :(得分:0)

似乎自动装配在Symfony 3.3.8中不适用于抽象服务。我为此创建了an issue on Github

与此同时,我个人删除了abstract选项,它运行正常。