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

Doctrine Plural Naming Laravel Package

rikudou/doctrine-plural-naming

A Doctrine DBAL naming strategy that automatically pluralizes table names using Laravel's pluralizer, keeping database schemas consistent without manual table naming. Drop-in integration for Laravel projects using Doctrine.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is. A single-class Doctrine ORM naming strategy that turns entity class names into pluralized snake_case table names (UserProfile → user_profiles). It only affects table names; column names, reference columns, and join columns use the parent Doctrine strategy's behavior. In practice you're enabling a convention, not integrating a framework.

Install

composer require rikudou/doctrine-plural-naming

Before wiring it in, check vendor/rikudou/doctrine-plural-naming/src/ and composer.json — the surface is tiny (typically PluralUnderscoreNamingStrategy and PluralCamelCaseNamingStrategy), you want to confirm the Doctrine ORM/DBAL constraint intersects your lockfile, and you want to confirm pluralization delegates to doctrine/inflector. That's the whole vetting exercise.

Wiring — standalone Doctrine

use Doctrine\ORM\Configuration;
use RikudouSage\DoctrinePluralNaming\PluralUnderscoreNamingStrategy;

$config = new Configuration();
$config->setNamingStrategy(new PluralUnderscoreNamingStrategy());

Wiring — Symfony / DoctrineBundle

# config/packages/doctrine.yaml
doctrine:
    orm:
        naming_strategy: RikudouSage\DoctrinePluralNaming\PluralUnderscoreNamingStrategy

Wiring — Laravel with laravel-doctrine/orm

// config/doctrine.php
'managers' => [
    'default' => [
        'naming_strategy' => \RikudouSage\DoctrinePluralNaming\PluralUnderscoreNamingStrategy::class,
    ],
],

The exact key depends on the Doctrine integration you use; the invariant is that a NamingStrategy instance is set on the Configuration before the EntityManager is built. If you don't see that hook in your integration, resolve a service named doctrine.orm.naming_strategy.default.

Where to look first

  1. classToTableName() — the only method that changes day-to-day behavior. Everything else is inherited.
  2. joinTableName() — many-to-many pivots also get pluralized; verify against your reporting queries.
  3. The inflection rules in the package (or its doctrine/inflector usage) — this is where irregular plurals live, and where you'll debug surprises.
  4. The README for which of the two shipped strategies to use. PluralUnderscoreNamingStrategy is the safe default matching SQL conventions.

First use case

Greenfield service where you want users, orders, line_items without #[ORM\Table] on every entity. Turn the strategy on, drop #[ORM\Table] from all entities, then validate before any migration runs:

php bin/console doctrine:schema:validate

Then confirm at the metadata level:

$meta = $em->getClassMetadata(App\Entity\User::class);
echo $meta->getTableName(); // "users"

Only after those two checks pass should you generate a migration.

Implementation Patterns

Default-to-convention, override for irregulars. The strategy is a default. An explicit #[ORM\Table(name: 'people')] always wins, so keep a short override list for Person, Mouse, Criterion, Status-style nouns instead of trying to make inflection perfect. Reviewers should expect an empty diff when adding a new entity; if they see a #[ORM\Table] annotation, it should be justified.

Adopting on an existing schema — pin first, adopt second. Adding the strategy to a production app doesn't rename tables; it renames them only once you call schema:update or regenerate migrations. That's the dangerous path. Two safe patterns:

  • Add explicit #[ORM\Table] everywhere so the strategy has no effect on legacy entities, and only use pluralization for new entities. You get the default-forward but stay pin-stable.
  • Accept a one-time rename migration in a single dedicated PR with all FKs, indexes, and sequences adjusted. Do this only on services without external SQL consumers.

Custom pluralization via subclass. Don't patch the vendor file. Subclass and override just the pluralization step:

final class DomainNamingStrategy extends PluralUnderscoreNamingStrategy
{
    protected function pluralize(string $word): string
    {
        return match (strtolower($word)) {
            'person'   => 'people',
            'criterion'=> 'criteria',
            'schema'   => 'schemas',
            default    => parent::pluralize($word),
        };
    }
}

Register the subclass as a service and point naming_strategy at the service id. This means vendor upgrades touch one seam.

Unit-test the strategy without a database. It's a pure function; the test lives outside your fixtures:

final class NamingStrategyTest extends TestCase
{
    public function testRealVocabulary(): void
    {
        $s = new DomainNamingStrategy();
        self::assertSame('user_groups',   $s->classToTableName(App\UserGroup::class));
        self::assertSame('people',        $s->classToTableName(App\Person::class));
        self::assertSame('order_items',   $s->classToTableName(App\OrderItem::class));
    }
}

Keep this test alongside your real entity vocabulary. It's the regression net when the package or Doctrine bumps a minor version.

Join tables — verify, don't assume. Many-to-many pivot names are generated by the strategy too. Run a schema diff and grep:

php bin/console doctrine:schema:update --dump-sql | grep -i 'create table'

If a specific pivot name is externally visible (reporting, BI, external FK references), annotate it with #[ORM\JoinTable(name: '...')] in the owning entity.

Migration workflow with a naming strategy change. Treat it as a schema migration event, not a config change:

  1. Land the strategy with explicit #[ORM\Table] on all existing entities.
  2. Add a test asserting table names for the current entity set.
  3. Add new entities without annotations.
  4. When you decide to convert a legacy entity, do it in an isolated PR: drop the annotation, regenerate the migration, manually verify every FK rename.

Enforce with a lint check. A simple PHPStan rule or CI grep for #[ORM\Table] in the entity namespace catches drift. Without this you end up with a mix of pluralized and singular tables, which is worse than either convention applied consistently.

Pair it with your migrations. Your migration files will contain literal table names — those are the source of truth once shipped. Any naming-strategy change that would rename a table must be expressed as an explicit migration, not left to schema:update.

Gotchas and Tips

It only changes table names. Column names, join columns, and identifier columns are inherited from the parent UnderscoreNamingStrategy. If you're expecting $userProfile->getEmailAddress() to map to email_address and it doesn't, that's the parent strategy, not this package.

Irregular nouns are the number-one source of production incidents. Person → people, Status → statuses, Analysis → analyses, and uncountables like Data are classic. Any table name that ends up in a hand-written query or an external report should have an #[ORM\Table] override pinning it.

The rename failure mode is late. A mis-pluralized table name doesn't fail at boot — it fails at query time on whichever code path touches that entity. If your integration tests don't hit that entity, the failure ships. Add a smoke test that touches every entity at least once (find(0) is enough) right after adopting the strategy.

Silent no-op if the strategy isn't wired. If your Doctrine integration resolves naming_strategy only for a specific connection, or a config layer overwrites it, the app boots fine and you get singular table names. Verify with:

echo get_class($em->getConfiguration()->getNamingStrategy());

Do this in a CI health check for services that depend on plural tables.

Compatibility across DBAL 2 / 3 and ORM 2 / 3. Doctrine's NamingStrategy moved interfaces between versions. If your platform is on a modern DBAL 3/ORM 3 stack, confirm the package's constraint isn't pinned to an older version. If it is, don't try to make it work — vendor the strategy (it's ~40 lines) into your namespace with a Doctrine-version-native interface.

quoted identifiers and PostgreSQL. Pluralization can produce names that collide with reserved words (order, user, group). Doctrine's quoted metadata flag, or identifier_quoting in the Configuration, is what protects you. If you see syntax error at or near "user" in Postgres, that's the fix — not a naming-strategy change.

Inflection dependency should be explicit. If the package relies on doctrine/inflector but doesn't declare it as a direct dependency in its composer.json, you can get a version bump through Doctrine's transitive deps that changes a plural. Pin doctrine/inflector in your own composer.json and lock it.

Double-pluralization with mixed conventions. If any entities already have plural table names via annotation (#[ORM\Table(name: 'users')]), the strategy is a no-op for them — that's fine. But if you have an entity class literally named Users, you'll get userses. Rename the class rather than fighting the strategy.

Custom strategy service wiring. In Symfony, naming_strategy accepts a class string or a service id. If your subclass has constructor arguments, use a service id and mark the service public, or point to a factory. If you pass a class string for a class that requires arguments, Doctrine will try to instantiate it with no args and blow up at EntityManager construction — before any application code runs, which is at least an easy failure to diagnose.

Join table naming can surprise you. joinTableName() combines both sides; the exact form (e.g., user_role vs roles_users) depends on the strategy's implementation and which side is owning. Never hardcode pivot names in raw SQL — read them from ClassMetadata::getAssociationMapping($field)['joinTable']['name'] in tests.

Extension point is classToTableName (and joinTableName). Everything else is inherited. If you need to change column naming, extend the appropriate Doctrine strategy and call the naming strategy only for tables — but at that point, consider vendoring both behaviors into one class.

Don't add this if you're Eloquent-only. Laravel's default pluralization is already correct for Eloquent. This package only matters if you're running Doctrine ORM inside the app. The cost of adding it to an Eloquent-only project is zero benefit plus an unmaintained dependency.

Vendoring is a legitimate option. With 0 stars and 0 dependents, the support surface is the source file itself. If you're worried about abandonment or Doctrine-version drift, copy the strategy class into app/Doctrine/, add the two or three inflection cases you care about, and test it. You lose nothing but the composer require.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core