Как настроить префикс таблицы в symfony2

Как в вопросе вопроса, как настроить префикс таблицы по умолчанию в symfony2?

Лучше всего, если он может быть установлен по умолчанию для всех объектов, но с возможностью переопределения для отдельных.

Ответы

Ответ 1

Только подумав об этом, я хотел бы пролить свет на то, как это сделать.

Symfony 2 и Doctrine 2.1
Примечание. Я использую YML для конфигурации, так что я буду показывать.

Инструкции

  • Откройте свой пакет Ресурсы /config/services.yml

  • Определите параметр префикса таблицы:
    Обязательно измените mybundle и myprefix _

    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: сделать что-то похожее для последовательностей

  • Enjoy

Ответ 2

Альтернативный ответ

Это обновление с учетом новых функций, доступных в Doctrine2.

Стратегия именования Doctrine2

Doctrine2 использует классы NamingStrategy, которые реализуют преобразование из имени класса в имя таблицы или из имени свойства в имя столбца.

DefaultNamingStrategy просто находит "короткое имя класса" (без его пространства имен), чтобы вывести имя таблицы.

UnderscoreNamingStrategy делает то же самое, но также уменьшает и "подчёркивает" "короткое имя класса".

Ваш класс CustomNamingStrategy может расширить один из указанных выше (по вашему усмотрению) и переопределить методы classToTableName и joinTableName, чтобы вы могли указать, как должно быть построено имя таблицы (с использованием префикс).

Например, мой класс CustomNamingStrategy расширяет UnderscoreNamingStrategy и находит имя пакета на основе соглашений об именах и использует это как префикс для всех таблиц.


Стратегия именования Symfony2

Использование вышеприведенного в Symfony2 требует объявления вашего класса CustomNamingStragery в качестве службы, а затем ссылки на него в вашей конфигурации:

doctrine:
    # ...

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

Плюсы и минусы

Плюсы:

  • запуск одного фрагмента кода для выполнения одной задачи - ваш класс стратегии именования вызывается непосредственно и используется его выход;
  • Ясность структуры - вы не используете события для запуска кода, которые изменяют вещи, которые уже были созданы другим кодом;
  • лучший доступ ко всем аспектам соглашений об именах;

Минусы:

  • нулевой доступ к метаданным сопоставления - у вас есть только тот контекст, который был предоставлен вам в качестве параметров (это тоже может быть полезно, потому что оно выдает соглашение, а не исключение);
  • нужна доктрина 2.3 (не такая уж большая проблема теперь, возможно, в 2011 году, когда задавали этот вопрос: -));

Ответ 4

Ответ Simshaun работает нормально, но имеет проблему, когда у вас есть однонаправленное наследование, с ассоциациями на дочернем объекте. Первый if-statement возвращает, когда объект не является 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;
                }
            }
        }
    }
}

У этого есть недостаток; Неправильно выбранный префикс может вызвать конфликты, когда он фактически уже является частью имени таблицы. Например. используя префикс 'co', когда theres table, называемый "content", приведет к таблице без префикса, поэтому использование подчеркивания типа "co_" уменьшит этот риск.

Ответ 5

Я не использую решение, которое связано с событием catching (проблема с производительностью), поэтому я попробовал альтернативное решение, но оно не работает для меня. Я добавлял JMSPaymentCoreBundle и хотел добавить префикс в таблицы платежей. В этом пакете определение таблиц содержится в каталоге ресурсов \config\doctrine (формат xml). Я наконец нашел это решение:

1) скопируйте каталог доктрин, содержащий определения в таблице, и вставьте его в мой основной пакет

2) измените имя таблиц в определениях, чтобы добавить префикс

3) объявите его в вашем файле config.yml в разделе doctrine/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

Также вы можете использовать этот комплект для новой версии Symfony (4) - DoctrinePrefixBundle

Ответ 7

Ответ на

@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

    <?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->getName(), чтобы добавить префикс только один раз, на владеющую сущность поля.