简体   繁体   English

如何在 symfony2 中设置表前缀

[英]How to setup table prefix in symfony2

Like in question topic, how can I setup default table prefix in symfony2?就像问题主题一样,如何在 symfony2 中设置默认表前缀?

The best if it can be set by default for all entities, but with option to override for individual ones.如果可以默认为所有实体设置它是最好的,但可以选择覆盖单个实体。

Having just figured this out myself, I'd like to shed some light on exactly how to accomplish this.我自己刚刚想通了这一点,我想说明一下如何做到这一点。

Symfony 2 & Doctrine 2.1 Symfony 2 和教义 2.1
Note: I use YML for config, so that's what I'll be showing.注意:我使用 YML 进行配置,所以这就是我要展示的内容。

Instructions指示

  1. Open up your bundle's Resources/config/services.yml打开你的 bundle 的Resources/config/services.yml

  2. Define a table prefix parameter:定义表前缀参数:
    Be sure to change mybundle and myprefix_一定要更改mybundlemyprefix_

     parameters: mybundle.db.table_prefix: myprefix_
  3. Add a new service:添加新服务:

     services: mybundle.tblprefix_subscriber: class: MyBundle\\Subscriber\\TablePrefixSubscriber arguments: [%mybundle.db.table_prefix%] tags: - { name: doctrine.event_subscriber }
  4. Create MyBundle\\Subscriber\\TablePrefixSubscriber.php创建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. Optional step for postgres users: do something similary for sequences postgres 用户的可选步骤: 对序列做一些类似的事情

  6. Enjoy享受

Alternate answer替代答案

This is an update taking into account the newer features available in Doctrine2.这是考虑到 Doctrine2 中可用的新功能的更新。

Doctrine2 naming strategy Doctrine2 命名策略

Doctrine2 uses NamingStrategy classes which implement the conversion from a class name to a table name or from a property name to a column name. Doctrine2 使用NamingStrategy类来实现从类名到表名或从属性名到列名的转换。

The DefaultNamingStrategy just finds the "short class name" (without its namespace) in order to deduce the table name. DefaultNamingStrategy只是找到“短类名”(没有其命名空间)以推断表名。

The UnderscoreNamingStrategy does the same thing but it also lowercases and "underscorifies" the "short class name". UnderscoreNamingStrategy做同样的事情,但它也小写并“强调”“短类名”。

Your CustomNamingStrategy class could extend either one of the above (as you see fit) and override the classToTableName and joinTableName methods to allow you to specify how the table name should be constructed (with the use of a prefix).您的CustomNamingStrategy类可以扩展上述任一类(如您所见)并覆盖classToTableNamejoinTableName方法,以允许您指定应如何构造表名(使用前缀)。

For example my CustomNamingStrategy class extends the UnderscoreNamingStrategy and finds the bundle name based on the namespacing conventions and uses that as a prefix for all tables.例如,我的CustomNamingStrategy类扩展了UnderscoreNamingStrategy并根据命名空间约定查找包名称并将其用作所有表的前缀。


Symfony2 naming strategy Symfony2 命名策略

Using the above in Symfony2 requires declaring your CustomNamingStragery class as a service and then referencing it in your config:在 Symfony2 中使用上述内容需要将您的CustomNamingStragery类声明为服务,然后在您的配置中引用它:

doctrine:
    # ...

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

Pros and cons利弊

Pros:优点:

  • running one piece of code to do one single task -- your naming strategy class is called directly and its output is used;运行一段代码来完成一项任务——你的命名策略类被直接调用并使用其输出;
  • clarity of structure -- you're not using events to run code which alter things that have already been built by other code;结构清晰——你没有使用事件来运行改变其他代码已经构建的东西的代码;
  • better access to all aspects of the naming conventions;更好地访问命名约定的所有方面;

Cons:缺点:

  • zero access to mapping metadata -- you only have the context that was given to you as parameters (this can also be a good thing because it forces convention rather than exception);对映射元数据的零访问权限——您只有作为参数提供给您的上下文(这也可能是一件好事,因为它强制约定而不是例外);
  • needs doctrine 2.3 (not that much of a con now, it might have been in 2011 when this question was asked :-));需要学说 2.3(现在不是那么严重,可能是在 2011 年提出这个问题时:-));

Simshaun's answer works fine, but has a problem when you have a single_table inheritance, with associations on the child entity. Simshaun 的回答工作正常,但是当您有一个 single_table 继承时会出现问题,并且在子实体上有关联。 The first if-statement returns when the entity is not the rootEntity, while this entity might still have associations that have to be prefixed.当实体不是 rootEntity 时,第一个 if 语句返回,而该实体可能仍然具有必须加前缀的关联。

I fixed this by adjusting the subscriber to the following:我通过将订阅者调整为以下内容来解决此问题:

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

This has a drawback though;不过这有一个缺点; A not wisely chosen prefix might cause conflicts when it's actually already part of a table name.当前缀实际上已经是表名的一部分时,未明智选择的前缀可能会导致冲突。 Eg using prefix 'co' when theres a table called 'content' will result in a non-prefixed table, so using an underscore like 'co_' will reduce this risk.例如,当存在名为“content”的表时使用前缀“co”将导致非前缀表,因此使用像“co_”这样的下划线将降低这种风险。

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

I don't when to implement a solution that involved catching event (performance concern), so I have tried the Alternate Solution but it doesn't work for me.我不知道何时实施涉及捕获事件(性能问题)的解决方案,所以我尝试了替代解决方案,但它对我不起作用。 I was adding the JMSPaymentCoreBundle and wanted to add a prefix on the payment tables.我正在添加 JMSPaymentCoreBundle 并想在支付表上添加一个前缀。 In this bundle, the definition of the tables are in the Resources\\config\\doctrine directory (xml format).在此包中,表的定义位于 Resources\\config\\doctrine 目录(xml 格式)中。 I have finally found this solution:我终于找到了这个解决方案:

1) copy doctrine directory containing the definitions on the table and paste it in my main bundle 1)复制包含表上定义的学说目录并将其粘贴到我的主包中

2) modify the name of the tables in the definitions to add your prefix 2)修改定义中表的名称以添加您的前缀

3) declare it in your config.yml, in the doctrine/orm/entity manager/mapping section (the dir is the directory where you have put the modified definitions): 3) 在你的 config.yml 中声明它,在 dotric/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

You can use this bundle: 你可以使用这个包:

DoctrinePrefixBundle DoctrinePrefixBundle

@simshaun answer is good, but there is a problem with Many-to-Many relationships and inheritance. @simshaun 的回答很好,但是多对多关系和继承存在问题。

If you have a parent class User and a child class Employee , and the Employee own a Many-to-Many field $addresses , this field's table will not have a prefix.如果您有一个父类User和一个子类Employee ,并且Employee拥有多对多字段$addresses ,则该字段的表将没有前缀。 That is because of:那是因为:

if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
    // if we are in an inheritance hierarchy, only apply this once
    return;
}

User class (parent)用户类(父类)

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 class (child)员工班(子)

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 class (relation with Employee)地址类(与Employee的关系)

namespace FooBundle\Bar\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * User
 *
 * @ORM\Entity()
 * @ORM\Table(name="address")
 */
class Address {

}

With the original solution, if you apply pref_ prefixe to this mapping, you will end up with tables :使用原始解决方案,如果您将pref_前缀应用于此映射,您将最终得到表:

  • pref_user
  • pref_address
  • employee_address

Solution解决方案

A solution can be to modify, in the answer of @simshaun, the point 4 like this:解决方案可以是在@simshaun 的回答中修改第 4 点,如下所示:

  1. Create MyBundle\\Subscriber\\TablePrefixSubscriber.php创建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()); } }

Here we handle the Many-to-Many relationship before verifying if the class is the child of an inheritance, and we add $mapping['sourceEntity'] == $classMetadata->getName() to add the prefix only one time, on the owning entity of the field.这里我们在验证类是否是继承的子类之前处理多对多关系,我们添加$mapping['sourceEntity'] == $classMetadata->getName()只添加一次前缀,在字段的拥有实体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM