就像问题主题中的描述一样,我该如何在Symfony2中设置默认的数据表前缀?
最好是能够为所有实体默认设置,但同时具有覆盖个别实体的选项。
就像问题主题中的描述一样,我该如何在Symfony2中设置默认的数据表前缀?
最好是能够为所有实体默认设置,但同时具有覆盖个别实体的选项。
我刚刚自己弄清楚了这个问题,我想解释一下如何准确地完成此操作。
Symfony 2和Doctrine 2.1
注意:我使用YML进行配置,所以我将展示YML的内容。
打开您的bundle的Resources/config/services.yml文件
定义一个表前缀参数:
请确保更改mybundle和myprefix_
parameters:
mybundle.db.table_prefix: myprefix_
添加一个新的服务:
services:
mybundle.tblprefix_subscriber:
class: MyBundle\Subscriber\TablePrefixSubscriber
arguments: [%mybundle.db.table_prefix%]
tags:
- { name: doctrine.event_subscriber }
创建MyBundle\Subscriber\TablePrefixSubscriber.php
<?php
namespace MyBundle\Subscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefixSubscriber implements \Doctrine\Common\EventSubscriber
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function getSubscribedEvents()
{
return array('loadClassMetadata');
}
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$classMetadata = $args->getClassMetadata();
if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// if we are in an inheritance hierarchy, only apply this once
return;
}
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY
&& array_key_exists('name', $classMetadata->associationMappings[$fieldName]['joinTable']) ) { // Check if "joinTable" exists, it can be null if this field is the reverse side of a ManyToMany relationship
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}
对于 Postgres 用户,可选步骤: 类似地为序列执行一些操作
MyBundle
中注册了此服务,但它并不是基于每个bundle运行的。它涵盖了所有bundle中的所有实体。您如何更改此行为以基于每个bundle运行,以便不同的bundle可以具有不同的前缀?! - Peyman MohamadpoursetTableName
方法已被弃用。 - Rikijs本文更新了Doctrine2中新特性的相关内容。
Doctrine2使用实现将类名转换为表名或属性名转换为列名的NamingStrategy
类。
默认命名策略DefaultNamingStrategy
仅查找“短类名”(不包括其命名空间)以推断表名。
下划线命名策略UnderscoreNamingStrategy
也执行相同的操作,但它还将“短类名”转换为小写并用下划线连接各部分。
你的CustomNamingStrategy
类可以扩展上述任何一个(根据具体情况),并覆盖classToTableName
和joinTableName
方法,以允许你指定如何构建表名(使用前缀)。
例如,我的CustomNamingStrategy
类扩展了UnderscoreNamingStrategy
,并基于命名约定找到bundle名称,然后将其用作所有表的前缀。
在Symfony2中使用上述内容需要将你的CustomNamingStrategy
类声明为服务,并在配置中引用它:
doctrine:
# ...
orm:
# ...
#naming_strategy: doctrine.orm.naming_strategy.underscore
naming_strategy: my_bundle.naming_strategy.prefixed_naming_strategy
优点:
缺点:
@ORM\Table(name="custom_name")
。或者你的意思是当它被应用时如何避免前缀? - Mihai Stancu@ORM\Table(name=...)
。 - Mihai StancuSimshaun的回答很好,但是当您使用单表继承时,子实体上有关联关系时会出现问题。第一个if语句在实体不是rootEntity时返回,而这个实体可能仍然有需要前缀的关联关系。
我通过调整订阅者来解决了这个问题:
<?php
namespace MyBundle\Subscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
class TablePrefixSubscriber implements EventSubscriber
{
protected $prefix = '';
/**
* Constructor
*
* @param string $prefix
*/
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
/**
* Get subscribed events
*
* @return array
*/
public function getSubscribedEvents()
{
return array('loadClassMetadata');
}
/**
* Load class meta data event
*
* @param LoadClassMetadataEventArgs $args
*
* @return void
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$classMetadata = $args->getClassMetadata();
// Only add the prefixes to our own entities.
if (FALSE !== strpos($classMetadata->namespace, 'Some\Namespace\Part')) {
// Do not re-apply the prefix when the table is already prefixed
if (false === strpos($classMetadata->getTableName(), $this->prefix)) {
$tableName = $this->prefix . $classMetadata->getTableName();
$classMetadata->setPrimaryTable(['name' => $tableName]);
}
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide'] == true) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
// Do not re-apply the prefix when the association is already prefixed
if (false !== strpos($mappedTableName, $this->prefix)) {
continue;
}
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}
}
不过这种方法有一个缺陷: 如果前缀选择得不恰当,可能会导致与表名已有的部分发生冲突。 例如,在存在名为“content”的表时使用前缀“co”将导致出现无前缀的表,因此使用下划线“co_”可以减少此风险。
doctrine:
orm:
...
entity_managers:
default:
mappings:
...
JMSPaymentCoreBundle:
mapping: true
type: xml
dir: "%kernel.root_dir%/Resources/JMSPayment/doctrine"
alias: ~
prefix: JMS\Payment\CoreBundle\Entity
is_bundle: false
使用Symfony 6进行测试:
创建一个继承Doctrine的UnderscoreNamingStrategy
并处理前缀的类:
<?php
# src/Doctrine/PrefixedNamingStrategy.php
namespace App\Doctrine;
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
class PrefixedNamingStrategy extends UnderscoreNamingStrategy
{
private const PREFIX = 'sf';
public function classToTableName($className)
{
$underscoreTableName = parent::classToTableName($className);
return self::PREFIX . '_' . $underscoreTableName;
}
}
并配置Doctrine以使用它:
# config/packages/doctrine.yaml
doctrine:
orm:
naming_strategy: 'App\Doctrine\PrefixedNamingStrategy'
if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// 如果我们在继承层次结构中,只应用一次
return;
}
namespace FooBundle\Bar\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* @ORM\Entity()
* @ORM\Table(name="user")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="string")
* @ORM\DiscriminatorMap({"user" = "User", "employee" = "\FooBundle\Bar\Entity\Employee"})
*/
class User extends User {
}
namespace FooBundle\Bar\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* @ORM\Entity()
*/
class Employee extends FooBundle\Bar\Entity\User {
/**
* @var ArrayCollection $addresses
*
* @ORM\ManyToMany(targetEntity="\FooBundle\Bar\Entity\Adress")
* @ORM\JoinTable(name="employee_address",
* joinColumns={@ORM\JoinColumn(name="employee_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="address_id", referencedColumnName="id")}
* )
*/
private $addresses;
}
namespace FooBundle\Bar\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* @ORM\Entity()
* @ORM\Table(name="address")
*/
class Address {
}
使用原始解决方案,如果将pref_
前缀应用于此映射,则会得到以下表:
pref_user
pref_address
employee_address
一种解决方案是修改@simshaun的答案中的第4点,如下所示:
创建MyBundle\Subscriber\TablePrefixSubscriber.php
<?php
namespace MyBundle\Subscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefixSubscriber implements \Doctrine\Common\EventSubscriber
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function getSubscribedEvents()
{
return array('loadClassMetadata');
}
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$classMetadata = $args->getClassMetadata();
// 将Many-yo-Many验证放在“继承”验证之前。否则,不会考虑子实体的字段
foreach($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY
&& array_key_exists('name', $classMetadata->associationMappings[$fieldName]['joinTable']) // 检查“joinTable”是否存在,如果此字段是ManyToMany关系的反向侧,则可以为null
&& $mapping['sourceEntity'] == $classMetadata->getName() // 如果这不是继承映射的根实体,但“子”实体拥有该字段,请在表名前缀上添加表。
) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
if($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// 如果我们在继承层次结构中,只应用一次
return;
}
$classMetadata->setTableName($this->prefix . $