Возможно ли автоматическое связывание абстрактных классов с DI в Symfony 3.3?

Я перемещаю проект 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
    {
        // ....
    }
}

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

Конечно, я что-то здесь упускаю или просто это невозможно, что приводит меня к следующему вопросу: это плохой дизайн ООП или недостающая функциональность в функциях 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 в дочернем классе?


person ReynierPM    schedule 25.08.2017    source источник


Ответы (4)


Интересный. Кажется, вы хотите, чтобы autowire понял, что ACChild расширяет AParent, а затем использовал определение службы AParent. Я не знаю, было ли это поведение намеренно упущено из виду или оно не поддерживается дизайном. autowire все еще находится в зачаточном состоянии и активно развивается.

Я бы посоветовал зайти в репозиторий digithub, проверить проблемы, а затем открыть их, если это применимо. Разработчики сообщат вам, задумано это или нет.

Тем временем вы можете использовать функциональные возможности родительской службы, если переместите дочернее определение в другой файл службы. Это будет работать, потому что материал _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.  

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

person Cerad    schedule 25.08.2017
comment
Да, я только сейчас понял, что все закрыто по замыслу! : D Я удалил его из OP, чтобы избежать путаницы. Я предполагаю, что во втором файле мне нужно определить дополнительные параметры, верно? - person ReynierPM; 25.08.2017
comment
Ага. Вы можете оставить api_count_url в родительском определении и просто добавить дополнительные аргументы в дочернее определение. Все действительно резко изменилось с тех пор, как Роулинг присоединилась к команде Symfony. - person Cerad; 25.08.2017
comment
Вы можете взглянуть на мой 2-й upd? Я открыл проблему в Symfony BTW (не нашел репо для DI) - person ReynierPM; 27.08.2017
comment
Возможно, пришло время начать новый вопрос. В частности, покажите конструктор вашего дочернего объекта. Остальные вещи не имеют значения. - person Cerad; 27.08.2017
comment
См. здесь он говорит, что мне нужно явно определить службы, но я не следую за ним, не могли бы вы улучшить свой ответ? - person ReynierPM; 27.08.2017

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

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

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

Итак, чтобы иметь возможность использовать автосвязывание в абстрактных классах с 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, чтобы установить public, autowire и autoconfigure, если вы используете parent. strong> ключ в объявлении службы. Установка значения autoconfigure в вашей дочерней службе на false важна, потому что, если она не установлена, у вас будет

Служба «CommonBundle\API\AChildren» не может иметь «родителя», а также иметь «автоконфигурацию». На самом деле смысл...

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

tags:
    - { name: console.command }

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

person Franck Gamess    schedule 24.07.2018

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

person beltouche    schedule 25.08.2017
comment
Я не думаю, что это полностью верно: вам, вероятно, нужно указать этот аргумент конструктора для каждого дочернего класса в соответствии с документами, имеющими родительскую службу, подразумевает, что аргументы и вызовы методов родительской службы должны использоваться для дочерних служб - person ReynierPM; 25.08.2017

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

А пока лично я удаляю опцию abstract и все работает нормально.

person Fabien Salles    schedule 13.09.2017