Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Encrypt Laravel Package

51systems/doctrine-encrypt

Transparent field-level encryption for Doctrine entities in Laravel. Custom DBAL types and event subscribers encrypt sensitive columns at rest, keeping normal ORM reads and writes seamless while protecting stored data.

Deep Wiki
Context7

Getting Started

This package is a small Doctrine DBAL type extension: you register an "encrypted" column type, point it at a key/cipher, and then mark entity fields with that type. Doctrine handles encryption on write and decryption on read transparently.

Minimal setup:

  1. Install (or vendor it — see the archive warning below):
    composer require 51systems/doctrine-encrypt
    
  2. Find the DBAL type class first. Look in src/ for the class extending Doctrine\DBAL\Types\Type. That is the whole package: convertToDatabaseValue() encrypts, convertToPHPValue() decrypts. Read those two methods before trusting anything else — with a package this small and this old, the source is the documentation.
  3. Find the config surface. Look for a Symfony bundle class or a small factory that accepts a key and cipher/algorithm. Most packages like this expose a single key + algorithm, sometimes an IV strategy.
  4. Register the type in your Doctrine DBAL config (or Type::addType() in a service provider):
    doctrine:
      dbal:
        types:
          encrypted: App\Doctrine\EncryptedType
    
  5. Key from environment, never inline:
    APP_ENCRYPTION_KEY=base64:...
    

First use case: pick exactly one sensitive, low-query column — a notes or api_token field — and change its @Column(type="encrypted"). Persist something, read it back, then check the raw row in the database to confirm ciphertext on disk and plaintext in PHP. That round-trip is your smoke test.

Implementation Patterns

Column-level, not entity-level. Apply the type to the smallest set of fields that need it: tokens, PII, free-text notes. Each encrypted column is a read/write cost and a query liability, so be deliberate.

Transparent to the ORM layer. Once the type is registered, your normal entityManager->persist() / find() flow just works. The cipher is applied in the DBAL layer during parameter binding and result hydration, so nothing in your controllers or repositories changes.

Key sourcing pattern. Read the key once at container boot from env or a secrets manager and inject it into the type. Keep per-environment keys separate; a shared key between local and prod is a footgun. If the package allows it, derive per-column keys from the master key so a single leaked column key doesn't expose the rest.

Blind index for lookups. Encrypted columns can't be WHERE'd on directly. When you need equality lookup (e.g., "find user by SSN"), add a companion ssn_hash column populated in a lifecycle callback or setter with hash_hmac('sha256', $value, $macKey) and query on that. Never use a plain hash for low-entropy values like SSNs — always HMAC with a secret.

Migration from plaintext. Do it in three deploys: (1) add the encrypted column and dual-write, (2) backfill via a console command that re-saves each entity, (3) drop the plaintext column with a Doctrine migration. Decrypt-on-read code can bridge the gap temporarily.

Testing. Put a throwaway key in phpunit.xml or .env.test. Fixtures written through the entity manager are automatically encrypted; fixtures loaded with raw SQL are not — keep those two paths separate or your round-trip tests will lie.

Serialization. Decrypted values flow into API responses and logs by default. Wrap sensitive fields in your serializer's groups and be explicit about what leaves the app.

Gotchas and Tips

Archived since 2017 — treat it as source to vendor, not a dependency to trust. Pin your PHP and Doctrine DBAL versions, or better, copy the type class into your own namespace, audit it (it's tiny), and own it. DBAL type signatures changed across 2.x/3.x; an unmaintained type may simply fail to register or hydrate on modern Doctrine.

Crypto review is mandatory. Check: which cipher (AES-CBC/CTR vs. AES-ECB — ECB is broken), how the IV is generated and stored, whether a MAC/authenticated mode is used, and whether mcrypt is used anywhere (removed in PHP 7.2). If it's plain CBC without a MAC, you have malleability issues and should wrap it in an authenticated scheme yourself.

Deterministic vs. randomized. If the implementation uses a fixed IV for a given plaintext, identical values produce identical ciphertext — that's what makes lookups possible but leaks equality. Know which mode you're in and document it.

Key rotation is not built in. Changing the key means decrypting with the old one and re-encrypting with the new across every row. Plan a rotation command and dual-key read path early, because retrofitting one after the data grows is painful.

Query and schema limits. No ORDER BY, GROUP BY, LIKE, or joins on encrypted columns. Range queries are impossible. Type maps to TEXT/BLOB under the hood — set the column length on the plaintext expectation, not the ciphertext, or you'll get silent truncation depending on the implementation.

Nulls and empty strings. Verify what convertToDatabaseValue(null) does — some types happily encrypt '' into a non-empty blob, breaking your IS NULL assumptions. Test both explicitly.

Debugging checklist when reads return garbage or throw:

  • "Unknown column type 'encrypted'" → type not registered in this connection/process (CLI vs. web).
  • Decryption error on read → key changed, wrong key in that environment, or rows written before the encrypted type was applied.
  • Plaintext in the DB → you wrote through raw SQL, a bulk update, or a migration that bypassed the DBAL type.

Extension points. The type class is the natural seam: subclass it to change the cipher, add a MAC, or swap the key provider to a Vault/KMS-backed reader. Keep the key provider injectable so you can test encryption without real secrets.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle