Может ли Doctrine определять сгенерированные столбцы MySQL?
Правильно ли (и если да, то как) использовать сгенерированный столбец mySQL внутри сущностей Symfony?
Например, GENERATED ALWAYS
в приведенном ниже примере:
CREATE TABLE contacts (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
fullname varchar(101) GENERATED ALWAYS AS (concat(first_name,' ',last_name)),
email VARCHAR(100) NOT NULL
);
Я понимаю, что это может быть сделано в сущностях __construct()
но будет ли правильнее обрабатывать его с помощью Doctrine/mySQL? Что-то вроде:
/**
* @ORM\Column(type="text")
* @ORM\Generated(concat(first_name,' ',last_name))
*/
private $fullname;
2 ответа
Во-первых, я хотел бы извиниться за столь поздний ответ, но мне удалось создать обходной путь для базовых запросов SELECT и JOIN через DQL. Я не тестировал это с помощью запросов UPDATE в результате изменения полученного объекта.
Как отметил пользователь dbu выше, вам необходимо сначала выполнить это руководство:https://www.liip.ch/en/blog/doctrine-and-generated-columns
Это предотвращает попытку доктрины изменить сгенерированный столбец при запуске построителя схемы. При этом доктрина не игнорирует столбец при обновлении сущности.
Убедитесь, что вы создали миграцию, которая добавляет сгенерированный столбец. Я добился этого так:
/**
* Adds a generated column for fullname to the contacts table
*
* @param Schema $schema
*/
public function up(Schema $schema)
{
$this->addSql('
ALTER TABLE
contacts
ADD COLUMN
fullname varchar(101) GENERATED ALWAYS AS (concat(first_name,' ',last_name));
');
}
Имея вышеупомянутую основу, вы должны иметь возможность использовать доктрину конструктора схем для создания вашей схемы БД как обычно, и чтобы ваш сгенерированный столбец не мешал при добавлении через миграции.
Теперь следующая проблема состоит в том, чтобы гарантировать, что ваши данные могут быть перенесены в сущность контактов, не пытаясь изменить результат в базе данных во время запросов UPDATE и INSERT.
Уловка состоит в том, чтобы создать другую сущность, которая расширяет вашу текущую сущность "Контакты" и используется исключительно для запросов SELECT и JOIN.
<?php
namespace Embark\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Generated values from the contacts table
*
* @ORM\Table(name="contacts")
* @ORM\MappedSuperclass
*/
class ContactGenerated extends Contact
{
/**
*
* @ORM\Column(name="fullname", type="string", nullable=true)
*/
private $fullname;
}
Аннотация @MappedSuperClass предотвращает попытки построителя схемы доктрин создать таблицу с тем же именем.
Затем вы можете использовать DQL для получения данных:
$queryBuilder->select('contact')
->from(ContactGenerated::class, 'contact');
Это вернет вам массив объектов ContactGenerated. Вы столкнетесь с проблемами, если попытаетесь их устранить, вам действительно следует рассматривать их как только для чтения.
Я оставлю вам решать, как преобразовать их в стандартные классы контактов, которые удаляют несуществующее свойство "fullname", которое позволит вам выполнять запросы UPDATE из выбранного.
Я очень надеюсь, что это поможет вам, как и мне в моем случае использования - любые вопросы, не стесняйтесь спрашивать:)
Отображение сгенерированных столбцов в сущности Doctrine возможно, но оно имеет некоторые ограничения:
- Вы не можете сохранить новую сущность, потому что Doctrine попытается вставить ее в сгенерированный столбец, и это приведет к ошибке.
- Инструмент схемы (
orm:schema-tool:update --dump-sql
) всегда будет думать, что вам нужно изменить столбец.
Одним из возможных способов решения этой проблемы является использование триггеров, как в старые времена:
CREATE TABLE contacts (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
fullname varchar(101) NOT NULL,
email VARCHAR(100) NOT NULL
);
CREATE TRIGGER contacts_before_insert BEFORE INSERT ON contacts
FOR EACH ROW BEGIN
SET NEW.fullname = (CONCAT(NEW.first_name, ' ', NEW.last_name));
END;
CREATE TRIGGER contacts_before_update BEFORE UPDATE ON contacts
FOR EACH ROW BEGIN
SET NEW.fullname = (CONCAT(NEW.first_name, ' ', NEW.last_name));
END;
Другой возможный обходной путь - перемещение логики обновления в сущность, но тогда она не будет отражать изменения, выполненные непосредственно в базе данных.
Когда вам также нужно написать сущности, есть обходной путь, чтобы добавить сгенерированный столбец в SQL, но полностью скрыть его от Doctrine ORM с помощью прослушивателя схемы. Затем вы можете использовать этот столбец только в запросах Doctrine DBAL, но не в QueryBuilder/DQL. Я объяснил, как это работает в этом посте: https://www.liip.ch/en/blog/doctrine-and-generated-columns
Я верю, что это просто
/**
* @ORM\Column(name="fullname", type="text")
*/
private $fullname;
Данные будут прочитаны так же, как и из обычного столбца. Но вы не должны позволять редактировать это поле (не устанавливайте).