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

Tavvet Doctrine Prefix Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install:
    composer require tavvet/tavvet-doctrine-prefix-bundle
    
  2. Register the bundle. On Symfony Flex the bundle is auto-registered; otherwise add it to config/bundles.php:
    Tavvet\DoctrinePrefixBundle\TavvetDoctrinePrefixBundle::class => ['all' => true],
    
  3. Read 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.
  4. Minimal config (adjust keys to match Configuration.php):
    # config/packages/tavvet_doctrine_prefix.yaml
    tavvet_doctrine_prefix:
        table_prefix: 'app_'
        column_prefix: 'app_'
    
  5. Verify the metadata was actually rewritten:
    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.

Implementation Patterns

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.

Gotchas and Tips

  • Metadata cache will lie to you. The listener runs on 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.
  • Migration diff generates DROP/CREATE on adoption. 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 prefixes must be consistent, or joins break. If 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.
  • Native SQL and DQL bypass the listener. Raw $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().
  • Naming strategy conflicts. If you use 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.
  • Sequences, indexes, and constraints. PostgreSQL sequences and MySQL index/constraint names are derived from table/column names. After prefixing, re-diff your schema to confirm the sequence/constraint names move with the table; otherwise you'll get "relation already exists" errors on multi-tenant setups.
  • Order of bundle registration doesn't matter, but cache warm order does. cache:warmup must run after the prefix config is loaded, i.e. after any env-specific YAML overrides.
  • Tiny, single-maintainer package (1 star, 0 dependents). Read 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.
  • Fallback if the bundle misbehaves: the listener is likely a thin wrapper on 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.
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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk