precision-soft/symfony-doctrine-encrypt
A Symfony bundle that adds transparent field-level encryption to Doctrine ORM entities. Protect sensitive database columns at rest with configurable encryption, simple entity mapping, and seamless integration into Symfony applications.
Start with the .example/ directory in the package. It is now a runnable micro-kernel app that installs the bundle from the working tree via a path repository and exercises every public capability on real engines. Copy its bundle registration and configuration block into your own skeleton rather than reading the source first; it is the shortest path to a working setup.
Minimum steps:
bundles.php / provider entry).text/blob depending on your driver), then do a single write-and-read through the ORM before touching any command.database:encrypt --dry-run over a legacy plaintext table. It performs the walk and reports what it would change without touching rows. Only after that is clean do you run without --dry-run.For an existing encrypted dataset, your first command is database:rotate --dry-run, not database:rotate. Confirm the cursor and salt version it plans to write before running it for real.
Rotate in the foreground, verify in a separate process. The documented post-rotation check is database:rotate --dry-run --verify. It writes nothing, reads back the stored columns, and fails naming the fields and the row counts still on another salt version. Run it as a distinct invocation after the rotation exits, not chained with && in the same command — a rotation that fails validation should not silently proceed to the next stage.
Treat checkpoints as state you own. Checkpoints now carry a scope: command, entity manager, and salt version. A database:rotate checkpoint cannot be resumed by database:encrypt, against a different manager, or toward a salt version that has since changed. This is a guardrail, not a limitation — if you genuinely need to switch commands or managers mid-migration, finish the current run or delete the checkpoint deliberately, and know that deleting means the whole table is walked again. In CI, mount the checkpoint directory on persistent storage if you expect long runs to resume across job retries.
Resume safely with --from-id. Identifier values are now rebuilt through the identifier's DBAL type, so bigint binds as an integer, and date_immutable/uid identifiers bind as the objects the mapping expects. You can persist a cursor yourself (e.g. in a job payload) and hand it back later. Values beyond PHP_INT_MAX stay the string DBAL returns — do not cast them yourself before passing them in. An identifier the type cannot rebuild is refused with the type and field named; that is a configuration problem, not a data problem.
Scope --verify runs to one class in SINGLE_TABLE hierarchies. Under SINGLE_TABLE inheritance, --verify now counts only the rows of the selected class and its subclasses — the rows the walk could actually see. A sibling class still on an older salt version is not the selected class's failure. If you have a hierarchy, verify each concrete class separately rather than expecting one --verify to be authoritative for the table.
Resolve commands from the container but do not cache their run state. A command instance reads its checkpoint afresh on every run, because as a container service it outlives a single run(). This means repeated invocations within one process (a worker, a long-lived test kernel, a queued job that calls the command twice) are now correct. If you have code that assumed a command object held the previous cursor, drop that assumption — but also stop reusing the same instance as a shortcut to resume; pass --from-id or let the checkpoint file do it.
Failures name the row and carry the cause. A batch that fails to load now reports the entity and the cursor it was loading after, and preserves the original exception. When a run dies on a row whose salt version was dropped from configuration, the message tells you exactly which cursor to inspect. Log the full exception, not just the message.
--batch-size is sanitized. Values that saturate to PHP_INT_MAX are rejected rather than turning into one batch over the whole table. If you previously passed an absurd sentinel value to mean "no batching", pick a real number.Stringable identifier is stored as its string form and rebuilt through its type; anything else is refused both when the cursor is written and when it is read. A DateTimeImmutable or uid cursor is now rebuilt properly rather than bound back as a JSON array or a string. If you persist cursors yourself, store the Stringable's string form and nothing more.CheckpointService constructor signature changed — the scope is now the first argument. If you instantiate or decorate it, update the call site. setIdentifierValues() accepts only integers and strings; castIdentifierValue() may now return an object. Any subclass overriding the cast must respect that return contract.AbstractDatabaseCommand, DatabaseRotateCommand and EntityService changed their message formatting. Any log-scraping or alerting regex that matched the old casing will need updating..example/ is export-ignored. It is a development and reference artefact, not shipped in vendor/. Read it from the repository or a source checkout, and gate your own equivalent with .dev/validate/all.sh --example if you maintain one.How can I help you explore Laravel packages today?