Стала тут передо мной задача, расширить некий функционал.
Приведенное наследование, может использоваться и в друхих ситуациях. Я же, просто описываю свой конкретный кейс.
Все есть “псевдо”-код
Дано: REST сервис который обрабатывает входящие данные. т.е: POST запрос с огомным payload.
Обработка: Реализован патерн Chain Of Responsibility в котором, собственно и происходит вся обработка.
Из входящего payload, цепь строит некую структуру связанных между собой объектов, обрабатывает их и пишет в базу.
Задача: Добавить дополнительные данные внутри цепи к вложенному объекту и отправить в другой сервис (не записывая его в базу – (почему? – это уже совсем другая история)).
Пример нискольких елементов цепи
<?php
namespase ApiBundle\Service\Processor;
use AppBundle\Entity\Data;
class DataProcessor extends AbstractProcessor {
public function process($requestData, $chainData)
{
$data = new Data();
...
$this->em->persist();
return $chainData;
}
}<?php
namespase ApiBundle\Service\Processor;
use AppBundle\Entity\Data;
class DummyRelation extends AbstractProcessor {
public function process($requestData, $chainData)
{
$data = new DummyRelation();
...
$this->em->persist();
return $chainData;
}
}В AbstractProcessor, также лежит логика, в конечном итоге, вызывая $this->em->flush();
Почему бы не написать обработку после выполнения цепи? – Основная причина это сложная структура данных, и если использовать, например, пост процессор, алгоритм обработки усложняеться на 2n раз. –
2n – ничем не обоснованное видение сложности автора. 🙂 А если, в случае с пост процессором, всю структуру (дерево) нужно обойти несколько раз.
Был выбран более эфективный, по мнению автора, алгоритм, который обрабатывает нужные данные, в нужном месте цепи, без обхода всех объектов (дерева).
Цель:
- Расширить класс сущности без создания таблицы в базе данных.
- Не захламлять класс entity полями которые не относятся к таблице.
- Использовать наследника для записи в базу, что бы не использовать всякого рода датамаперы.
Т.e. (моя структура (псевдо))
src/ - ApiBundle -- Model --- DataResponse.php --- DataResponse ---- SomeDummyRelationResponse.php ---- DummyRelationResponse.php -- Service --- DataProcessor.php --- AbstractDataProcessor.php --- DummyRelationProcessor.php - AppBundle -- Entity --- Data.php --- Data ---- SomeDummyRelation.php ---- DummyRelationResponse.php
Давайте посмотрим на пример кода (на самом деле псевдокода) сущности:
<?php
namespace AppBundle\Entity;
/**
* @ORM\Entity()
*/
class Data {
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Data\SomeDummyRelation")
*/
private $someDummyRelation;
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Data\DummyRelation")
*/
private $dummyRelation;
... // getters / setsrs
}В данном случае нам не важно что лежит в классах связи, их описывать я не буду (хотя может и стоило, напишите в комментариях).
Первоначально я просто отнаследовал модель от ентити, но из этого ничего не вышло и начались валится ероры.
[Doctrine\ORM\Mapping\MappingException] Class "Bla\Bla" sub class of "Blah\Blah" is not a valid entity or mapped super class.
Doctrine говорит нам, что есть класс, и он расширяет сущность. Сделайте что-нибудь, чтобы решить эту проблему, потому что я не знаю, как использовать этот класс как сущность или что-то другое? Для решения этой проблемы есть некоторое решение.
Для того что бы расширить класс сущности и не создавать новую таблицу в своей базе, я буду использовать MappedSuperClass. Посмотрим на примере:
<?php
namespace ApiBundle\Model;
use AppBundle\Entity\Data;
/**
*@ORM\MappedSuperclass
*/
class DataResponse extends Data {
/**
* @Serializer\Type("ApiBundle\Model\Data\SomeDummyRelation")
*/
private $someDummyRelation;
/**
* @Serializer\Type("ApiBundle\Model\Data\DummyRelation")
*/
private $dummyRelation;
}<?php
namespace ApiBundle\Model\Data;
use AppBundle\Entity\Data\SomeDummyRelation;
/**
*@ORM\MappedSuperclass
*/
class SomyDummyRelationResponse extends SomeDummyRelation {
...
}<?php
namespace ApiBundle\Model\Data;
use AppBundle\Entity\Data\DummyRelation;
/**
*@ORM\MappedSuperclass
*/
class DummyRelationResponse extends DummyRelation {
...
}Обновил свои процессоры:
<?php
namespase ApiBundle\Service\Processor;
use ApiBundle\Model\DataResponse;
class DataProcessor extends AbstractProcessor {
public function process($requestData, $chainData)
{
$data = new DataResponse();
...
$this->em->persist();
return $chainData;
}
}<?php
namespase ApiBundle\Service\Processor;
use ApiBundle\Model\Data\DummyRelationResponse;
class DummyRelation extends AbstractProcessor {
public function process($requestData, $chainData)
{
$data = new DummyRelationResponse();
...
$this->em->persist();
return $chainData;
}
}
Думал, заживем …
Появилась новая проблема. Я не могу записать в базу – что эсть плохо. А как мы помним в цепи вызываеться $this->em->persist() и в конце $this->em->flush().
MappedSuperclass – не может быть сущностью, он не зависит от запросов и постоянных отношений, определяемых MappedSuperclass, должены быть однонаправленными. Это означает, что ассоциации «один-ко-многим» вообще невозможны для MappedSuperclass. Кроме того, ассоциации Many-to-Many возможны только в том случае, если MappedSuperclass используется только в одном объекте на данный момент. Для дальнейшей поддержки наследования необходимо использовать одиночные или объединенные функции наследования таблиц.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html
Но все же выход из ситуации нашелся, так как я не хотел использовать пост обработку.
Вспомнил я что у Doctrine есть чудный ResolveTargetEntityListener который позволяет мапить Интерфейс модели на одну сущность.
В конечном итоге, я добавил немного в конфиг:
doctrine:
# ...
orm:
# ...
resolve_target_entities:
ApiBundle\Model\DataResponse: AppBundle\Entity\Data
ApiBundle\Model\DataResponse\SomeDummyRelationResponse: AppBundle\Entity\Data\SomeDummyRelation
ApiBundle\Modle\DataResponse\DummyRelationResponse: AppBundle\Entity\Data\DummyRelationС помощью ResolveTargetEntityListener вы можете разделить свои пакеты, сохраняя их структуру неизменной, но все же имея возможность определять отношения между различными объектами. Используя этот метод, ваши пакеты станет легче поддерживать.
https://symfony.com/doc/current/doctrine/resolve_target_entity.html
Описаный подход, не претендует быть “истинно правильным”. Если у вас есть возражения или пожелания, их можно оставить в комментариях.
Ну и на последок, как многие ютюб-блогеры – “Лайки ставим, на канал подписываемся, в коменты пишем, и т.д. и т.п”
P.S. Спасибо за потраченное время. Если есть мысли о том что может пойти не так в предложенной схеме, велкам в комменты.
