coka/doctrine-encrypt-bundle
Installation
composer require coka/doctrine-encrypt-bundle
Add to config/bundles.php (Symfony) or config/packages/doctrine.yaml (Laravel via Symfony components):
CedrickOka\DoctrineEncryptBundle\CedrickOkaDoctrineEncryptBundle::class => ['all' => true],
Configuration
Define encryption keys in .env:
DOCTRINE_ENCRYPT_KEY=your_secure_256bit_key_here
DOCTRINE_ENCRYPT_IV=your_16byte_iv_here
(Or configure via config/packages/doctrine_encrypt.yaml if using Symfony.)
First Use Case Encrypt a field in an entity:
use Doctrine\ORM\Mapping as ORM;
use CedrickOka\DoctrineEncryptBundle\Type\EncryptedType;
#[ORM\Entity]
class User {
#[ORM\Column(type: EncryptedType::NAME)]
private $creditCardNumber;
}
The bundle automatically handles encryption/decryption on read/write.
Field-Level Encryption
Use EncryptedType for sensitive fields (e.g., password, ssn, api_keys):
#[ORM\Column(type: EncryptedType::NAME, options: ["encrypted" => true])]
private $apiKey;
Custom Encryption Logic Extend the base type for custom algorithms:
use CedrickOka\DoctrineEncryptBundle\Type\AbstractEncryptedType;
class CustomEncryptedType extends AbstractEncryptedType {
protected function encrypt($value): string {
return openssl_encrypt($value, 'aes-256-cbc', $this->key, 0, $this->iv);
}
}
Register in services.yaml:
services:
App\Type\CustomEncryptedType:
tags: [doctrine.type]
Querying Encrypted Fields Avoid direct queries on encrypted fields. Use application logic or store hashed values separately:
// Bad: Querying encrypted field directly
$user = $entityManager->createQueryBuilder()
->where('u.creditCardNumber = :cc')
->getQuery()
->getOneOrNullResult();
// Good: Use a hashed column for comparisons
#[ORM\Column(type: 'string', length: 64)]
private $creditCardHash;
Bulk Operations For mass updates, disable encryption temporarily (use with caution):
$entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
// Perform bulk operations
$entityManager->flush();
doctrine/orm or laravel-doctrine/orm for hybrid projects.TEXT in migrations. Document this in schema updates.$this->mock(EncryptionService::class)
->shouldReceive('encrypt')
->andReturn('encrypted_value');
Key Management
.env is insecure for production. Use a secrets manager (e.g., AWS KMS, HashiCorp Vault).Performance Overhead
email for login).Doctrine Events
prePersist, preUpdate, and preFlush events. Override these carefully:
#[ORM\PrePersist]
public function prePersist() {
// Avoid double-encryption if using lifecycle callbacks
}
Database Compatibility
Serialization Issues
@Groups (API Platform) or manual serialization:
#[Groups(['api'])]
public function getMaskedCard(): string {
return '****-' . substr($this->creditCardNumber, -4);
}
Corrupted Data: If decryption fails, check:
.env.dd($entityManager->getConnection()->getDatabasePlatform()) to verify platform support.Logs: Enable Doctrine logging to trace encryption:
doctrine:
dbal:
logging: true
profiling: true
Custom Types
Extend AbstractEncryptedType for non-OpenSSL encryption (e.g., sodium_crypto_secretbox):
class SodiumEncryptedType extends AbstractEncryptedType {
protected function encrypt($value): string {
return sodium_crypto_secretbox($value, random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES), $this->key);
}
}
Field-Level Configuration Override encryption per field via YAML:
App\Entity\User:
properties:
creditCardNumber:
type: encrypted
options:
algorithm: aes-128-cbc
key: custom_key_here
Event Subscribers
Listen to preEncrypt/postDecrypt events for auditing:
#[AsEventListener(event: 'preEncrypt', method: 'onPreEncrypt')]
public function onPreEncrypt(PreEncryptEventArgs $args) {
// Log sensitive data access
}
EncryptedType with nullable: true for optional fields.doctrine/extensions for GDPR right-to-erasure support:
$entityManager->createQueryBuilder()
->update(User::class, 'u')
->set('u.creditCardNumber', ':empty')
->where('u.id = :id')
->getQuery()
->execute();
How can I help you explore Laravel packages today?