dualmedia/disable-orm-bundle
A Symfony + Doctrine bundle that lets you transparently disable entity fields from the ORM via a #[DisableORM] attribute, enabling zero-downtime deployments and safe field removal across multiple app versions.
Architecture fit. The first finding a TPM must record is a stack mismatch: despite the "Laravel/PHP package" framing, this is a Symfony + Doctrine ORM bundle, not Laravel. It registers as a Symfony bundle and overrides doctrine.orm.entity_managers.<name>.class_metadata_factory_name; Eloquent/Blade services get zero value. Where the stack is Symfony + Doctrine and multiple app versions share one database during rolling/blue-green/canary deploys, the fit is precise and narrow: it strips #[DisableORM]-tagged fields from ORM metadata so a new release can ignore a column while draining old instances still use it. That converts column removal from a synchronized maintenance-window event into two ordinary deploys — a release-engineering capability, not a product feature.
Integration feasibility. Low code integration, moderate process integration. The README path is three steps (composer require, config/bundles.php registration, per-entity-manager class_metadata_factory_name). The real effort is behavioral verification: every consumer of ORM metadata — repositories, query builders, hydrators, serializers, and admin/API layers (EasyAdmin, API Platform, Gedmo/Stof-style extensions) — must be audited because a disabled field disappears from introspection. The bundle ships meaningful guardrails that make adoption more feasible than a hand-rolled factory: a configurable disable_on_commands allowlist (defaults keep doctrine:schema:validate and doctrine:migrations:diff aware of the field, so migrations can still target it), a PHPStan rule for entity misuse, and a /scripts git-diff CI job (GitLab CI sample included) that fails a build when a field is deleted without first being tagged.
Technical risk.
Key questions for the engineering spike. (1) Which services are Symfony/Doctrine and actually run multiple concurrent versions? (2) Is the migration need specifically column removal? (3) Do we have a DB default strategy for every candidate column? (4) What metadata-consuming bundles and admin/API layers touch the pilot entities? (5) Who owns the fork if the maintainer stops? (6) Can CI accept the change-detection gate?
Stack fit. Gate first on framework. Laravel/Eloquent: reject, document the "expand/contract via native migrations" alternative, and use this package only as a conceptual reference. Symfony + Doctrine (bundle 3 / Symfony 7.4–8 per the 1.1.0 release, PHP 8+): eligible. Atomic or version-locked deploys (single instance, always-drained workers): ineligible — the bundle buys nothing and adds a metadata factory to own.
Migration path. Treat the bundle as the contract step of an existing expand/contract habit, not a new doctrine. Per-column workflow: ensure a DB-level default → tag the property #[DisableORM] (keeping the #[ORM\Column] mapping) → deploy → later, drop the column in a follow-up migration. Use @deprecated annotations as the on-ramp so the intent is visible in code review before the attribute appears.
Compatibility. Preserve the default disable_on_commands list so doctrine:schema:validate and doctrine:migrations:diff still see the field — deviating here silently degrades migration tooling. Add the extension.neon PHPStan include so static analysis catches entity-level misuse, and wire the /scripts git change-detection job into CI before enabling the workflow broadly. Audit for metadata-decorating bundles and verify hydration/serialization on the pilot entity.
Sequencing.
#[DisableORM]-tagged."Maintenance. Low surface area, but a single-maintainer, zero-adoption dependency. Budget for fork-and-own as a plausible steady state; the MIT license and compact implementation cap that cost. Version pinning plus a periodic compatibility check against Doctrine/Symfony upgrades is the minimum posture.
Support. The support burden shifts from runtime to diagnostics and onboarding. Two persistent questions will recur: "why is this field null/missing?" and "why didn't my migration see this column?" Answer both with the runbook and by keeping the command allowlist at defaults. Because failures are silent, invest in test coverage on read paths rather than expecting exceptions to surface problems.
Scaling. Scales with the number of entities and entity managers, not with traffic — the cost is a metadata factory pass per manager at boot/warm-up, which must be benchmarked on the largest entity graph. Org-wide scaling depends on the CI gate staying enforced; unenforced, the practice degrades into ad-hoc field deletions and the exact incident the tool was adopted to prevent.
Failure modes to document. (a) Field deleted without prior #[DisableORM] tagging → silent breakage in still-deployed old versions (mitigated by CI job + PHPStan). (b) Missing DB default on a disabled column → insert failures from new instances. (c) Config drift in the per-entity-manager YAML → the factory silently isn't applied. (d) A read path still relying on the field through the ORM → wrong results, not errors. (e) Metadata-decorating bundle conflict → unpredictable mapping behavior. (f) Rollback expectation mismatch: re-enabling means removing the attribute and redeploying — not instant, not runtime-toggleable.
Ramp-up. One short engineering walkthrough (bundle registration, factory assignment, attribute + default-value workflow) plus a CI/PHPStan setup session. The conceptual prerequisite is that the team already thinks in expand/contract terms; without that, the attribute becomes a magic annotation rather than a release-safety tool. Expect a one-sprint pilot to reach confidence, with PM-level value landing only when it removes scheduled downtime windows from the roadmap and unlocks a plannable "retire N legacy columns this quarter" initiative.
How can I help you explore Laravel packages today?