如何在symfony2中设置表前缀

发布于 2024-12-06 07:42:50 字数 79 浏览 0 评论 0原文

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

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

Like in question topic, how can I setup default table prefix in symfony2?

The best if it can be set by default for all entities, but with option to override for individual ones.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

拿命拼未来 2024-12-13 07:42:50

在我自己弄清楚这一点之后,我想详细说明如何实现这一目标。

Symfony 2 和 Symfony 2教义2.1

注意:我使用 YML 进行配置,因此这就是我将要展示的内容。

说明

  1. 打开捆绑包的资源/配置/服务。 yml

  2. 定义表前缀参数:

    请务必更改 mybundlemyprefix_

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

    <前><代码>服务:
    mybundle.tblprefix_subscriber:
    类:MyBundle\Subscriber\TablePrefixSubscriber
    参数:[%mybundle.db.table_prefix%]
    标签:
    - { 名称:doctrine.event_subscriber }

  4. 创建MyBundle\Subscriber\TablePrefixSubscriber.php

    <前><代码>前缀 = (字符串) $prefix;
    }

    公共函数 getSubscribedEvents()
    {
    返回数组('loadClassMetadata');
    }

    公共函数 loadClassMetadata(LoadClassMetadataEventArgs $args)
    {
    $classMetadata = $args->getClassMetadata();
    if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
    // 如果我们处于继承层次结构中,则仅应用一次
    返回;
    }

    $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']) ) { // 检查“joinTable”是否存在,如果该字段是多对多关系的反面,则可以为 null
    $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
    $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix 。 $mappedTableName;
    }
    }
    }
    }

  5. postgres 用户的可选步骤:对序列做类似的事情

  6. 享受

Having just figured this out myself, I'd like to shed some light on exactly how to accomplish this.

Symfony 2 & Doctrine 2.1

Note: I use YML for config, so that's what I'll be showing.

Instructions

  1. Open up your bundle's Resources/config/services.yml

  2. Define a table prefix parameter:

    Be sure to change mybundle and myprefix_

    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

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

  6. Enjoy
假面具 2024-12-13 07:42:50

替代答案

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

Doctrine2 命名策略

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

DefaultNamingStrategy 只是查找“短类名”(不带其命名空间)来推断表名。

UnderscoreNamingStrategy 执行相同的操作,但它也会小写并“下划线化”“短类名”。

您的 CustomNamingStrategy 类可以扩展上述任一方法(如您认为合适)并重写 classToTableNamejoinTableName 方法,以允许您指定如何应构造表名(使用前缀)。

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


Symfony2 命名策略

在 Symfony2 中使用上述内容需要声明您的 < code>CustomNamingStrategy 类作为一项服务,然后在配置中引用它:

doctrine:
    # ...

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

优点和缺点

优点:

  • 运行一段代码来完成一项任务 --直接调用您的命名策略类并使用其输出;
  • 结构清晰——您没有使用事件来运行改变其他代码已经构建的内容的代码;
  • 更好地访问命名约定的各个方面;

缺点:

  • 对映射元数据的零访问——您只有作为参数提供给您的上下文(这也可能是一件好事,因为它强制约定而不是例外);
  • 需要原则 2.3(现在还没有那么严重,可能是在 2011 年提出这个问题的时候:-));

Alternate answer

This is an update taking into account the newer features available in Doctrine2.

Doctrine2 naming strategy

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.

The DefaultNamingStrategy just finds the "short class name" (without its namespace) in order to deduce the table name.

The UnderscoreNamingStrategy does the same thing but it also lowercases and "underscorifies" the "short class name".

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).

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.


Symfony2 naming strategy

Using the above in Symfony2 requires declaring your CustomNamingStragery class as a service and then referencing it in your config:

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 :-));
叹沉浮 2024-12-13 07:42:50

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

但这有一个缺点;
当前缀实际上已经是表名的一部分时,选择不明智的前缀可能会导致冲突。
例如,当存在名为“content”的表时使用前缀“co”将导致无前缀表,因此使用像“co_”这样的下划线将降低这种风险。

Simshaun's answer works fine, but has a problem when you have a single_table inheritance, with associations on the child entity. The first if-statement returns when the entity is not the rootEntity, while this entity might still have associations that have to be prefixed.

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.
E.g. 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.

最冷一天 2024-12-13 07:42:50

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

Also, you can use this bundle for the new version of Symfony (4) - DoctrinePrefixBundle

等你爱我 2024-12-13 07:42:50

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

1)复制包含表上定义的学说目录并将其粘贴到我的主包中

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

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.
In this bundle, the definition of the tables are in the Resources\config\doctrine directory (xml format).
I have finally found this solution:

1) copy doctrine directory containing the definitions on the table and paste it in my main bundle

2) modify the name of the tables in the definitions to add your prefix

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):

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
旧人哭 2024-12-13 07:42:50

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

tested with Symfony 6 :

Create a class that extends Doctrine's UnderscoreNamingStrategy and handles the prefix :

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

and configure doctrine to use it :

# config/packages/doctrine.yaml

doctrine:
    orm:
        naming_strategy: 'App\Doctrine\PrefixedNamingStrategy'
水中月 2024-12-13 07:42:50

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

如果您有一个父类 User 和一个子类 Employee,并且 Employee 拥有一个多对多 字段$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

    <前><代码>前缀 = (字符串) $prefix;
    }

    公共函数 getSubscribedEvents()
    {
    返回数组('loadClassMetadata');
    }

    公共函数 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”是否存在,如果该字段是多对多关系的反向,则可以为 null
    && $mapping['sourceEntity'] == $classMetadata->getName() // 如果这不是继承映射的根实体,但“子”实体拥有该字段,则为表添加前缀。
    ){
    $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
    $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix 。 $mappedTableName;
    }
    }

    if($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
    // 如果我们处于继承层次结构中,则仅应用一次
    返回;
    }

    $classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
    }
    }

在这里,我们在验证该类是否是某个类的子类之前处理多对多关系继承,并且我们添加 $mapping['sourceEntity'] == $classMetadata->getName() 以在字段的所属实体上仅添加一次前缀。

@simshaun answer is good, but there is a problem with Many-to-Many relationships and inheritance.

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.
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)

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

Solution

A solution can be to modify, in the answer of @simshaun, the point 4 like this:

  1. Create 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文