ru en

Пересчет индексов Nested Set дерева в Symfony

TreeОднажды использовал Nestedset behavior в Doctrine, Symfony и столкнулся с тем, что индексы стали неверными. (Прочитать как работает Nested Set можно здесь). Случилось это, скорее всего, во время миграции - по неосторожности был исполнен низкоуровневый MySQL запрос, из-за которого не отработали ивенты Doctrine, которые пересчитывают индексы. Для того, чтобы исправить это, была написана команда, которая пересчитывает индексы. В принципе, при большом желании, можно оформить это в виде отдельного сервиса, и запустить с помощью следующей миграции. Здесь показан просто рабочий пример, как пересчитать индексы.

 

Аргумент команды - сущность, для которой нужно пересчитать индексы.

<?php

namespace Vendor\Bundle\SomeBundle\Command;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class NestedSetRefreshCommand extends ContainerAwareCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('vendor:nested-set:refresh')
            ->addArgument('entity', InputArgument::REQUIRED)
        ;
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @throws \Exception
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $container = $this->getContainer();
        /** @var EntityManagerInterface $em */
        $em = $container->get('doctrine.orm.entity_manager');
        $repository = $em->getRepository($input->getArgument('entity'));

        $refreshLeftAndRight = function($root, $left) use ($repository, &$refreshLeftAndRight) {
            $right = $left + 1;
            $children = $repository->findBy([
                'parent' => $root,
            ]);

            foreach ($children as $entity) {
                // recursive execution of this function for each
                // child of this node
                // $right is the current right value, which is
                // incremented by the refreshLeftAndRight function
                $right = $refreshLeftAndRight($entity, $right);
            }

            // we've got the left value, and now that we've processed
            // the children of this node we also know the right value
            $this->updateValue($left, $root, 'left');
            $this->updateValue($right, $root, 'right');
            return $right + 1;
        };

        foreach ($repository->findBy(['parent' => null]) as $rootEntry) {
            $refreshLeftAndRight($rootEntry, 1);
        }

        $em->flush();

        foreach ($repository->findAll() as $entity) {
            $level = 0;
            $parent = $entity;

            /** @noinspection PhpUndefinedMethodInspection */
            while ($parent = $parent->getParent()) {
                $level++;
            }

            $this->updateValue($level, $entity, 'level');
        }

        $em->flush();
    }

    /**
     * @param mixed $value
     * @param object $entity
     * @param string $property
     */
    private function updateValue($value, $entity, $property)
    {
        $reflection = new \ReflectionProperty(get_class($entity), $property);
        $reflection->setAccessible(true);
        $reflection->setValue($entity, $value);
    }
}

https://gist.github.com/harentius/d70af4a470cee0cec1bb36db966b0d9a