【发布时间】:2011-09-21 17:33:17
【问题描述】:
和问题一样,如何在 symfony2 中设置默认表前缀?
如果可以默认为所有实体设置最好,但可以选择覆盖单个实体。
【问题讨论】:
标签: php symfony doctrine-orm
和问题一样,如何在 symfony2 中设置默认表前缀?
如果可以默认为所有实体设置最好,但可以选择覆盖单个实体。
【问题讨论】:
标签: php symfony doctrine-orm
我自己刚刚弄清楚了这一点,我想具体说明一下如何实现这一点。
Symfony 2 和 Doctrine 2.1
注意:我使用 YML 进行配置,所以这就是我要展示的内容。
打开你的包的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 用户的可选步骤:do something similary for sequences
【讨论】:
MyBundle 中注册了此服务,但它不会在每个捆绑包的基础上运行。它涵盖了所有捆绑包中的所有实体。你怎么能改变这种行为以在每个包的基础上行动,以便不同的包可以有不同的前缀?!
setTableName 是不推荐使用的方法。
这是一个考虑到 Doctrine2 中可用的新功能的更新。
Doctrine2 使用NamingStrategy 类实现从类名到表名或从属性名到列名的转换。
DefaultNamingStrategy 只是找到“短类名”(没有它的命名空间)以推断表名。
UnderscoreNamingStrategy 做同样的事情,但它也小写并“强调”“短类名”。
您的 CustomNamingStrategy 类可以扩展上述任一方法(如您所见)并覆盖 classToTableName 和 joinTableName 方法,以允许您指定应如何构造表名(使用前缀)。
例如,我的 CustomNamingStrategy 类扩展了 UnderscoreNamingStrategy 并根据命名空间约定查找包名称并将其用作所有表的前缀。
在 Symfony2 中使用上述内容需要将您的 CustomNamingStragery 类声明为服务,然后在您的配置中引用它:
doctrine:
# ...
orm:
# ...
#naming_strategy: doctrine.orm.naming_strategy.underscore
naming_strategy: my_bundle.naming_strategy.prefixed_naming_strategy
优点:
缺点:
【讨论】:
@ORM\Table(name="custom_name")都有一个注解。或者你的意思是当它被应用时如何避免前缀?
@ORM\Table(name=...)。
Simshaun 的答案工作正常,但是当您有一个单表继承时会出现问题,并且在子实体上有关联。当实体不是 rootEntity 时,第一个 if 语句返回,而该实体可能仍具有必须添加前缀的关联。
我通过将订阅者调整为以下内容来解决此问题:
<?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_' 这样的下划线会降低这种风险。
【讨论】:
此外,您可以将此捆绑包用于新版本的 Symfony (4) - DoctrinePrefixBundle
【讨论】:
我不知道何时实施涉及捕获事件(性能问题)的解决方案,因此我尝试了替代解决方案,但它对我不起作用。 我正在添加 JMSPaymentCoreBundle 并想在付款表上添加一个前缀。 在这个包中,表的定义在 Resources\config\doctrine 目录(xml 格式)中。 我终于找到了这个解决方案:
1) 复制包含表上定义的学说目录并将其粘贴到我的主包中
2) 修改定义中的表名以添加您的前缀
3) 在您的 config.yml 中,在教义/orm/entity manager/mapping 部分中声明它(目录是您放置修改后定义的目录):
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
【讨论】:
@simshaun 回答很好,但是多对多关系和继承存在问题。
如果您有一个父类User 和一个子类Employee,并且Employee 拥有一个Many-to-Many 字段$addresses,则该字段的表将没有一个前缀。
那是因为:
if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// if we are in an inheritance hierarchy, only apply this once
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_userpref_addressemployee_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();
// Put the Many-yo-Many verification before the "inheritance" verification. Else fields of the child entity are not taken into account
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
&& $mapping['sourceEntity'] == $classMetadata->getName() // If this is not the root entity of an inheritance mapping, but the "child" entity is owning the field, prefix the table.
) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
if($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// if we are in an inheritance hierarchy, only apply this once
return;
}
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
}
}
这里我们在验证类是否是继承的子类之前处理 多对多 关系,并且我们添加 $mapping['sourceEntity'] == $classMetadata->getName() 以仅在拥有实体上添加一次前缀领域。
【讨论】: