Как настроить префикс таблицы в 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 использует классы NamingStrategy
, которые реализуют преобразование из имени класса в имя таблицы или из имени свойства в имя столбца.
DefaultNamingStrategy
просто находит "короткое имя класса" (без его пространства имен), чтобы вывести имя таблицы.
UnderscoreNamingStrategy
делает то же самое, но также уменьшает и "подчёркивает" "короткое имя класса".
Ваш класс CustomNamingStrategy
может расширить один из указанных выше (по вашему усмотрению) и переопределить методы classToTableName
и joinTableName
, чтобы вы могли указать, как должно быть построено имя таблицы (с использованием префикс).
Например, мой класс CustomNamingStrategy
расширяет UnderscoreNamingStrategy
и находит имя пакета на основе соглашений об именах и использует это как префикс для всех таблиц.
Использование вышеприведенного в Symfony2 требует объявления вашего класса CustomNamingStragery
в качестве службы, а затем ссылки на него в вашей конфигурации:
doctrine:
# ...
orm:
# ...
#naming_strategy: doctrine.orm.naming_strategy.underscore
naming_strategy: my_bundle.naming_strategy.prefixed_naming_strategy
Плюсы и минусы
Плюсы:
- запуск одного фрагмента кода для выполнения одной задачи - ваш класс стратегии именования вызывается непосредственно и используется его выход;
- Ясность структуры - вы не используете события для запуска кода, которые изменяют вещи, которые уже были созданы другим кодом;
- лучший доступ ко всем аспектам соглашений об именах;
Минусы:
- нулевой доступ к метаданным сопоставления - у вас есть только тот контекст, который был предоставлен вам в качестве параметров (это тоже может быть полезно, потому что оно выдает соглашение, а не исключение);
- нужна доктрина 2.3 (не такая уж большая проблема теперь, возможно, в 2011 году, когда задавали этот вопрос: -));
Ответ 3
Я нашел решение: http://www.doctrine-project.org/docs/orm/2.0/en/cookbook/sql-table-prefixes.html
Ответ 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 следующим образом:
-
Создать 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()
, чтобы добавить префикс только один раз, на владеющую сущность поля.