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

philetaylor/doctrine-encrypt

Transparent field-level encryption for Doctrine entities in Laravel, letting you store sensitive columns encrypted at rest and decrypt them automatically through a custom DBAL type.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the README banner before anything else: it says WIP DO NOT USE, and the package's own metadata backs that up — 1 star, 0 dependents, and no release since 2022-01-03. Treat this repository as a reading/reference artifact, not a dependency you composer require into a Laravel app. If you're evaluating it, that is the first use case: audit the source to understand how annotation-driven Doctrine field encryption is attempted, then decide whether to borrow the pattern rather than the package.

Minimal steps to explore it:

  1. Open composer.json first — check PHP and doctrine/orm / doctrine/dbal constraints. A 2022 package will likely pin Doctrine versions incompatible with a current laravel-doctrine/orm install, so expect conflict errors before any code runs.
  2. Find where it hooks Doctrine — look for a DBAL custom type (doctrine.types), an event subscriber (postLoad, prePersist, onFlush), or both. This tells you the whole design and whether encrypted columns are queryable.
  3. Find the key/cipher config — grep for openssl_encrypt, sodium_crypto, aes-256, config. Note whether the key comes from a config file, env, or is hardcoded in tests.
  4. Run the package's own tests in an isolated clone before touching a Laravel app. If the suite doesn't pass on its own, stop.

First realistic use case (in a throwaway branch, never main): encrypt one non-searchable PII column — ssn, national_id, a stored API secret — on a single Doctrine entity, verify the raw DB row is ciphertext, and verify a round-trip through the EntityManager. Do not put it in an auth or token path.

If you actually need this capability in production, start from Laravel's built-in Crypt with a custom Eloquent cast, or ambta/doctrine-encrypt-bundle for Doctrine — both maintained and audited. Use this repo to understand the tradeoffs, not to ship.

Implementation Patterns

Because you should not run this on a real app, the useful pattern is extracting the idea into your own code.

Application-layer encryption with a cast (recommended). Encrypt in a mutator/cast before save(), decrypt on attribute access. Keeps the ciphertext out of the query layer entirely, so WHERE, LIKE, and ORDER BY on those columns are simply impossible by design — which is the correct mental model anyway.

// app/Casts/Encrypted.php  (sketch)
protected function set($model, $key, $value, $attributes) {
    return [$key => $value === null ? null : Crypt::encryptString(serialize($value))];
}
protected function get($model, $key, $value, $attributes) {
    return $value === null ? null : unserialize(Crypt::decryptString($value));
}

Split write and read paths. Encrypt immediately before persist; decrypt immediately after hydrate. Never leave a plaintext attribute sitting on the model longer than the request needs it.

Key rotation from day one. Store a key_id alongside the ciphertext (a second column), so rotation is "decrypt with old, re-encrypt with new, write both" rather than a full-table guess. Packages of this vintage almost never ship rotation.

Dual-write migration pattern (if replacing this package or moving from plaintext): add column_enc → dual-write plaintext and encrypted on every write → backfill existing rows in batches → switch reads to encrypted with plaintext fallback → drop plaintext once metrics confirm zero fallback hits. This is the only safe way to move a live PII column.

Testing pattern. Pin a fixed key in phpunit.xml, then assert two things per test: (a) the model attribute returns the plaintext, and (b) DB::table('users')->where('id',$id)->value('ssn') is not the plaintext. If your test only checks (a), you can pass with a broken cast that silently stores plaintext.

Blind indexes for lookup. If you must query an encrypted column (e.g., find by email), don't reach for deterministic encryption alone — store a keyed HMAC (hash_hmac('sha256', strtolower($email), $indexKey)) in a separate email_index column with a unique constraint, and keep the ciphertext non-deterministic. This is the pattern any serious implementation converges on (CipherSweet does exactly this).

Gotchas and Tips

  • The banner is the headline. "WIP DO NOT USE," 1 star, 0 dependents, no release since Jan 2022 means no security review, no CVE response, no maintainer. Encryption code is the single worst place to accept an unmaintained dependency — a silent bug here is a data breach, not a 500.
  • Crypto smells to check in the source. Search for openssl_encrypt with OPENSSL_RAW_DATA/OPENSSL_ZERO_PADDING, for ECB mode, for a reused IV/nonce, for missing authentication tags, and for base64_encode-everywhere without integrity. Older packages routinely omit the auth tag, which makes ciphertext malleable.
  • DBAL-type approach kills queryability. If encryption lives in the Doctrine type layer, findBy, LIKE, ORDER BY, and joins on that column operate on ciphertext and will return nonsense. Uniqueness constraints also compare ciphertext — with a random IV, "unique" silently stops enforcing anything.
  • postLoad listeners mark entities dirty. Decrypting in an event subscriber mutates the entity after hydration; Doctrine's change tracker may see a change and re-encrypt/re-persist on the next flush. Decrypt in a getter or type, not a listener — or your ciphertext grows every save.
  • Column length blowup. Base64 + IV + tag adds ~35–50% over plaintext. A VARCHAR(255) plaintext overflows a VARCHAR(255) column. Use TEXT/BLOB for ciphertext and validate against the encrypted length, not the plaintext length.
  • Serialization leaks. Encrypted attributes still flow into toArray(), JSON responses, Log::info($model), queue payloads, and Telescope. Add $hidden and scrub job payloads, or you've encrypted at rest and decrypted into your logs.
  • Laravel config cache. If the package reads env('ENCRYPT_KEY') at runtime, it breaks under php artisan config:cache. Move the key into config/*.php via env() there, and keep the raw key out of version control.
  • Version conflicts first. Run composer require philetaylor/doctrine-encrypt --dry-run before anything else; a 2022 doctrine/orm constraint will almost certainly clash with current Laravel Doctrine integrations.
  • Debug via raw SQL, not the model. DB::table(...)->value(...) is the only reliable check that something is actually encrypted. The model will happily show plaintext because a listener decrypted it.
  • If you fork, don't patch in place. Wrap the cipher behind an interface (encrypt(string): string / decrypt(string): string), add the auth tag yourself (Sodium crypto_secretbox/aead or Crypt), write round-trip and tamper-detection tests before changing logic. Then you have your own code with a known provenance, not a vendored WIP.
  • Better starting points: Laravel Crypt + an encrypted cast (native, audited, rotation-friendly), ambta/doctrine-encrypt-bundle (maintained Doctrine equivalent), ParagonIE CipherSweet (blind indexes done right), or Symfony's Sodium bundles if you're cross-framework.
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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk