如何将参数传递给 Symfony4 中的自定义 Doctrine 类型



我开发了一种新的Doctrine类型来加密字符串。

<?php
namespace AppDoctrineDBALTypes;
use DoctrineDBALPlatformsAbstractPlatform;
use DoctrineDBALTypesStringType;
use AppSecurityEncoderOpenSslEncoder;
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 SymfonyComponentHttpKernelBundleBundle;
use DoctrineDBALTypesType;
class AppBundle extends Bundle
{
public function __construct()
{
Type::addType('encrypted_string', 'AppBundleDoctrineDBALTypesEncryptedStringType');
}
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应用程序中做同样的事情?我知道我可以在doctrine.yaml配置文件中注册一个新类型。但是我需要设置密码参数...如何在新版本中设置对象参数?

谢谢。

同样,Symfony 4Kernel类有一个具有类似目的的boot()方法,因此您可以肯定地将该代码移动到那里:

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

我不喜欢引导答案,所以我深入研究了教义代码,看看我是否能找到一个与symfony依赖注入系统更集成的解决方案。

这是我带来的:

config/services.yaml

我覆盖了连接工厂类,以及一个用于标记我需要使用注入的服务的接口。

parameters:
doctrine.dbal.connection_factory.class: AppDoctrineBundleConnectionFactory
services:
_instanceof:
AppDoctrineDBALTypesServiceTypeInterface:
tags: [ app.doctrine.dbal.service_type ]

配置/包

标准的自定义类型声明,没有什么特别的。

doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%' 
types:
my_type:  AppDoctrineDBALTypesMyTypeNeedingDependencyInjection
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: 'AppEntity'
alias: App

src/内核.php

编译器传递,用于将类型注册到连接工厂中。您也可以使用单独的编译器传递。

<?php
namespace App;
// Standard Kernel use plus those
use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
use SymfonyComponentDependencyInjectionContainerBuilder;
use SymfonyComponentDependencyInjectionReference;
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 AppDoctrineBundle;
use AppDoctrineDBALTypesServiceTypeInterface;
use DoctrineBundleDoctrineBundleConnectionFactory as BaseFactory;
use DoctrineCommonEventManager;
use DoctrineDBALConfiguration;
use DoctrineDBALTypesType;
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 AppDoctrineDBALTypes;
interface ServiceTypeInterface
{
}

src/Doctrine/DBAL/Types/MyTypeNeedingDependencyInjection.php

<?php
namespace AppDoctrineDBALTypes;
use AppServiceMyService;
use DoctrineDBALTypesType;
use DoctrineDBALPlatformsAbstractPlatform;
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,doctrine 2.7),至少对于我需要的用法,我可以以最小的努力添加更多类型(只需要实现ServiceTypeInterface并使用setter注入),但请注意,这使用内部原则功能,通过反射和使用标记为内部的函数,所以没有向前兼容性发布。

最新更新