【问题标题】:How to setup table prefix in symfony2如何在 symfony2 中设置表前缀
【发布时间】:2011-09-21 17:33:17
【问题描述】:

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

如果可以默认为所有实体设置最好,但可以选择覆盖单个实体。

【问题讨论】:

标签: php symfony doctrine-orm


【解决方案1】:

我自己刚刚弄清楚了这一点,我想具体说明一下如何实现这一点。

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

说明

  1. 打开你的包的Resources/config/services.yml

  2. 定义一个表前缀参数:
    一定要更改 mybundlemyprefix_

    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. 享受

【讨论】:

  • 感谢您花时间写这篇文章!
  • 太棒了。但是,如果我与多个实体管理器合作,我可以为每个实体管理器设置不同的前缀吗?
  • 您如何在第三方捆绑包中实施此解决方案?
  • 您已在MyBundle 中注册了此服务,但它不会在每个捆绑包的基础上运行。它涵盖了所有捆绑包中的所有实体。你怎么能改变这种行为以在每个包的基础上行动,以便不同的包可以有不同的前缀?!
  • Symfony v4.4 的表前缀配置是什么?请注意,setTableName 是不推荐使用的方法。
【解决方案2】:

备选答案

这是一个考虑到 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 年提出这个问题时 :-));

【讨论】:

  • 很好,但是现在如何设置自定义表名(不带前缀),如问题第二段中所述?
  • 每个实体@ORM\Table(name="custom_name")都有一个注解。或者你的意思是当它被应用时如何避免前缀?
  • 对,在某些情况下如何避免呢?
  • 据我所知,NamingStrategy 不会覆盖您的显式 @ORM\Table(name=...)
【解决方案3】:

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_' 这样的下划线会降低这种风险。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      我不知道何时实施涉及捕获事件(性能问题)的解决方案,因此我尝试了替代解决方案,但它对我不起作用。 我正在添加 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
      

      【讨论】:

        【解决方案6】:

        @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_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();
          
                  // 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-&gt;getName() 以仅在拥有实体上添加一次前缀领域。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-02-15
          • 1970-01-01
          • 2015-01-10
          • 2015-04-01
          • 2015-02-08
          • 1970-01-01
          • 2015-09-22
          相关资源
          最近更新 更多