Sometime we want to encrypt some data directly in database, using Symfony and doctrine. But we want to do it automatically, i mean we donβt want to encrypt manually the data before save it, and decrypt it, when we get the data.
By creating a new doctrine type we can encrypt and decrypt magically the data. Let see together the steps.
Step 1 : Create a service responsible for encryption and decryption
interface EncryptionEngineInterface
{
public function encrypt(string $data): string;
public function decrypt(string $data): string;
}First of all, using an interface will make our life easier the day we want to change our encryption method.
Let use OpenSsl Encryption implementaion
final class EncryptionOpenSslEngine implements EncryptionEngineInterface
{
public function __construct(
private readonly string $sslCiphering = 'AES-128-CTR',
private readonly int $sslOption = 0,
private readonly string $sslEncryptionIv = '1234567891011121',
private readonly string $sslEncryptionKey = 'EncryptionKey',
) {
}
public function encrypt(string $data): string
{
return openssl_encrypt(
$data,
$this->sslCiphering,
$this->sslEncryptionKey,
$this->sslOption,
$this->sslEncryptionIv
);
}
public function decrypt(string $data): string
{
return openssl_decrypt(
$data,
$this->sslCiphering,
$this->sslEncryptionKey,
$this->sslOption,
$this->sslEncryptionIv
);
}
}β οΈ Dont forget to have openssl php extension installed β οΈ
Step 2 : Create a new doctrine type
use Doctrine\DBAL\Types\StringType;
final class EncryptType extends StringType
{
public const NAME = 'encrypt';
private EncryptionEngineInterface $encryptionEngine;
public function setEncryptionEngine(EncryptionEngineInterface $encryptionEngine): void
{
$this->encryptionEngine = $encryptionEngine;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform): string
{
return $this->encryptionEngine->encrypt($value);
}
public function convertToPHPValue($value, AbstractPlatform $platform): string
{
return $this->encryptionEngine->decrypt($value);
}
public function canRequireSQLConversion(): bool
{
return true;
}
public function getName(): string
{
return self::NAME;
}
}Just override the both method convertToDatabaseValue and convertToPHPValue to encrypt and decrypt data by using EncryptionEngineInterface already created.
Step 3 : Declare your service and doctrine type
In your services.yaml
services:
App\EncryptionEngineInterface:
class: App\EncryptionOpenSslEngine
public: trueAnd in your kernel.php file, override boot method to declare your new doctrine type and inject your encryptionServiceInterface
public function boot()
{
parent::boot();
$this->addEncryptTypeToDoctrine();
}
private function addEncryptTypeToDoctrine(): void
{
if (Type::hasType(EncryptType::NAME) === false) {
/** @var EncryptionEngineInterface $encryptionEngine */
$encryptionEngine = self::getContainer()->get(EncryptionEngineInterface::class);
Type::addType(EncryptType::NAME, EncryptType::class);
/** @var EncryptType $encrypt */
$encrypt = Type::getType(EncryptType::NAME);
$encrypt->setEncryptionEngine($encryptionEngine);
}
}Step 4 : Use your new doctrine type
#[ORM\Entity()]
class Entity
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: EncryptType::NAME)]
private string $secret;
}Then use directly your type in you doctrine column definition, and the secret attribute will be encrypted before insert and decrypted when we get the entity.
Enjoy π, tell me your opinion in comment