如何在symfony2中设置表前缀

时间:2011-09-21 17:33:18

标签: php symfony doctrine-orm

与问题主题一样,如何在symfony2中设置默认表格前缀?

最好是默认情况下可以为所有实体设置,但可以选择覆盖各个实体。

6 个答案:

答案 0 :(得分:46)

我自己刚刚想到这一点,我想详细说明如何实现这一目标。

Symfony 2&学说2.1
注意:我使用YML进行配置,这就是我要展示的内容。

说明

  1. 打开捆绑包的 Resources / config / services.yml

  2. 定义表前缀参数:
    请务必更改 mybundle myprefix _

    parameters:
        mybundle.db.table_prefix: myprefix_
    
  3. 添加新服务:

    services:
        mybundle.tblprefix_subscriber:
            class: MyBundle\Subscriber\TablePrefixSubscriber
            arguments: [%mybundle.db.table_prefix%]
            tags:
                - { name: doctrine.event_subscriber }
    
  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();
            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;
                }
            }
        }       
    }
    
  5. postgres用户的可选步骤:do something similary for sequences

  6. 享受

答案 1 :(得分:11)

替代答案

这是一个考虑到Doctrine2中可用的新功能的更新。

Doctrine2 naming strategy

Doctrine2使用NamingStrategy类来实现从类名转换为表名或从属性名转换为列名。

DefaultNamingStrategy只是找到“短类名”(没有名称空间)以推断表名。

UnderscoreNamingStrategy执行相同的操作,但它也会降低范围并“低估”“短名称”。

您的CustomNamingStrategy类可以扩展上述任一项(根据您的需要)并覆盖classToTableNamejoinTableName方法,以允许您指定应如何构造表名(使用前缀)。

例如,我的CustomNamingStrategy类扩展了UnderscoreNamingStrategy并根据命名空间约定查找了包名称,并将其用作所有表的前缀。


Symfony2 naming strategy

在Symfony2中使用上述内容需要将您的CustomNamingStragery类声明为服务,然后在配置中引用它:

doctrine:
    # ...

    orm:
        # ...
        #naming_strategy: doctrine.orm.naming_strategy.underscore
        naming_strategy: my_bundle.naming_strategy.prefixed_naming_strategy

利弊

优点:

  • 运行一段代码来执行一项任务 - 直接调用命名策略类并使用其输出;
  • 结构的清晰度 - 你没有使用事件运行代码来改变已经由其他代码构建的东西;
  • 更好地访问命名约定的所有方面;

缺点:

  • 对映射元数据的零访问 - 你只有作为参数给你的上下文(这也是一件好事,因为它强制约定而不是异常);
  • 需要学说2.3(现在不是那么多了,可能是在2011年提出这个问题的时候: - ));

答案 2 :(得分:1)

Simshaun的回答很好,但是如果你有一个single_table继承,并且在子实体上有关联,则会出现问题。当实体不是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;
                }
            }
        }
    }
}

这有一个缺点; 如果一个不明智的选择前缀实际上已经是表名的一部分,则可能会引发冲突。 例如。使用前缀&#39; co&#39;当有一个名为“内容”的表格时将导致一个没有前缀的表,所以使用像#co;&#39; co&_ 39;将降低这种风险。

答案 3 :(得分:0)

我没有在何时实施涉及捕获事件(性能问题)的解决方案,所以我尝试了替代解决方案,但它对我不起作用。 我正在添加JMSPaymentCoreBundle,并希望在付款表上添加前缀。 在此捆绑包中,表的定义位于Resources \ config \ doctrine目录(xml格式)中。 我终于找到了这个解决方案:

1)复制包含表格定义的doctrine目录并将其粘贴到我的主包

2)修改定义中表格的名称以添加前缀

3)在你的config.yml中,在doctrine / orm / entity manager / mapping部分声明它(dir是放置修改过的定义的目录):

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

答案 4 :(得分:0)

此外,您可以将此捆绑软件用于新版本的Symfony(4)-DoctrinePrefixBundle

答案 5 :(得分:-1)

@simshaun答案很好,但是多对多关系和继承存在问题。

如果您有父类User和子类Employee,并且Employee拥有多对多字段{{1} },此字段的表将没有前缀。 那是因为:

$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_user
  • pref_address

解决方案

解决方案可以在@simshaun的答案中修改第4点,如下所示:

  1. 创建 MyBundle \ Subscriber \ TablePrefixSubscriber.php

    employee_address
  2. 在验证类是否是继承的子代之前,我们处理多对多关系,并且我们添加<?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()); } } 只添加前缀一次,在拥有该领域的实体。