Автоматическая проводка в абстрактных классах с DI в Symfony 3.3, возможно ли это?


Я переношу проект Symfony 3.2 в Symfony 3.3 и хотел бы использовать Новые функции . Я прочитал документы , но пока я могу заставить это работать. Смотрите следующее определение класса:

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" метода "__построить()" должен иметь подсказку типа или быть явно задано значение.

Конечно, я что-то здесь упускаю или просто это невозможно, что приводит меня к следующему вопросу: это плохой дизайн ООП или это недостающая функциональность функций Symfony 3.3 DI?

Конечно, я не хочу делать класс AParent интерфейсом, так как я не хочу переопределять методы в классах, реализующих такой интерфейс.

Также я не хочу повторяться и копировать/вставлять одни и те же функции у всех детей.

Идеи? Подсказки? Совет? Возможно ли это?

ОБНОВЛЕНИЕ

После прочтения " Как управлять общими зависимостями с родительскими службами " Я попробовал следующее в своем сценарии:

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, я сделал несколько модов (см. Код выше и см. Определение ниже), и теперь объекты идешь 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 находятся в дочернем классе?

Author: ReynierPM, 2017-08-25

4 answers

Интересно. Похоже, вы хотите, чтобы 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'

И еще одно последнее замечание, немного не по теме: Нет необходимости в 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);
    }

И поскольку существует только одна реализация регистратора 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.  

Автоматическая проводка все еще находится в стадии интенсивной разработки. Не собираюсь тратить усилия на данный момент, чтобы выяснить, почему. Что-то связанное с порядком аргументы. Я пересмотрю, как только будет выпущена версия LTS.

 3
Author: Cerad, 2017-08-29 12:57:38

Ну, я прошел через ту же проблему. По некоторым причинам мне пришлось создать проект Symfony 3.3.17 для запуска, потому что в моей компании мне пришлось использовать 2 наших пакета, которые все еще застряли в SF 3.3 (я знаю, что вы скажете об этом, но я намерен обновить их все в ближайшее время).

Моя проблема была немного другой, поэтому Решение Cerad было немного сложным в использовании в моем случае. Но я могу принести (возможно) также решение этой проблемы упомянуто в вопросе. В моем случае я использую Symfony Flex для управления своим приложением, даже если это SF 3.3. Итак, те, кто знает flex, знают, что config.yml больше не существует, и вместо этого в корне приложения есть папка config. Внутри него у вас просто есть файл services.yaml. Итак, если вы хотите добавить файл services2.yaml, вы увидите, что он не будет обнаружен, если вы не переименуете его в services_2.yaml. Но в этом случай, это означает, что у вас есть другая среда, называемая 2 например, dev или тест. Нехорошо, верно?

Я нашел решение из ответа ксаббуха в этом вопросе.

Итак, чтобы иметь возможность использовать автопроводку в абстрактных классах с DI в Symfony 3.3, вы все равно можете сохранить определение своих сервисов в одном файле:

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%'

Дело здесь в том, что

Вам нужно быть немного более откровенным

Как сказал ксаббух. Вы не можете наследовать от _default, чтобы установить общедоступным, автоматическое подключение и автонастройка, если вы используете родительский ключ в объявлении службы. Установка автонастройки значения в дочерней службе на false важна, потому что, если оно не установлено, у вас будет

Служба "CommonBundle\API\ACHILDREN" не может иметь "родителя", а также "автоконфигурировать". На самом деле это имеет смысл...

И последнее, если проблема была связана с консольной командой Symfony (как в моем случае), не забудьте использовать

tags:
    - { name: console.command }

Для вашей дочерней службы, потому что автоконфигурация имеет значение false.

 1
Author: Franck Gamess, 2018-07-24 11:51:11

Я думаю, что ответ находится прямо в сообщении об ошибке и не имеет ничего общего с использованием абстрактного класса. Есть ли у вас автоматическое подключение, настроенное таким образом, чтобы дети могли подключаться без ваших четких инструкций? Если это так, вам, вероятно, нужно указать этот аргумент конструктора для каждого дочернего класса. См. https://symfony.com/doc/current/service_container/parent_services.html для возможной помощи

 0
Author: beltouche, 2017-08-25 19:31:58

Похоже, что автозапуск не работает с абстрактными сервисами в Symfony 3.3.8. Для этого я создаю проблему на Github.

Тем временем лично я удаляю опцию abstract, и она отлично работает.

 0
Author: Fabien Salles, 2017-09-13 09:25:02