Как сохранить объект со ссылкой на себя с помощью Doctrine?

#php #postgresql #doctrine-orm #doctrine

Вопрос:

У меня есть следующая схема, где TestEntity#testEntity есть сама ссылка и не может быть NULL .

 #[ORMEntity)]
class TestEntity
{
    #[ORMId]
    #[ORMGeneratedValue]
    #[ORMColumn(type: 'integer')]
    private $id;

    #[ORMColumn(type: 'string', length: 255)]
    private $name;

    #[ORMManyToOne(targetEntity: TestEntity::class)]
    #[ORMJoinColumn(nullable: false)]
    private $testEntity;
    
    // Standard setters and getters
}
 

Мой скрипт для создания и сохранения объекта выглядит следующим образом:

 $testEntity = new AppEntityTestEntity();
$testEntity
->setName('hello')
->setTestEntity($testEntity)
;
$output->writeln(sprintf('BEFORE persist: Parent: name: %s id: %d Child: name: %s id: %d', $testEntity->getName(), $testEntity->getId(), $testEntity->getTestEntity()->getName(), $testEntity->getTestEntity()->getId()));
$this->entityManager->persist($testEntity);
$output->writeln(sprintf('AFTER persist:  Parent: name: %s id: %d Child: name: %s id: %d', $testEntity->getName(), $testEntity->getId(), $testEntity->getTestEntity()->getName(), $testEntity->getTestEntity()->getId()));
try{
    $this->entityManager->flush();
}
catch(Exception $exception) {
    $output->writeln($exception->getMessage());
    return Command::FAILURE;
}
 

Я выполняю скрипт и получаю следующую ошибку:

 BEFORE persist: Parent: name: hello id: 0 Child: name: hello id: 0
AFTER persist:  Parent: name: hello id: 7 Child: name: hello id: 7
An exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7
ERROR:  null value in column "test_entity_id" of relation "test_entity" violates not-null constraint
DETAIL:  Failing row contains (7, null, hello).
 

Почему это не удается? Если бы я не использовал Doctrine и использовал собственные SQL-запросы с PostgreSQL, я понял, что мне нужно будет использовать отложенные ограничения. Я не знаю, использует ли Doctrine отложенные ограничения, но независимо от того, насколько видно из вывода, Doctrine достаточно умен, чтобы установить PK после сохранения объекта. Как это можно сделать?

Комментарии:

1. Не похоже, что это сопоставление схемы было бы возможно с использованием диспетчера сущностей из-за того, что операция сброса все еще вставляется null изначально, а затем выполняется UPDATE впоследствии, чтобы связать вставленный идентификатор. Даже если идентификатор задан вручную… Кажется, единственным вариантом использования entity manager было бы разрешить nullable с проверкой для объекта.

2. @WillB. Спасибо, Уилл, в итоге пришел к тому же выводу. Если идентификатор указан вручную, то я получу ошибку ограничения FK, если не будут использованы отложенные ограничения, которые, очевидно, не поддерживаются Doctrine, поскольку все базы данных не поддерживают. Надеюсь, Doctrine не тратит запрос на получение PK во время операции сохранения, но не уверен, как еще он устанавливается. Не стесняйтесь добавлять ответ как таковой, и я был бы рад выбрать его.

3. Согласно документации, сгенерированный PK доступен во время postPersist . «Сгенерированные значения первичного ключа доступны в событии postPersist». Вы можете вручную выполнить инструкцию $em->getConnection()->executeStatement('SET CONSTRAINTS fk_test_entity_id DEFEERED') , но у меня это не сработало даже во время транзакции или установки столбца вручную DEFERRABLE INITIALLY DEFERRED Из-за INSERT null , UPDATE test_entity_id = id .

4. Строка кода, ссылающаяся на операцию после вставки для новых ассоциаций сущностей в логике persistNew. Кажется, что ссылка на PK назначается с помощью SELECT NEXTVAL('test_entity_id_seq')

5. @WillB. Хорошая детективная работа! Также выглядит как субоптимизированный запрос Doctrine. Не то чтобы я жаловался, поскольку чрезмерно оптимизированный, на мой взгляд, хуже, чем субоптимизированный, но все же полезно знать.