tavvet/tavvet-doctrine-prefix-bundle
Symfony bundle that adds configurable table prefixes to Doctrine ORM entities, helping share databases or isolate tables without editing mappings. Integrates with Doctrine metadata and Symfony configuration for consistent prefixed table names.
composer require tavvet/tavvet-doctrine-prefix-bundle
config/bundles.php:
Tavvet\DoctrinePrefixBundle\TavvetDoctrinePrefixBundle::class => ['all' => true],
Configuration.php first. With 1 star and sparse docs, the ground truth for config keys, defaults, and supported scopes lives at vendor/tavvet/tavvet-doctrine-prefix-bundle/src/DependencyInjection/Configuration.php. Open it before writing YAML — names here vary (table_prefix / column_prefix vs. a nested prefix: {table:, column:} structure) and guessing wastes time.Configuration.php):
# config/packages/tavvet_doctrine_prefix.yaml
tavvet_doctrine_prefix:
table_prefix: 'app_'
column_prefix: 'app_'
php bin/console cache:clear
php bin/console doctrine:mapping:info
php bin/console doctrine:schema:validate
If mapping:info still shows unprefixed tables, jump to the cache and event-listener gotchas below.First use case: isolating this app's tables in a shared database by globally prefixing everything with app_, or retrofitting a module prefix (billing_, crm_) onto a subset of entities without touching entity code. Start with a single entity or a dev database — never point the initial doctrine:schema:update at production.
Global prefix (whole app). The dominant pattern: set table_prefix once, let the bundle's loadClassMetadata listener rewrite every table. Good for "one app, one database prefix" or embedding into a shared DB.
Per-entity / per-module prefix. If the bundle exposes an annotation or attribute (check the Annotation/Attribute directory in src/), apply it on the entity instead of globally:
#[ORM\Entity]
#[PrefixedTable(prefix: 'billing_')]
class Invoice { /* ... */ }
This lets you namespace modules independently while they share a database. If no annotation exists, use multiple Doctrine connections with different prefixes, or subclass the bundle's listener and gate it on a class-namespace check.
Multi-tenant via runtime prefix. Because the rewrite happens on loadClassMetadata (which fires once per metadata load and is cached), you cannot flip the prefix per request without rebuilding the metadata cache. For real multi-tenancy, prefer one connection (and one entity-manager) per tenant, each with its own table_prefix, over a shared EntityManager.
Migrations. Let the bundle generate the target schema, then hand-write the diff:
php bin/console doctrine:migrations:diff --from-empty-schema
Read the output — for a rename you want RENAME TABLE old TO app_old, not DROP + CREATE. Copy the generated SQL out and edit it before committing the migration. Do this once at adoption; after that, keep the prefix config in every environment so diffs stay quiet.
Testing. Point the test DB at the same prefix and rebuild schema each run so the fixture/entity metadata match:
# config/packages/test/tavvet_doctrine_prefix.yaml
tavvet_doctrine_prefix:
table_prefix: 'test_'
Then doctrine:database:drop --force --if-exists && doctrine:database:create && doctrine:migrations:migrate.
Integrating with an existing app. Scope adoption to new tables first (per-entity prefix), migrate legacy tables later, so one rename doesn't take down half the schema at once.
loadClassMetadata, then Doctrine caches the result. After changing config you must bin/console cache:clear (and in prod, warm the cache again). Symptom: prefixed table appears in one command and not the next.doctrine:migrations:diff sees renamed tables as unrelated and will propose DROP TABLE users; CREATE TABLE app_users;. Never run it blindly — edit to RENAME TABLE.column_prefix is applied, foreign-key and join columns have to end up with the same name. This is the most common source of "Column not found" errors: EntityManager join metadata, ManyToMany join tables, and the FK in the owning entity must all derive the same prefixed column. Verify with doctrine:mapping:info on a ManyToMany before trusting it.$conn->executeQuery('SELECT * FROM users') and any hand-written SQL will fail against the prefixed table. Route those through Doctrine metadata — e.g. build the name via $em->getClassMetadata(Foo::class)->getTableName().doctrine.orm.naming_strategy: underscore or a custom strategy, the prefixing listener and the strategy can both rewrite column names. Confirm the final name comes from one source; don't combine a custom naming strategy with column_prefix unless you have verified an end-to-end migration.cache:warmup must run after the prefix config is loaded, i.e. after any env-specific YAML overrides.src/ before depending on undocumented behavior; it's small enough to fork. The event listener is the main extension point — if you need per-tenant or per-connection prefixes, fork the listener and add your own condition, or subclass the bundle's subscriber and tag it instead of the original.loadClassMetadata. Replacing it is ~20 lines: a subscriber implementing EventSubscriber that on loadClassMetadata calls $args->getClassMetadata()->setTableName($prefix . $name) and rewrites fieldNames/columnNames. That is often more reliable than debugging an unmaintained bundle.How can I help you explore Laravel packages today?