Древовидная структура с гидратированной моделью в Pomm-проекте

У меня есть простая таблица базы данных, которая реализует древовидную структуру через атрибут parent_id. Что-то вроде этого:

+----+------+-----------+
| id | name | parent_id |
+----+------+-----------+
|  1 | test | null      |
+----+------+-----------+
|  2 | tes2 | 1         |
+----+------+-----------+
|  3 | tes3 | 2         |
+----+------+-----------+
|  4 | tst  | 2         |
+----+------+-----------+

Я хочу получить объект PHP с древовидной структурой. Таким образом, объект Category будет иметь подкатегории свойств, которые будут списком объектов Category и так далее. Я хочу получить этот объект непосредственно из базы данных PostgreSQL через рекурсивный SQL-запрос с Pomm. Цель состоит не в том, чтобы пройти полученные данные и построить такой объект в PHP. Я хочу прямой процесс PostreSQL -> Pomm -> Object.

А пока я получаю то, что хочу, только на первом уровне. Таким образом, категория первого уровня имеет подкатегории, которые являются списком объектов категории. Однако следующий уровень (глубина 2) не имеет подкатегорий.

Пока у меня есть это:

$sql = <<<SQL
with recursive
cat as (select c.* FROM :category c where parent_id is null),
subcat as (
select c.* from :category c join cat on c.parent_id=cat.id
union all
select c.* from :category c join subcat on c.parent_id=subcat.id
)
select :projection from cat cc, subcat
where cc.id=subcat.parent_id
group by :group_fields
SQL;

$projection = $this->createProjection()
    ->setField('subcategories', 'array_agg(subcat)', 'public.category[]');
$sql = strtr($sql, [
        ':category' => $this->structure->getRelation(),
        ':projection' => $projection->formatFieldsWithFieldAlias('cc'),
        ':group_fields' => $this->createProjection()->formatFields('cc'),
]);

Мой вопрос, если это возможно с Pomm и если да, то как?

1 ответ

То, чего вы хотите достичь, на самом деле не возможно напрямую, потому что в Pomm по соображениям производительности, когда выполняется запрос, итератор помещает курсор базы данных в результаты.

$iterator = $this->query($sql, $array_of_parameters);

foreach ($iterator as $entity) {
   $entity->getParentId();
}

Каждый раз, когда данные выбираются из итератора, система преобразования превращает их в объект. Но сущности не знают о базе данных, поэтому они не могут получить больше данных, используя свои средства доступа.

Тривиальная идея состоит в том, чтобы получить один результат, содержащий все результаты как вложенные объекты:

with recursive
cat as (
select * from test_tree tt where not exists (select parent_id from test_tree tt2 where tt2.parent_id = tt.id)
union all
select tt.*, array_agg(child) from test_tree tt join cat child on tt.id = child.parent_id group by tt.id
)
select * from cat

Но, к сожалению, невозможно использовать агрегатные функции в рекурсивном члене CTE.

Другая идея заключалась бы в том, чтобы индексировать результаты по идентификатору, дающему потомков для каждого parent_id, и использовать эти итераторы Pomm с возможностью прокрутки для их извлечения:

with
  tree as (
    select
      tt.id,
      array_agg(child) as children
    from
      test_tree tt
      join lateral (select * from test_tree tt2 where tt2.parent_id = tt.id) child on (true) group by tt.id
  )
select
  idx as id,
  tree.children
from
  generate_series(1, (select max(id) from test_tree)) idx
  left join tree on tree.id = idx

какие выводы:

┌────┬─────────────────────────────────────────┐
│ id │                children                 │
├────┼─────────────────────────────────────────┤
│  1 │ {"(2,\"test 2\",1)","(3,\"test 3\",1)"} │
│  2 │ {"(4,\"test 4\",2)","(5,\"test 5\",2)"} │
│  3 │ {"(6,\"test 6\",3)"}                    │
│  4 │ ¤                                       │
│  5 │ ¤                                       │
│  6 │ {"(7,\"test 7\",6)"}                    │
│  7 │ ¤                                       │
└────┴─────────────────────────────────────────┘
(7 rows)

Тогда набор результатов будет упорядочен по parent_id так $iterator->get($parent_id) вернул бы массив дочерних объектов (или ноль), но это больше похоже на хак, чем на реальную функцию.

Если рассматривать проблему с другой стороны, то представляется возможным создать выделенные гибкие сущности, которые встраивают шаблон вложенного множества в конструкцию для использования детьми.

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