Автоматическая проводка в абстрактных классах с 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
находятся в дочернем классе?
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.
Ну, я прошел через ту же проблему. По некоторым причинам мне пришлось создать проект 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.
Я думаю, что ответ находится прямо в сообщении об ошибке и не имеет ничего общего с использованием абстрактного класса. Есть ли у вас автоматическое подключение, настроенное таким образом, чтобы дети могли подключаться без ваших четких инструкций? Если это так, вам, вероятно, нужно указать этот аргумент конструктора для каждого дочернего класса. См. https://symfony.com/doc/current/service_container/parent_services.html для возможной помощи
Похоже, что автозапуск не работает с абстрактными сервисами в Symfony 3.3.8. Для этого я создаю проблему на Github.
Тем временем лично я удаляю опцию abstract
, и она отлично работает.