Как передать параметры пользовательскому типу Doctrine в Symfony4

Я разработал новый тип Doctrine для шифрования строк.

<?php
namespace App\Doctrine\DBAL\Types;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;
use App\Security\Encoder\OpenSslEncoder;

class EncryptedStringType extends StringType {

    const MTYPE = 'encrypted_string';

    private $cypherMethod;
    private $iv;
    private $privateKey;

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        $openSslEncoder = new OpenSslEncoder($this->cypherMethod, $this->iv, $this->privateKey);

        return $openSslEncoder->decrypt($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        $openSslEncoder = new OpenSslEncoder($this->cypherMethod, $this->iv, $this->privateKey);

        return $openSslEncoder->encrypt($value);
    }

    public function getName()
    {
        return self::MTYPE;
    }

    /**
     * @param mixed $cypherMethod
     */
    public function setCypherMethod($cypherMethod)
    {
        $this->cypherMethod = $cypherMethod;
    }

    /**
     * @param mixed $iv
     */
    public function setIv($iv)
    {
        $this->iv = $iv;
    }

    /**
     * @param mixed $privateKey
     */
    public function setPrivateKey($privateKey)
    {
        $this->privateKey = $privateKey;
    }
} 

В старых приложениях Symfony3 я регистрировал новый тип следующим образом:

<?php

namespace AppBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Doctrine\DBAL\Types\Type;

class AppBundle extends Bundle
{
    public function __construct()
    {
        Type::addType('encrypted_string', 'AppBundle\Doctrine\DBAL\Types\EncryptedStringType');
    }

    public function boot()
    {
        $encryptedString = Type::getType('encrypted_string');
        $encryptedString->setCypherMethod($this->container->getParameter('open_ssl_cypher_method'));
        $encryptedString->setIv($this->container->getParameter('open_ssl_iv'));
        $encryptedString->setPrivateKey($this->container->getParameter('open_ssl_private_key'));
    }
}

Как я могу сделать то же самое в новых приложениях Symfony4? Я знаю, что могу зарегистрировать новый тип в конфигурационном файле доктрины.yaml. Но мне нужно установить параметры шифра... Как я могу установить параметры объекта в новой версии?

Большое спасибо.


person Gianluca78    schedule 08.01.2018    source источник


Ответы (2)


Точно так же Symfony 4 Kernel класс имеет метод boot() с аналогичной целью, поэтому вы можете точно переместить туда этот код:

// src/Kernel.php

class Kernel extends BaseKernel
{   
    // ...

    public function boot()
    {
        parent::boot();

        // move here.
    }

// ...
person yceruto    schedule 08.01.2018
comment
Я пробую это, и он отлично работает при первом запуске приложения. После этого он выдает исключение, что тип не существует и его нужно добавить? Как-то теряется при генерации кеша? - person Nin; 15.04.2018
comment
Пожалуйста, откройте новый вопрос с подробностями. - person yceruto; 15.04.2018
comment
Я сделал: stackoverflow.com/questions/49839886 / - person Nin; 15.04.2018

Мне не понравился ответ на загрузку, поэтому я копаюсь в коде доктрины, чтобы посмотреть, смогу ли я найти решение, более интегрированное с системой внедрения зависимостей 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