Мне не понравился ответ на загрузку, поэтому я копаюсь в коде доктрины, чтобы посмотреть, смогу ли я найти решение, более интегрированное с системой внедрения зависимостей symfony.
Вот с чем я пришел:
конфиг/services.yaml
Я переопределяю класс фабрики соединений и интерфейс для пометки сервисов, с которыми мне нужно использовать инъекцию.
parameters:
doctrine.dbal.connection_factory.class: App\Doctrine\Bundle\ConnectionFactory
services:
_instanceof:
App\Doctrine\DBAL\Types\ServiceTypeInterface:
tags: [ app.doctrine.dbal.service_type ]
конфиг/пакеты
Стандартное объявление пользовательского типа, ничего особенного.
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
types:
my_type: App\Doctrine\DBAL\Types\MyTypeNeedingDependencyInjection
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
источник/ядро.php
Передача компилятора для регистрации типов в фабрике соединений. Вы также можете использовать отдельный проход компилятора.
<?php
namespace App;
// Standard Kernel use plus those
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition(Breadcrumb::class);
$tag = 'app.breadcrumb.title_provider';
foreach ($this->findAndSortTaggedServices($tag, $container) as $ref) {
$definition->addMethodCall('addTitleProvider', [$ref]);
}
$definition = $container->getDefinition('doctrine.dbal.connection_factory');
foreach ($container->findTaggedServiceIds('app.doctrine.dbal.service_type') as $id => $_) {
$definition->addMethodCall('registerServiceType', [new Reference($id)]);
}
}
// Standard Kernel code goes there
}
Я проверяю, есть ли сервис, соответствующий классу типа, и если да, то использую сервис через реестр типов вместо регистрации имени класса.
src/Doctrine/Bundle/ConnectionFactory.php
<?php
namespace App\Doctrine\Bundle;
use App\Doctrine\DBAL\Types\ServiceTypeInterface;
use Doctrine\Bundle\DoctrineBundle\ConnectionFactory as BaseFactory;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Types\Type;
class ConnectionFactory extends BaseFactory
{
protected $serviceTypes;
public function registerServiceType(ServiceTypeInterface $serviceType)
{
$this->serviceTypes[get_class($serviceType)] = $serviceType;
}
public function createConnection(
array $params,
Configuration $config = null,
EventManager $eventManager = null,
array $mappingTypes = []
)
{
$reflect = new \ReflectionProperty(BaseFactory::class, 'initialized');
$reflect->setAccessible(true);
if (!$reflect->getValue($this)) {
$typesReflect = new \ReflectionProperty(BaseFactory::class, 'typesConfig');
$typesReflect->setAccessible(true);
foreach ($typesReflect->getValue($this) as $typeName => $typeConfig) {
if (is_a($typeConfig['class'], ServiceTypeInterface::class, true)) {
$registry = Type::getTypeRegistry();
if ($registry->has($typeName)) {
$registry->override($typeName, $this->serviceTypes[$typeConfig['class']]);
}
else {
$registry->register($typeName, $this->serviceTypes[$typeConfig['class']]);
}
}
elseif (Type::hasType($typeName)) {
Type::overrideType($typeName, $typeConfig['class']);
}
else {
Type::addType($typeName, $typeConfig['class']);
}
}
$reflect->setValue($this, true);
}
return parent::createConnection($params, $config, $eventManager, $mappingTypes);
}
}
src/App/Doctrine/DBAL/Types/ServiceTypeInterface.php
<?php
namespace App\Doctrine\DBAL\Types;
interface ServiceTypeInterface
{
}
src/Doctrine/DBAL/Types/MyTypeNeedingDependencyInjection.php
<?php
namespace App\Doctrine\DBAL\Types;
use App\Service\MyService;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class MyTypeNeedingDependencyInjection extends Type implements ServiceTypeInterface
{
protected $myService;
/**
* We have to use setter injection since parent Type class make the constructor final
* @required
*/
public function setService(MyService $myService)
{
$this->myService = $myService;
}
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
{
return $platform->getVarcharTypeDeclarationSQL($column);
}
public function getDefaultLength(AbstractPlatform $platform)
{
return 16;
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $this->myService->toPHP($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return $this->myService->fromPHP($value);
}
public function getName()
{
return 'my_type';
}
}
Комментарий
Это работает очень хорошо (symfony 5.1, доктрина 2.7), по крайней мере, для использования, которое мне нужно, и я могу добавить больше типов с минимальными усилиями (просто нужно реализовать ServiceTypeInterface и использовать инъекцию сеттера), но обратите внимание, что это использует внутренние функции доктрины , как за счет отражения, так и за счет использования функции, помеченной как внутренняя, поэтому выпуска с прямой совместимостью не существует.
person
Lulhum
schedule
09.11.2020