如何在Symfony2中设置表前缀

20

就像问题主题中的描述一样,我该如何在Symfony2中设置默认的数据表前缀?

最好是能够为所有实体默认设置,但同时具有覆盖个别实体的选项。


我找到了解决方案:https://www.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/sql-table-prefixes.html - canni
7个回答

46

我刚刚自己弄清楚了这个问题,我想解释一下如何准确地完成此操作。

Symfony 2和Doctrine 2.1
注意:我使用YML进行配置,所以我将展示YML的内容。

说明

  1. 打开您的bundle的Resources/config/services.yml文件

  2. 定义一个表前缀参数:
    请确保更改mybundlemyprefix_

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 用户,可选步骤: 类似地为序列执行一些操作

  • 享受吧!

  • 感谢您抽出时间编写这个! - antony
    可以,如果我正在使用多个实体管理器,那么我可以为每个实体管理器设置不同的前缀吗? - malloc4k
    你如何在第三方包中实现这个解决方案? - Kaz
    您已在MyBundle中注册了此服务,但它并不是基于每个bundle运行的。它涵盖了所有bundle中的所有实体。您如何更改此行为以基于每个bundle运行,以便不同的bundle可以具有不同的前缀?! - Peyman Mohamadpour
    Symfony v4.4 的数据表前缀配置应该是什么?请注意,setTableName 方法已被弃用。 - Rikijs
    @Rikijs 更现代的解决方案是实现自定义命名策略,就像下面链接的捆绑包所做的那样。(https://github.com/borsaco/DoctrinePrefixBundle) - simshaun

    12

    备选答案

    本文更新了Doctrine2中新特性的相关内容。

    Doctrine2 命名策略

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

    默认命名策略DefaultNamingStrategy仅查找“短类名”(不包括其命名空间)以推断表名。

    下划线命名策略UnderscoreNamingStrategy也执行相同的操作,但它还将“短类名”转换为小写并用下划线连接各部分。

    你的CustomNamingStrategy类可以扩展上述任何一个(根据具体情况),并覆盖classToTableNamejoinTableName方法,以允许你指定如何构建表名(使用前缀)。

    例如,我的CustomNamingStrategy类扩展了UnderscoreNamingStrategy,并基于命名约定找到bundle名称,然后将其用作所有表的前缀。


    Symfony2 命名策略

    在Symfony2中使用上述内容需要将你的CustomNamingStrategy类声明为服务,并在配置中引用它:

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

    优缺点

    优点:

    • 只需运行一个代码片段执行单一任务——直接调用命名策略类并使用其输出;
    • 结构清晰——不使用事件运行更改已由其他代码构建的内容的代码;
    • 更好地访问所有命名约定的方面。

    缺点:

    • 无法访问映射元数据——您只有作为参数提供给您的上下文(这也可能是一个好事,因为它强制执行规范而不是例外);
    • 需要Doctrine 2.3(现在不算太大的缺点了,在2011年提出这个问题时可能是)。


    不错,但现在如何设置自定义表名(无前缀),正如问题第二段所述? - Arkemlar
    每个实体都有一个注释 @ORM\Table(name="custom_name")。或者你的意思是当它被应用时如何避免前缀? - Mihai Stancu
    好的,我在某些情况下如何避免它? - Arkemlar
    据我所记,NamingStrategy不会覆盖您明确指定的@ORM\Table(name=...) - Mihai Stancu

    2

    Simshaun的回答很好,但是当您使用单表继承时,子实体上有关联关系时会出现问题。第一个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_”可以减少此风险。


    1

    0
    我不知道何时实现涉及捕获事件的解决方案(性能问题),因此我尝试了备选解决方案,但对我没有用。我正在添加JMSPaymentCoreBundle并希望在付款表上添加前缀。在这个bundle中,表的定义在Resources\config\doctrine目录下(xml格式)。我最终找到了这个解决方案:
    1)复制包含表定义的doctrine目录,并将其粘贴到我的主要bundle中。
    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
    

    0

    使用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'
    

    -1
    @simshaun的回答很好,但是在多对多关系和继承方面存在问题。
    如果您有一个父类User和一个子类Employee,并且Employee拥有一个Many-to-Many字段$addresses,则此字段的表将没有前缀。 这是因为:

    if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
        // 如果我们在继承层次结构中,只应用一次
        return;
    }
    

    User类(父类)

    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 {
    
    }
    

    Employee类(子类)

    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;
    }
    

    Address类(与Employee的关系)

    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点,如下所示:

    1. 创建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 . $

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接