DoctrineExtensions icon indicating copy to clipboard operation
DoctrineExtensions copied to clipboard

Sortable doesn't work according to the documentation

Open enumag opened this issue 11 years ago • 17 comments

I've tried this example in my application (copy-paste) and it doesn't print what it's supposed to.

Expected output (according to docs):

0: item 0
1: item 1
2: item 2

Actual output:

0: item 1
0: item 0
1: item 2

Version: Gedmo 2.3.9 + Doctrine 2.4.4

Is something missing in the docs or is it a bug?

enumag avatar Jul 26 '14 14:07 enumag

It works as expected if I add flush after each persist so I think it really is a bug.

enumag avatar Jul 26 '14 15:07 enumag

The problem is probably that SortableListener::getMaxPosition() doesn't check entities sheduled for insertion.

enumag avatar Jul 26 '14 16:07 enumag

what if make flush after first and last persist?

vetalt avatar Aug 21 '14 22:08 vetalt

In that case the output is different but still buggy:

0: item 0
1: item 1
1: item 2

enumag avatar Aug 21 '14 22:08 enumag

I can confirm this behaviour. Anyone working on this "bug"?

thoresuenert avatar Nov 19 '14 11:11 thoresuenert

can you make unit test?

vetalt avatar Nov 19 '14 14:11 vetalt

I don't know how to contribute the right way. I there is any advice share it with me plz. For now i will paste them here:

 /**
     * @test
     */
    public function shouldFixIssue1130()
    {
        $item1 = new Node();
        $item1->setName('Node1');
        $item1->setPath('category 1');
        $this->em->persist($item1);

        $item2 = new Node();
        $item2->setName('Node2');
        $item2->setPath('category 1');
        $this->em->persist($item2);

        $item0 = new Node();
        $item0->setName('Node0');
        $item0->setPath('category 1');
        $item0->setPosition(0);
        $this->em->persist($item0);

        $this->em->flush();
        $this->em->clear(); // to reload from database  

        $repo = $this->em->getRepository(self::NODE);
        $nodes = $repo->getBySortableGroups(array('path' => 'category 1'));

        $this->assertEquals('Node0', $nodes[0]->getName());
        $this->assertEquals('Node1', $nodes[1]->getName());
        $this->assertEquals('Node2', $nodes[2]->getName());

        for ($i = 0; $i < count($nodes); $i++) {
            $this->assertSame($i, $nodes[$i]->getPosition());
        }
    }

    /**
     * @test
     */
    public function shouldFixPositionTooHighAfterInsert()
    {
        $item1 = new Node();
        $item1->setName('Node1');
        $item1->setPath('category 1');
        $this->em->persist($item1);

        $this->em->flush();

        $item2 = new Node();
        $item2->setName('Node2');
        $item2->setPath('category 1');
        $this->em->persist($item2);


        $item0 = new Node();
        $item0->setName('Node0');
        $item0->setPath('category 1');
        $item0->setPosition(0);
        $this->em->persist($item0);

        $this->em->flush();
        $this->em->clear(); // to reload from database  

        $repo = $this->em->getRepository(self::NODE);
        $nodes = $repo->getBySortableGroups(array('path' => 'category 1'));

        $this->assertEquals('Node0', $nodes[0]->getName());
        $this->assertEquals('Node1', $nodes[1]->getName());
        $this->assertEquals('Node2', $nodes[2]->getName());

        for ($i = 0; $i < count($nodes); $i++) {
            $this->assertSame($i, $nodes[$i]->getPosition());
        }
    }

Results:

1) Gedmo\Sortable\SortableTest::shouldFixIssue1130
Failed asserting that 0 is identical to 1.

/Users/thore/Projects/doctrine-laravel/tests/Gedmo/Sortable/SortableTest.php:603

2) Gedmo\Sortable\SortableTest::shouldFixPositionTooHighAfterInsert
Failed asserting that 1 is identical to 2.

thoresuenert avatar Nov 20 '14 13:11 thoresuenert

A quick and Dirty Solution: Within OnFLush in SortableListener around Line 80:

$orderedObjects = [];
        foreach ($ea->getScheduledObjectInsertions($uow) as $object) {
            $meta = $om->getClassMetadata(get_class($object));
            if ($config = $this->getConfiguration($om, $meta->name)) {
                if(! is_null($meta->getReflectionProperty($config['position'])->getValue($object)))
                {
                    array_unshift($orderedObjects, $object);  
                    continue;  
                }
                $orderedObjects[] = $object;
            }
        }
        foreach ($orderedObjects as $object) {
            $meta = $om->getClassMetadata(get_class($object));
            if ($config = $this->getConfiguration($om, $meta->name)) {
                $this->processInsert($ea, $config, $meta, $object);
            }
        }

solution: Put all objects with a specified position in front of the processed array, so the deltas will effect the memory. this is a quick and dirty fix to the implementation inside porcessInsert

thoresuenert avatar Nov 20 '14 16:11 thoresuenert

Next failing Test (my code and the initial code fail here):

/**
     * @test
     */
    public function shouldFixPositionOtherThenZero()
    {

        $item0 = new Node();
        $item0->setName('Node0');
        $item0->setPath('category 1');
        $this->em->persist($item0);


        $item2 = new Node();
        $item2->setName('Node2');
        $item2->setPath('category 1');
        $this->em->persist($item2);

        $item1 = new Node();
        $item1->setName('Node1');
        $item1->setPath('category 1');
        $item1->setPosition(1);
        $this->em->persist($item1);

        $this->em->flush();
        $this->em->clear(); // to reload from database  

        $repo = $this->em->getRepository(self::NODE);
        $nodes = $repo->getBySortableGroups(array('path' => 'category 1'));

        $this->assertEquals('Node0', $nodes[0]->getName());
        $this->assertEquals('Node1', $nodes[1]->getName());
        $this->assertEquals('Node2', $nodes[2]->getName());

        for ($i = 0; $i < count($nodes); $i++) {
            $this->assertSame($i, $nodes[$i]->getPosition());

        }
    }

thoresuenert avatar Nov 20 '14 19:11 thoresuenert

I would flash() after each persist()

vetalt avatar Nov 24 '14 13:11 vetalt

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Nov 25 '21 10:11 github-actions[bot]

Still not solved afaik.

enumag avatar Nov 25 '21 11:11 enumag

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Jun 24 '22 09:06 github-actions[bot]

I had the same problem. The SortableListener was registered twice but has to run only once.

FireLizard avatar Jul 06 '22 07:07 FireLizard

I had the same problem. The SortableListener was registered twice but has to run only once.

Could you advise on how to do so please ?

nayodahl avatar Oct 27 '23 15:10 nayodahl

I had the same problem. The SortableListener was registered twice but has to run only once.

Could you advise on how to do so please ?

I just get all listeners of event "onFlush" and look for the existence of SortableListener.

$listeners = array_map(
    static fn($listener) => get_class($listener),
    $em->getEventManager()->getListeners('onFlush')
);
if ( ! in_array(SortableListener::class, $listeners, true)) {
    $em->getEventManager()->addEventSubscriber(new SortableListener());
}

FireLizard avatar Nov 01 '23 10:11 FireLizard