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

Rey*_*rPM 5 php dependency-injection symfony symfony-3.3 symfony3.x

我正在将Symfony 3.2项目移植到Symfony 3.3,我想使用DI新功能.我已经阅读了文档,但到目前为止我可以使这个工作.请参阅以下类定义:

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
    {
        // ....
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的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%'
Run Code Online (Sandbox Code Playgroud)

但是我收到以下错误:

AutowiringFailedException无法自动装配服务"CommonBundle\API\AChildren":方法"__construct()"的参数"$ api_count_url"必须具有类型提示或显式赋予值.

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

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

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

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

UPDATE

阅读" 如何使用父服务管理常见依赖关系 "后,我在我的方案中尝试了以下内容:

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%' 
Run Code Online (Sandbox Code Playgroud)

但错误变成:

当设置"父"时,服务"CommonBundle\API\AChildren"上的属性"autowire"不能从"_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%'
Run Code Online (Sandbox Code Playgroud)

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

更新#2

按照@Cerad说明我做了几个mod(参见上面的代码,看下面的定义),现在对象来了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 }
Run Code Online (Sandbox Code Playgroud)

为什么对象NULL在子类中?

Cer*_*rad 4

有趣的。看来您希望自动装配了解 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'
Run Code Online (Sandbox Code Playgroud)

最后一个稍微离题的注释:没有必要 public: false 除非你胡闹自动配置的东西。默认情况下,所有服务都被定义为私有的,除非您明确声明它们是公共的。

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

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);
    }
Run Code Online (Sandbox Code Playgroud)

由于只有一种 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.  
Run Code Online (Sandbox Code Playgroud)

Autowire 仍在大力开发中。此时不会花精力去找出原因。与参数的顺序有关。LTS 版本发布后我会再次访问。