【问题标题】:How to pass parameters to a custom Doctrine type in Symfony4如何将参数传递给 Symfony4 中的自定义 Doctrine 类型
【发布时间】:2018-01-08 16:32:33
【问题描述】:

我开发了一种新的 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 配置文件中注册一个新类型。但是我需要设置密码参数...如何在新版本中设置对象参数?

非常感谢。

【问题讨论】:

    标签: symfony doctrine-orm symfony4


    【解决方案1】:

    以同样的方式,Symfony 4 Kernel class has a boot() method 具有相似的目的,因此您可以肯定地将代码移到那里:

    // src/Kernel.php
    
    class Kernel extends BaseKernel
    {   
        // ...
    
        public function boot()
        {
            parent::boot();
    
            // move here.
        }
    
    // ...
    

    【讨论】:

    • 我正在尝试这个,它在我第一次运行应用程序时运行完美。之后它会抛出类型不存在并且必须添加的异常?生成缓存时不知何故丢失了?
    • 请打开一个包含详细信息的新问题。
    【解决方案2】:

    我不喜欢启动答案,所以我深入研究了学说代码,看看是否可以找到与 symfony 依赖注入系统更集成的解决方案。

    这是我带来的:

    config/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
    

    src/Kernel.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 并使用 setter 注入),但请注意,这个使用内部学说功能,通过反射和使用注释为内部的功能,因此没有向前兼容的版本。

    【讨论】:

      猜你喜欢
      • 2018-09-25
      • 1970-01-01
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-06
      • 2021-08-18
      • 1970-01-01
      相关资源
      最近更新 更多