borsaco/doctrine-prefix-bundle
Installation
composer require borsaco/doctrine-prefix-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Borsaco\DoctrinePrefixBundle\DoctrinePrefixBundle::class => ['all' => true],
];
Basic Configuration
Create config/packages/doctrine_prefix.yaml:
doctrine_prefix:
table_prefix: "app_"
column_prefix: "usr_"
Ensure naming_strategy is set in config/packages/doctrine.yaml:
doctrine:
orm:
naming_strategy: Borsaco\DoctrinePrefixBundle\Doctrine\ORM\Mapping\PrefixNamingStrategy
First Use Case
Define an entity (e.g., src/Entity/User.php):
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/** @ORM\Entity */
class User
{
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
private $id;
/** @ORM\Column(type="string", length=255) */
private $name; // Will become `usr_name` in DB
}
Run migrations (php bin/console doctrine:migrations:diff and php bin/console doctrine:migrations:migrate). Verify the table (app_user) and column (usr_name) prefixes in the database.
Multi-Tenant Prefixing
Dynamically set prefixes per tenant by overriding the PrefixNamingStrategy:
# config/packages/doctrine_prefix.yaml
doctrine_prefix:
table_prefix: "%tenant_prefix%_" # e.g., "client1_"
column_prefix: "usr_"
Use environment variables or runtime logic (e.g., middleware) to inject %tenant_prefix%.
Legacy Schema Integration
For existing databases, manually adjust PrefixNamingStrategy to skip specific tables/columns:
// src/Doctrine/ORM/Mapping/ExtendedPrefixNamingStrategy.php
namespace App\Doctrine\ORM\Mapping;
use Borsaco\DoctrinePrefixBundle\Doctrine\ORM\Mapping\PrefixNamingStrategy;
class ExtendedPrefixNamingStrategy extends PrefixNamingStrategy
{
public function classToTableName(\Doctrine\ORM\Mapping\ClassMetadata $class)
{
if ($class->getTableName() === 'legacy_table') {
return $class->getTableName(); // Skip prefix
}
return parent::classToTableName($class);
}
}
Update doctrine_prefix.yaml:
doctrine_prefix:
naming_strategy:
class: App\Doctrine\ORM\Mapping\ExtendedPrefixNamingStrategy
Custom Naming Strategies
Combine with Doctrine’s built-in strategies (e.g., UnderscoreNamingStrategy):
doctrine_prefix:
naming_strategy:
class: Doctrine\ORM\Mapping\UnderscoreNamingStrategy
arguments: ['CASE_LOWER', true] # Converts camelCase to snake_case
Result: User → app_users (table), createdAt → usr_created_at (column).
doctrine:migrations:diff after changing prefixes to avoid schema conflicts.doctrine/doctrine-fixtures-bundle fixtures to reflect prefixed column names.QueryBuilder with explicit aliases to avoid ambiguity:
$qb->select('u.usr_name as name') // Explicit alias for prefixed column
->from('App\Entity\User', 'u');
$builder->add('usr_name', TextType::class); // Matches DB column
Case Sensitivity
Prefixes are case-sensitive in some databases (e.g., PostgreSQL). Ensure consistency in naming_strategy arguments (e.g., CASE_LOWER).
Migration Conflicts Changing prefixes mid-project may break existing migrations. Use a new migration branch or database dumps to avoid corruption.
Circular Dependencies
Avoid prefixing tables/columns that reference each other (e.g., app_users → app_user_roles → app_roles). Use explicit foreign key names:
# config/packages/doctrine_prefix.yaml
doctrine_prefix:
foreign_key_prefix: "fk_"
Doctrine Cache Clear the metadata cache after changing prefixes:
php bin/console cache:clear
php bin/console doctrine:cache:clear-metadata
Verify Prefixes: Dump entity metadata to confirm prefixes:
php bin/console debug:container --parameter=doctrine_prefix
Or inspect an entity’s metadata:
$em->getClassMetadata(User::class)->getTableName(); // Should return "app_user"
SQL Logging: Enable logging to check generated queries:
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
logging: true
logging_format: '%%sql%%'
Dynamic Prefixes
Override PrefixNamingStrategy to fetch prefixes from a database or API:
class DynamicPrefixNamingStrategy extends PrefixNamingStrategy
{
public function tableName($className)
{
$prefix = $this->fetchPrefixFromService(); // Custom logic
return $prefix . '_' . parent::tableName($className);
}
}
Exclude Entities
Skip prefixing for specific entities (e.g., AuditLog):
// src/Doctrine/ORM/Mapping/ExcludedPrefixNamingStrategy.php
class ExcludedPrefixNamingStrategy extends PrefixNamingStrategy
{
public function classToTableName(ClassMetadata $class)
{
if ($class->getName() === AuditLog::class) {
return $class->getReflectionClass()->getShortName();
}
return parent::classToTableName($class);
}
}
Custom Prefix Logic
Use the onGenerateTableName and onGenerateColumnName events (Symfony 5.3+):
# config/services.yaml
Borsaco\DoctrinePrefixBundle\Doctrine\ORM\Mapping\PrefixNamingStrategy:
arguments:
$tablePrefixGenerator: '@custom.table_prefix_generator'
// src/Service/CustomTablePrefixGenerator.php
class CustomTablePrefixGenerator
{
public function generate(string $entityName): string
{
return match ($entityName) {
'User' => 'client_',
default => 'app_',
};
}
}
table_prefix: "" or column_prefix: "" to disable prefixes entirely.UnderscoreNamingStrategy, ensure it’s loaded before PrefixNamingStrategy in the chain:
doctrine_prefix:
naming_strategy:
class: Doctrine\ORM\Mapping\UnderscoreNamingStrategy
arguments: ['CASE_LOWER', true]
How can I help you explore Laravel packages today?