All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- [Unreleased]
- [2.3.11] - 2026-08-20
- [2.3.10] - 2026-08-20
- [2.3.9] - 2026-08-19
- [2.3.8] - 2026-08-19
- [2.3.7] - 2026-08-18
- [2.3.6] - 2026-07-29
- [2.3.5] - 2026-07-29
- [2.3.4] - 2026-07-28
- [2.3.3] - 2026-07-28
- [2.3.2] - 2026-07-28
- [2.3.1] - 2026-07-20
- [2.3.0] - 2026-07-18
- [2.2.3] - 2026-07-09
- [2.2.2] - 2026-06-11
- [2.2.1] - 2026-06-11
- [2.2.0] - 2026-06-01
- [2.1.0] - 2026-06-01
- [2.0.10] - 2026-02-21
- [2.0.9] - 2026-02-21
- [2.0.8] - 2026-02-20
- [2.0.7] - 2026-02-20
- [2.0.6] - 2026-02-20
- [2.0.5] - 2026-02-20
- [2.0.4] - 2026-02-19
- [2.0.3] - 2026-02-19
- [2.0.2] - 2026-02-19
- [2.0.1] - 2026-02-19
- [2.0.0] - 2026-02-19
- [1.0.0] - 2026-02-19
2.3.12 - 2026-08-24
- Demos: MySQL env policy in FrankenPHP stack (REQ-DEMO-011).
- QA: add
phpstan-frankenphpextension (REQ-CS-005). - Docs: PHP-FIG PSR evaluation (REQ-CS-007).
- No API or configuration changes for integrators unless noted above.
- UPGRADING.md: production MysqlAes hard-fail (since 2.3.9) is documented at the top of the guide so upgrades that skip 2.3.9 notes cannot miss it. 2.3.10 no longer reads as “no steps” for MysqlAes hosts.
- Crypto prod guard: regression tests for MysqlAes FQCN and mixed profiles blocked in
prod; Halite still allowed (DoctrineEncryptExtensionTest).
- SECURITY.md: align MysqlAes guidance with the hard-fail production guard shipped in 2.3.9.
- Production guard: container compilation fails when any profile uses the deprecated
MysqlAesencryptor inprod(REQ-SEC-004). Use Halite or Defuse and migrate legacy ciphertext.
- CI: run
composer audit --lockedafter dependency install (REQ-SEC / P3).
- Demos: pin
nowo-tech/hot-reload-bundleto^1.4with FrankenPHP Mercure/hot_reload(dev/testonly).
- REQ-MAKE-010: Root and
demo/symfony8Makefiles preferdocker compose(V2) and fall back todocker-compose(V1). - REQ-MAKE-009: Monorepo
update-depsincludes use-includeso standalone GitHub Actions checkouts do not fail when../.scripts/is absent. - Demo
config/reference.php: drop auto-generateddeclare(strict_types=1)(Symfony Flex reference file).
No application configuration changes required. See UPGRADING.md.
make demo-smoke+.github/workflows/demo-smoke.yml: bootdemo/symfony8and assert HTTP 200 (REQ-TEST-011).- PHPStan bootstrap (
phpstan/bootstrap.php+ stubs) for optionalAsDoctrineListenerwhen doctrine-bundle is absent. - CI job PHPStan in
.github/workflows/ci.yml(REQ-CS-006).
- REQ-CS-006:
phpstan-baseline.neonemptied (ignoreErrors: []); analyse clean at level 8 withtreatPhpDocTypesAsCertain: false. Types/PHPDoc tightened acrosssrc/and tests. composer.jsonkeywords: addphp,frankenphp.- README FrankenPHP banner sentence uses the canonical short form (REQ-DOCS-017).
- docs/PHPSTAN.md updated for empty baseline policy.
- Table of contents on long docs (REQ-DOCS-005).
- INSTALLATION: recipe path corrected to
.symfony/recipe/. - DEMO-FRANKENPHP:
make demo-smoke(REQ-TEST-011).
No application configuration changes required. See UPGRADING.md.
- REQ-DOCS-018: GitHub About panel set — plain-text Description (aligned with
composer.json), Websitehttps://packagist.org/packages/nowo-tech/doctrine-encrypt-bundle, Topics (php,symfony,symfony-bundle,frankenphp,doctrine,encryption,halite,defuse,gdpr,key-rotation). Metadata is on GitHub only (not in the git tree).
No application configuration or runtime changes. See UPGRADING.md.
- REQ-CS-006: docs/PHPSTAN.md documents level 8 + justified baseline categories (no blanket
ignoreErrors). - REQ-DOCS-004: README badges for GitHub stars and Coverage.
- REQ-SEC-002: Release security checklist includes AI audit (REQ-SEC-004).
- REQ-DEMO-010: Demo FrankenPHP image bumped to
dunglas/frankenphp:1-php8.5-alpine; docs/smoke notes updated. - REQ-PKG-004: Shorter Packagist
descriptionand focusedkeywords. - Security: Twig
|decryptdeadis_safe htmllegacy code removed; SECURITY.md documents auto-escape + MysqlAes deprecation. - Demo Compose: removed Flex leftover
database_datavolume fragment. - PHPStan: baseline regenerated to absorb
missingType.iterableValue(replacing blanket ignore).
- Legacy
.travis.yml(CI is GitHub Actions only).
No application configuration changes required. See UPGRADING.md.
- REQ-CS-005:
nowo-tech/phpstan-frankenphp(require-dev) with classic + worker rulesets; PHPStan baseline for justified findings. - REQ-DOCS-017: FrankenPHP Friendly Worker Mode banner in README (
docs/images/frankenphp-friendly.png). - REQ-MAKE-007:
make down-dev(alias ofdown --remove-orphans). - REQ-REL-003:
.scripts/check-open-prs.shwired intorelease-check. - REQ-DEMO-010:
FRANKENPHP_MODE(classic|worker) in demo.env.example, Compose, and entrypoint. - REQ-TEST-003: docs/COVERAGE.md documents justified PHPUnit exclusions; includable
src/at 100% line coverage.
- REQ-DOCS-016:
demo/README.mdtranslated to English. - REQ-DOCS-002: README
## Documentationuses canonical base-link order;GITHUB_CI.mdunder Additional documentation. - REQ-SF-005:
SYMFONY_DEPRECATIONS_HELPER=max[direct]=0inphpunit.xml.dist. - REQ-PHP-001: concrete services marked
final(subscriber intentionally non-final for command/test collaboration). PreferEncryptorInterface/ composition for custom encryptors. - Documentation: README demo section uses
FRANKENPHP_MODE; coverage section reports 100.00%. - Dev dependencies / CI: php-cs-fixer, rector, phpstan, and
actions/cachebumps (Dependabot).
No application configuration changes required for typical Halite/Defuse/MysqlAes setups. See UPGRADING.md.
- Demo Symfony 7: removed
demo/symfony7. Usedemo/symfony8(port 8008) as the reference demo app. Docs (DEMO.md,DEMO-FRANKENPHP.md, README, etc.) updated accordingly.
- REQ-GIT-001: CI job
git-hygiene,.scripts/check-no-cursor-coauthor.sh,.scripts/strip-cursor-coauthor-from-history.sh,.githooks/commit-msg, Cursor rule01-git-commits.mdc, and GITHUB_CI.md. Makefile targets:setup-hooks,check-no-cursor-coauthor,strip-cursor-coauthor-from-history(wired intorelease-check). - Code of Conduct: CODE_OF_CONDUCT.md (Contributor Covenant); linked from README and CONTRIBUTING.md.
- Documentation: README wording aligned with
default_profile/profiles(2.3.0 rename). - PHPUnit coverage: exclude high-branch CLI/subscriber/Halite/Configuration paths covered by functional tests (aggregate coverage focus).
#[Encrypted]: removed obsolete doctrine/annotations@Annotation/@TargetPHPDoc (attribute-only).
No application configuration changes required. See UPGRADING.md.
- Config naming (AuditKit-style):
default_config/configsrenamed todefault_profile/profiles. Legacy YAML keys (default_config,configs) and container parameters (nowo_doctrine_encrypt.default_config,nowo_doctrine_encrypt.configs) remain accepted during transition. Docs, Flex recipe, and demos use the new keys. - Validation:
default_profilemust be a key inprofiles. An unknown default now throwsInvalidArgumentException(previously fell back to the first profile).
The #[Encrypted('alias')] attribute is unchanged. See UPGRADING.md.
- GitHub Spec Kit:
.specify/tooling, Cursor Agent skills (.cursor/skills/speckit-*), and baseline spec inspecs/001-baseline/(100%src/inventory). See SPEC-KIT.md and SPEC-DRIVEN-DEVELOPMENT.md. - CodeRabbit: review configuration (
.coderabbit.yaml) and GitHub Actions workflow for automated PR review.
- Documentation: SPEC-KIT.md (install, structure, Cursor usage); README link under Documentation.
- MysqlAes: PHPDoc
@deprecated since 2.1.0— not recommended for new production deployments (AES-128-ECB); prefer Halite or Defuse. No runtime change. - Twig
|decryptfilter: removedisSafe: ['html']so decrypted output is auto-escaped by Twig (security fix). Use|rawonly when the decrypted value is trusted HTML. - composer.lock: dependency sync for the bundle root and demo apps (
symfony7,symfony8).
No configuration changes required. See UPGRADING.md.
- Functional tests (CI): enable Doctrine ORM native lazy objects on PHP 8.4+ when running the test suite with Symfony 8.x. Symfony 8
var-exporterno longer ships LazyGhost helpers; fixes 14 functional test errors on the PHP 8.4 × Symfony 8.0 / 8.1 matrix cells.
No application or configuration changes. See UPGRADING.md.
- composer.lock:
platform.phpandcontent-hashsynced withcomposer.json(>=8.2); fixescomposer validate --strictin CI. - CI: Symfony 8.0 / 8.1 matrix cells use matching
symfony/var-exporter(^8.0/^8.1) instead of^7.4, which blocked dependency resolution.
No application changes required. See UPGRADING.md.
- GenerateSecretKeyCommandTest: assertions updated for MysqlAes in supported-encryptor messages (2.1.0 gap).
- Requirements (Nowo bundle specs): minimum PHP 8.2+;
symfony/*constraints ^7.0 || ^8.0 (Symfony 6 removed fromcomposer.json, aligned with 2.0 release notes). CI matrix tests Symfony 7.4, 8.0, and 8.1 on PHP 8.2–8.5. - Documentation: README badges and requirements, INSTALLATION.md, ROADMAP.md — PHP 8.2+ and Symfony 7.4 | 8.0 | 8.1+.
- Symfony 6 from declared
symfony/*Composer constraints (was still present incomposer.jsonafter 2.0).
Breaking: PHP 8.2+ is required; Symfony 6 is no longer installable with current constraints. See UPGRADING.md.
MysqlAesencryptor — AES-128-ECB compatible with MySQLAES_ENCRYPT()/AES_DECRYPT()(defaultblock_encryption_mode). Configure withencryptor_class: MysqlAesand a passphrase (env or key file). See MYSQL_AES.md.doctrine:encrypt:generate-secret-key— generates a passphrase forMysqlAesconfigs.- Documentation: MYSQL_AES.md (native SQL, LIKE demos, preset fixtures); PERFORMANCE.md (encryptors and query patterns compared).
- Demos (Symfony 7 & 8):
/mysql-aes-note— Doctrine + repository SQL paths, LIKE filters, preset fixtures, DB values view (encrypted vs decrypted). Secret messages:/secret-message/db-valueswith the same raw/decrypted tabs. Demo Docker: optional MySQL 8 service,pdo_mysql, Bootstrap 5 form theme.
- Documentation: CONFIGURATION.md, USAGE.md, DEMO.md, ROADMAP.md, README — MysqlAes, performance notes, demo troubleshooting (Composer advisories, Symfony 7.4).
- Demos: Symfony 7 demo targets 7.4.*;
composer.locksynced (e.g. twig-inspector-bundle); entrypoint avoids restart loop on failedcomposer install.
No breaking changes for existing Halite/Defuse configs. See UPGRADING.md.
- Symfony 7.1+:
DoctrineEncryptExtensionnow extendsSymfony\Component\DependencyInjection\Extension\Extensioninstead ofSymfony\Component\HttpKernel\DependencyInjection\Extension. The latter is considered internal since Symfony 7.1 and will be deprecated in 8.1; this change removes the deprecation notice.
- Documentation: COMMANDS.md — batchSize default for encrypt/decrypt database is documented as 5 (from config); link to CONFIGURATION added.
No upgrade steps required from 2.0.9. See UPGRADING.md.
- Code style: PHP-CS-Fixer configuration updated and applied across the codebase (src, tests, demos).
- Internal: Refactors in commands, encryptors, subscriber, mapping, Twig extensions and utilities; tests aligned with updated style and structure.
- Demos: Symfony 7 and 8 demo apps updated (config, controllers, entities, forms, fixtures, bootstrap); added
demo/Makefilefor running demos. - CI & tooling: GitHub Actions workflow, root and demo Makefiles, and docker-compose adjustments.
- PHP 8.1+:
DefuseEncryptorTest::testGenerateKeyandHaliteEncryptorTest::testGenerateKeyno longer pass an int tomd5(); usemd5((string) time())so the test suite passes with strict types.
No upgrade steps required from 2.0.8. See UPGRADING.md.
- Symfony Console 7.0 / 8.0:
RotateKeysCommandTestno longer usesApplication::add()(removed in Symfony Console 8.0). Tests now register commands viaFactoryCommandLoaderandsetCommandLoader(), and set the application on the rotate command withsetApplication()so subcommands resolve correctly. This fixes the 8 failing unit tests when running on PHP 8.4 with Symfony 7.0 (e.g. in CI).
No upgrade steps required from 2.0.7. See UPGRADING.md.
- doctrine:encrypt:status: For each entity, lists which properties are encrypted and their config (e.g.
email (config: personal_data)). At the end, shows configured encryptor configs (config name, encryptor class Halite/Defuse, and which is default). - doctrine:encrypt:rotate-keys: New command to run the full key rotation: optional backup of key files (
--backup), full decrypt, key change (generate new key files or prompt for .env update), then re-encrypt. Each step asks for confirmation unless--no-interactionis used. See COMMANDS.md and KEY_ROTATION.md. - doctrine:encrypt:generate-secret-key: Option
--forceto overwrite existing key file(s) without asking. - doctrine:decrypt:database and doctrine:encrypt:database: Option
--forceto skip the confirmation prompt (useful when calling from scripts or fromrotate-keyswith--no-interaction). - Configuration:
nowo_doctrine_encrypt.batch_size(default5) for the default batch size of encrypt/decrypt database commands; overridable per run via thebatchSizeargument. - Documentation: INSTALLATION.md — new section FrankenPHP (runtime and worker mode): compatibility with FrankenPHP in both modes, no extra config; recommendations (e.g. limit requests per worker). README and DEMO.md reference FrankenPHP compatibility; demo Caddyfiles include a short comment.
- doctrine:decrypt:database and doctrine:encrypt:database: Use raw SQL for encrypt/decrypt (no Doctrine lifecycle events); default batch size is 5 (configurable via
nowo_doctrine_encrypt.batch_size). - Documentation: COMMANDS.md documents status output (per-entity properties and configs, configured configs), rotate-keys command, and
--forceon generate-secret-key, decrypt and encrypt. KEY_ROTATION.md and SECURITY.md reference the new rotate-keys command. README and INSTALLATION next steps mention rotate keys. ROADMAP updated. DEMO.md has a FrankenPHP subsection; demo Caddyfiles reference INSTALLATION § FrankenPHP.
- RotateKeysCommandTest: Test for “no configs” now builds the command with
DoctrineEncryptSubscriber(null)andregistry = nullso that an empty registry is never passed to the subscriber (which would callgetDefault()and throw).
- Coverage: Unit tests added for
RotateKeysCommand(full rotation, backup with SQLite, backup-db-cmd, env configs, paused step, no configs), forAbstractCommand(getEncryptedTableInfo,getColumnNameFromMetadata,getRowValue), for decrypt/encrypt database commands (executeStatement path, row with null id, decrypt throw fallback), forDoctrineEncryptStatusCommand(registry null, configured configs, none), and forGenerateSecretKeyCommand(config argument with unsupported encryptor). Code coverage for lines is ≥ 95%.
No upgrade steps required from 2.0.6. See UPGRADING.md.
- Tests: Unit test for
HaliteEncryptorwhen the key path is a directory (coversnormalizeKeyFile()early return). Unit test forGenerateSecretKeyCommandwhen the key file is created in a non-existent directory (coverscreateKey()withmkdir(..., true)). Improves code coverage (lines ≥ 95%).
- PHPUnit: The unit test that uses a directory as key path no longer triggers a PHP Notice (Halite emits a notice when reading a path that is a directory); the test suppresses it with
@on the call that triggers it so the test suite runs without notices.
No upgrade steps required from 2.0.5. See UPGRADING.md.
- doctrine:encrypt:generate-secret-key: Optional argument
configto generate the key for a single config; when the key file already exists, the command asks for confirmation before overwriting. Without argument, the command now checks all configs (allsecret_directory_pathentries) and creates a missing key file for each Halite or Defuse config; configs that already have a key are skipped with a message. - Makefile: Targets
validate(runscomposer validate --strict) andupdate(runscomposer update);make qanow runsvalidatebefore cs-check and test.
- Test directory: The test directory has been renamed from
Teststotests(lowercase). PHP namespaces remainNowo\DoctrineEncryptBundle\Tests\; only the folder and references inphpunit.xml.dist,composer.json(autoload-dev), and.php-cs-fixer.dist.phpchanged. - symfony/var-exporter: Kept in
require(not only in require-dev) so that Doctrine ORM 3.x LazyGhost is available in all environments (CI and production when using lazy loading). This fixes functional test failures on PHP 8.4/8.5 with Symfony 8.0 ("Symfony LazyGhost is not available"). - CI: Install step pins
symfony/var-exporter:^7.4so Doctrine ORM accepts the installed version for LazyGhost; coverage job also requiressymfony/var-exporter. - Documentation: COMMANDS.md updated: encrypt/decrypt database commands use argument
config(config alias); generate-secret-key documents the new behaviour (all configs vs single config with confirmation).
No upgrade steps required from 2.0.4. See UPGRADING.md.
- PHPUnit: Unit tests for
EncryptUtilandDecryptExtension(Twig) no longer mock the final classEncryptorRegistry; they use realEncryptorRegistryinstances with mockedEncryptorInterfacewhere needed. This fixesClassIsFinalException: Class ... is declared "final" and cannot be doubledwhen runningmake test-coverageorcomposer test-coveragewith PHPUnit 10/11. - Symfony Console: Commands
doctrine:encrypt:databaseanddoctrine:decrypt:databasenow register arguments in an idempotent way:configandbatchSizeare only added when not already present. This avoidsLogicException: An argument with name "config" already existswhen the Console component builds the command definition more than once (e.g. with certain Symfony Console 7/8 setups or when tests callgetDefinition()).
No upgrade steps required from 2.0.3. See UPGRADING.md.
- paragonie/sodium_compat: Constraint relaxed from
^1.20to^1.20 || ^2.0so the bundle installs in projects that requiresodium_compat2.x (e.g. 2.5.0).
No upgrade steps required from 2.0.2. See UPGRADING.md.
- Symfony Console 8.0: Command unit tests (
DoctrineEncryptDatabaseCommandTest,DoctrineDecryptDatabaseCommandTest) now use$command->setApplication($application)instead of$application->add($command), sinceApplication::add()was removed in Symfony Console 8.0. - CI: Ensure
symfony/var-exporteris installed so Doctrine ORM 3.x LazyGhost works in functional tests; CI install step now runs a fullcomposer updateso the package is resolved and installed on all matrix jobs.
No upgrade steps required from 2.0.1. See UPGRADING.md.
- require-dev:
symfony/var-exporter^7.0 || ^8.0 for Doctrine ORM 3.x LazyGhost support in functional tests.
- Documentation: README, INSTALLATION, and ROADMAP now state explicitly that the bundle is compatible with Symfony 7 and 8; Requirements and ROADMAP focus updated to 2.x.
- Unit tests for
DefuseEncryptorandHaliteEncryptorno longer depend on fixture key files orsys_get_temp_dir(); they create key files under the testfixturesdirectory so CI (e.g. GitHub Actions) runs reliably.
No upgrade steps required from 2.0.0. See UPGRADING.md.
- Symfony 6 support. The bundle now requires Symfony ^7.0 || ^8.0. If you are on Symfony 6.x, stay on
nowo-tech/doctrine-encrypt-bundle^1.0 or upgrade your application to Symfony 7 or 8. - Demo for Symfony 6. The
demo/symfony6project has been removed. Demos for Symfony 7 and 8 remain indemo/symfony7anddemo/symfony8.
- Composer requirements: all
symfony/*constraints updated from^6.0 || ^7.0 || ^8.0to^7.0 || ^8.0. - CI (GitHub Actions) and documentation now target only Symfony 7.0 and 8.0.
For upgrade steps, see UPGRADING.md.
First release under nowo-tech. Fork from hec-franco/doctrine-encrypt-bundle with Halite and Defuse encryptors, Doctrine ORM 2.x/3.x, and attribute/annotation support.
- nowo-tech fork: Bundle in namespace
Nowo\DoctrineEncryptBundleand packagenowo-tech/doctrine-encrypt-bundle. - Support for Symfony 7 and 8; PHP 8.1+.
- Multiple encryptors: Config key
configs(e.g.personal_data,financial_data) withdefault_config; each config has its own encryptor and key file (.{Encryptor}.{alias}.key, e.g..Halite.personal_data.key). - Attribute
#[Encrypted('alias')]to assign a field to a named config; use"default"for the default config. - Documentation in
docs/(CHANGELOG, UPGRADING, RELEASE, CONFIGURATION, INSTALLATION, USAGE, COMMANDS, DEMO, custom_encryptor, etc.). - Makefile and Docker setup for development (
test,cs-check,cs-fix,qa). - Demo applications for Symfony 7 and 8 (entity
SensitiveRecord, CRUD, fixtures, Halite + Defuse). - Symfony Flex recipe (config with
configsonly); demo apps excluded from the distributed package (archive.exclude,export-ignore).
- Configuration alias:
ambta_doctrine_encrypt→nowo_doctrine_encrypt. See UPGRADING.md when migrating from the old bundle. - Bundle class:
AmbtaDoctrineEncryptBundle→NowoDoctrineEncryptBundle; registerNowo\DoctrineEncryptBundle\NowoDoctrineEncryptBundle::classinconfig/bundles.php. - Single-encryptor config (legacy) still supported; recipe and docs recommend
configsfor new setups.
- (None in this release.)
For upgrade steps from ambta/doctrine-encrypt-bundle or hec-franco/doctrine-encrypt-bundle, see UPGRADING.md.