Может ли 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;

Данные будут прочитаны так же, как и из обычного столбца. Но вы не должны позволять редактировать это поле (не устанавливайте).

Другие вопросы по тегам