Skip to content

Instantly share code, notes, and snippets.

@dbu
Last active January 12, 2026 16:16
Show Gist options
  • Select an option

  • Save dbu/4faa29a7d556e083688092b66a77f6c4 to your computer and use it in GitHub Desktop.

Select an option

Save dbu/4faa29a7d556e083688092b66a77f6c4 to your computer and use it in GitHub Desktop.
encrypting doctrine fields
<?php
namespace App\EventListener;
use App\Doctrine\Encryption\EncryptingEntityInterface;
use App\Doctrine\Encryption\EncryptorInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Events;
/**
* Doctrine event subscriber to encrypt/decrypt entity properties.
*
* Inspired from https://github.com/michaeldegroot/DoctrineEncryptBundle
*
* Instead of using an annotation, we programmatically encrypted/decrypted field relations.
*/
class DoctrineEncryptListener implements EventSubscriber
{
/**
* @var EncryptorInterface
*/
private $encryptor;
public function __construct(EncryptorInterface $encryptor)
{
$this->encryptor = $encryptor;
}
public function getSubscribedEvents(): array
{
return [
Events::postLoad,
Events::preFlush,
];
}
/**
* After loading an entity, populate the decrypted properties by decrypting the encrypted tracked property.
*
* Track the entity so that we also update fields in preFlush.
*/
public function postLoad(LifecycleEventArgs $args): void
{
$entity = $args->getEntity();
if (!$entity instanceof EncryptingEntityInterface) {
return;
}
$entity->decrypt($this->encryptor);
}
/**
* Encrypt properties of entities that are newly inserted into the database or that have previously been loaded from the database.
*/
public function preFlush(PreFlushEventArgs $preFlushEventArgs): void
{
// encrypt entities that we are about to insert
$unitOfWork = $preFlushEventArgs->getEntityManager()->getUnitOfWork();
foreach ($unitOfWork->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof EncryptingEntityInterface) {
$entity->encrypt($this->encryptor);
}
}
}
}
<?php
namespace App\Doctrine\Encryption;
/**
* Interface for entities that encrypt/decrypt some of their properties.
*
* Recommended implementation:
* - field: Decrypted, exposed with getter/setter, NOT mapped to Doctrine.
* - encryptedField: Encrypted, not exposed, mapped to Doctrine.
*
* Optionally, use `decryptedField` to copy the value of the field on decryption, to decide
* during encryption whether encryptedField needs to be updated, as the encryption can be resource intensive.
*/
interface EncryptingEntityInterface
{
public function encrypt(EncryptorInterface $encryptor): void;
public function decrypt(EncryptorInterface $encryptor): void;
}
<?php
namespace App\Entity;
use App\Doctrine\Encryption\EncryptingEntityInterface;
use App\Doctrine\Encryption\EncryptorInterface; // https://github.com/michaeldegroot/DoctrineEncryptBundle/blob/master/Encryptors/EncryptorInterface.php
use Doctrine\ORM\Mapping as ORM;
class SampleEntity implements EncryptingEntityInterface
{
/**
* The IBAN of this entity.
*
* @var string
*/
private $bankAccountNumber;
/**
* The decrypted IBAN value when the entity has been loaded from the database, to know if the IBAN changed.
*
* @var string
*/
private $decryptedBankAccountNumber;
/**
* Encrypted field for $bankAccountNumber
*
* @var string
*
* @ORM\Column(type="string", name="bank_account_number")
*/
private $encryptedBankAccountNumber;
public function encrypt(EncryptorInterface $encryptor): void
{
if ($this->decryptedBankAccountNumber !== $this->bankAccountNumber) {
$this->decryptedBankAccountNumber = $this->bankAccountNumber;
$this->encryptedBankAccountNumber = $encryptor->encrypt($this->decryptedBankAccountNumber);
}
// continue if you have more encrypted fields
}
public function decrypt(EncryptorInterface $encryptor): void
{
$this->bankAccountNumber = $this->decryptedBankAccountNumber = $encryptor->decrypt($this->encryptedBankAccountNumber);
// continue if you have more encrypted fields
}
}
@dbu
Copy link
Copy Markdown
Author

dbu commented Apr 29, 2020

looking at absolute-quantum/DoctrineEncryptBundle#35, we should also listen to onFlush, as preFlush is missing cascading inserts and would lead to not explicitly persisted but cascading inserts not be encrypted.

@maluramichael
Copy link
Copy Markdown

Thanks @dbu you helped me a lot with your code here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment