Как обработать случай, когда parent_id равен нулю в структуре дерева рубрик
Что проверяют
Умеете ли вы собрать дерево из плоского списка и не сломаться на корнях, сиротах и циклах. Вторая половина вопроса про схему базы: почему корень обозначен нулём, а не NULL. Следом просят построить дерево за один проход без запроса на каждый узел.
Как отвечать
Сначала про схему. Ноль как признак корня означает, что внешний ключ FOREIGN KEY (parent_id) REFERENCES categories(id) повесить нельзя: строки с id = 0 нет, и база не защищает от ссылок на удалённых родителей. Правильнее хранить NULL. Если схему менять нельзя, приводите значение один раз при чтении, чтобы дальше в коде был единственный признак корня.
Сборка дерева за O(n) без рекурсии:
- индекс по id: первым проходом складываете узлы в
$byId[$row['id']], у каждого пустойchildren; - привязка: вторым проходом добавляете узел по ссылке
&вchildrenродителя, а узлы сparent_id, равным0илиnull, кладёте в список корней; - сироты: если родителя нет в
$byId, решите явно, считать узел корнем или писать в лог. Молча терять рубрики нельзя; - циклы: рубрика, ставшая родителем своего предка, превращает рекурсивный обход в бесконечный. Держите множество посещённых
idили ограничивайте глубину.
Про сравнение. PDO в зависимости от драйвера и настроек может вернуть числа строками, и parent_id придёт как "0". Приводите через (int) и сравнивайте строго: (int) $row['parent_id'] === 0. empty() выглядит удобно, но считает корнем и пустую строку, и это прячет ошибки в данных.
В SQL корни и всё поддерево достаются одним запросом WITH RECURSIVE: он есть в MySQL 8.0 и PostgreSQL, якорная часть WHERE parent_id IS NULL. Для частого чтения глубоких деревьев стоит упомянуть materialized path, nested sets или closure table.
Где спотыкаются
Делают отдельный запрос детей для каждого узла. На каталоге из тысячи рубрик это тысяча запросов на одну страницу.
Смешивают 0 и NULL в одной таблице. Выборка WHERE parent_id = 0 теряет половину корней.
Не думают о цикле в данных. Один неверный parent_id из админки кладёт страницу каталога по таймауту или переполнению стека.