1

I have developed a new Doctrine type to encrypt strings.

<?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; } } 

In the old Symfony3 applications, I registered the new type in the following way:

<?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')); } } 

How I can do the same in the new Symfony4 applications? I know that I can register a new type in the doctrine.yaml config file. But I need to set the cypher parameters... How I can set the object parameters in the new version?

Thank you very much.

2 Answers 2

1

In the same way, Symfony 4 Kernel class has a boot() method with similar purpose, so you can move that code there for sure:

// src/Kernel.php class Kernel extends BaseKernel { // ... public function boot() { parent::boot(); // move here. } // ... 
Sign up to request clarification or add additional context in comments.

3 Comments

I'm trying this and it runs perfect the first time I run the app. After that it throws an exception that the type does not exist and has to be added? Somehow it is lost when the cache is generated?
Please, open a new question with details.
1

I did not like the boot answer, so I dig into doctrine code to see if I could found a solution more integrated with symfony dependency injection system.

This is what I came with:

config/services.yaml

I override the connection factory class, and a an interface to tag the services I need to use injection with.

parameters: doctrine.dbal.connection_factory.class: App\Doctrine\Bundle\ConnectionFactory services: _instanceof: App\Doctrine\DBAL\Types\ServiceTypeInterface: tags: [ app.doctrine.dbal.service_type ] 

config/packages

Standard custom type declaration, nothing special there.

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

Compiler pass to register the types into the connection factory. You could use a separate compiler pass as well.

<?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 } 

I check if there is a service corresponding to the type class, and if so I use the service through the type registry instead of registering the class name.

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'; } } 

Comment

This work pretty well (symfony 5.1, doctrine 2.7), at least for the usage I needed, and I can add more types with a minimal effort (just need to implements ServiceTypeInterface and use setter injection), but note that this use internal doctrine functionnalities, both through reflection and usage of function annoted as internal, so there is not forward compatibility release.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.