* @author Gediminas Morkevicius * @link http://www.gediminasm.org * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ class ClosureTreeTest extends BaseTestCaseORM { const CATEGORY = "Tree\\Fixture\\Closure\\Category"; const CLOSURE = "Tree\\Fixture\\Closure\\CategoryClosure"; const PERSON = "Tree\\Fixture\\Closure\\Person"; const USER = "Tree\\Fixture\\Closure\\User"; const PERSON_CLOSURE = "Tree\\Fixture\\Closure\\PersonClosure"; const NEWS = "Tree\\Fixture\\Closure\\News"; const CATEGORY_WITHOUT_LEVEL = "Tree\\Fixture\\Closure\\CategoryWithoutLevel"; const CATEGORY_WITHOUT_LEVEL_CLOSURE = "Tree\\Fixture\\Closure\\CategoryWithoutLevelClosure"; protected $listener; protected function setUp() { parent::setUp(); $this->listener = new TreeListener; $evm = new EventManager; $evm->addEventSubscriber($this->listener); $this->getMockSqliteEntityManager($evm); $this->populate(); } /*public function testHeavyLoad() { $start = microtime(true); $dumpTime = function($start, $msg) { $took = microtime(true) - $start; $minutes = intval($took / 60); $seconds = $took % 60; echo sprintf("%s --> %02d:%02d", $msg, $minutes, $seconds) . PHP_EOL; }; $repo = $this->em->getRepository(self::CATEGORY); $parent = null; $num = 800; for($i = 0; $i < 500; $i++) { $cat = new Category; $cat->setParent($parent); $cat->setTitle('cat'.$i); $this->em->persist($cat); // siblings $rnd = rand(0, 3); for ($j = 0; $j < $rnd; $j++) { $siblingCat = new Category; $siblingCat->setTitle('cat'.$i.$j); $siblingCat->setParent($cat); $this->em->persist($siblingCat); } $num += $rnd; $parent = $cat; } $this->em->flush(); $dumpTime($start, $num.' - inserts took:'); $start = microtime(true); // test moving $target = $repo->findOneByTitle('cat300'); $dest = $repo->findOneByTitle('cat2000'); $target->setParent($dest); $target2 = $repo->findOneByTitle('cat450'); $dest2 = $repo->findOneByTitle('cat2500'); $target2->setParent($dest2); $this->em->flush(); $dumpTime($start, 'moving took:'); }*/ public function testClosureTree() { $repo = $this->em->getRepository(self::CATEGORY); $closureRepo = $this->em->getRepository(self::CLOSURE); $food = $repo->findOneByTitle('Food'); $dql = 'SELECT c FROM '.self::CLOSURE.' c'; $dql .= ' WHERE c.ancestor = :ancestor'; $query = $this->em->createQuery($dql); $query->setParameter('ancestor', $food); $foodClosures = $query->getResult(); $this->assertCount(12, $foodClosures); foreach ($foodClosures as $closure) { $descendant = $closure->getDescendant(); if ($descendant === $food) { $this->assertEquals(0, $closure->getDepth()); continue; } $descendantTitle = $descendant->getTitle(); $query->setParameter('ancestor', $descendant); $descendantClosures = $query->getResult(); switch ($descendantTitle) { case 'Fruits': $this->assertCount(5, $descendantClosures); $this->assertEquals(1, $closure->getDepth()); break; case 'Oranges': $this->assertCount(1, $descendantClosures); $this->assertEquals(2, $closure->getDepth()); break; case 'Berries': $this->assertCount(2, $descendantClosures); $this->assertEquals(2, $closure->getDepth()); break; case 'Vegitables': $this->assertCount(3, $descendantClosures); $this->assertEquals(1, $closure->getDepth()); break; case 'Milk': $this->assertCount(3, $descendantClosures); $this->assertEquals(1, $closure->getDepth()); break; case 'Cheese': $this->assertCount(2, $descendantClosures); $this->assertEquals(2, $closure->getDepth()); break; case 'Strawberries': $this->assertCount(1, $descendantClosures); $this->assertEquals(3, $closure->getDepth()); break; } } } public function testUpdateOfParent() { $repo = $this->em->getRepository(self::CATEGORY); $strawberries = $repo->findOneByTitle('Strawberries'); $cheese = $repo->findOneByTitle('Cheese'); $strawberries->setParent($cheese); $this->em->persist($strawberries); $this->em->flush(); $dql = 'SELECT c FROM '.self::CLOSURE.' c'; $dql .= ' WHERE c.descendant = :descendant'; $query = $this->em->createQuery($dql); $query->setParameter('descendant', $strawberries); $closures = $query->getResult(); $this->assertTrue($this->hasAncestor($closures, 'Cheese')); $this->assertTrue($this->hasAncestor($closures, 'Milk')); $this->assertTrue($this->hasAncestor($closures, 'Food')); $this->assertFalse($this->hasAncestor($closures, 'Berries')); $this->assertFalse($this->hasAncestor($closures, 'Fruits')); } public function testAnotherUpdateOfParent() { $repo = $this->em->getRepository(self::CATEGORY); $strawberries = $repo->findOneByTitle('Strawberries'); $strawberries->setParent(null); $this->em->persist($strawberries); $this->em->flush(); $dql = 'SELECT c FROM '.self::CLOSURE.' c'; $dql .= ' WHERE c.descendant = :descendant'; $query = $this->em->createQuery($dql); $query->setParameter('descendant', $strawberries); $closures = $query->getResult(); $this->assertCount(1, $closures); $this->assertTrue($this->hasAncestor($closures, 'Strawberries')); } public function testBranchRemoval() { $repo = $this->em->getRepository(self::CATEGORY); $fruits = $repo->findOneByTitle('Fruits'); $id = $fruits->getId(); $this->em->remove($fruits); $this->em->flush(); $dql = 'SELECT COUNT(c) FROM '.self::CLOSURE.' c'; $dql .= ' JOIN c.descendant d'; $dql .= ' JOIN c.ancestor a'; $dql .= ' WHERE (a.id = :id OR d.id = :id)'; $query = $this->em->createQuery($dql); $query->setParameter('id', $id); $this->assertEquals(0, $query->getSingleScalarResult()); // pdo_sqlite will not cascade } /** * @expectedException Gedmo\Exception\UnexpectedValueException */ public function testSettingParentToChild() { $repo = $this->em->getRepository(self::CATEGORY); $fruits = $repo->findOneByTitle('Fruits'); $strawberries = $repo->findOneByTitle('Strawberries'); $fruits->setParent($strawberries); $this->em->flush(); } public function testIfEntityHasNotIncludedTreeLevelFieldThenDontProcessIt() { $listener = $this->getMock('Gedmo\Tree\TreeListener', array('getStrategy')); $strategy = $this->getMock('Gedmo\Tree\Strategy\ORM\Closure', array('setLevelFieldOnPendingNodes'), array($listener)); $listener->expects($this->any()) ->method('getStrategy') ->will($this->returnValue($strategy)); $strategy->expects($this->never()) ->method('setLevelFieldOnPendingNodes'); $evm = $this->em->getEventManager(); $evm->removeEventListener($this->listener->getSubscribedEvents(), $this->listener); $evm->addEventListener($this->listener->getSubscribedEvents(), $this->listener); $cat = new CategoryWithoutLevel(); $cat->setTitle('Test'); $this->em->persist($cat); $this->em->flush(); } private function hasAncestor($closures, $name) { $result = false; foreach ($closures as $closure) { $ancestor = $closure->getAncestor(); if ($ancestor->getTitle() === $name) { $result = true; break; } } return $result; } protected function getUsedEntityFixtures() { return array( self::CATEGORY, self::CLOSURE, self::PERSON, self::PERSON_CLOSURE, self::USER, self::NEWS, self::CATEGORY_WITHOUT_LEVEL, self::CATEGORY_WITHOUT_LEVEL_CLOSURE ); } private function populate() { $food = new Category; $food->setTitle("Food"); $this->em->persist($food); $fruits = new Category; $fruits->setTitle('Fruits'); $fruits->setParent($food); $this->em->persist($fruits); $oranges = new Category; $oranges->setTitle('Oranges'); $oranges->setParent($fruits); $this->em->persist($oranges); $lemons = new Category; $lemons->setTitle('Lemons'); $lemons->setParent($fruits); $this->em->persist($lemons); $berries = new Category; $berries->setTitle('Berries'); $berries->setParent($fruits); $this->em->persist($berries); $strawberries = new Category; $strawberries->setTitle('Strawberries'); $strawberries->setParent($berries); $this->em->persist($strawberries); $vegitables = new Category; $vegitables->setTitle('Vegitables'); $vegitables->setParent($food); $this->em->persist($vegitables); $cabbages = new Category; $cabbages->setTitle('Cabbages'); $cabbages->setParent($vegitables); $this->em->persist($cabbages); $carrots = new Category; $carrots->setTitle('Carrots'); $carrots->setParent($vegitables); $this->em->persist($carrots); $milk = new Category; $milk->setTitle('Milk'); $milk->setParent($food); $this->em->persist($milk); $cheese = new Category; $cheese->setTitle('Cheese'); $cheese->setParent($milk); $this->em->persist($cheese); $mouldCheese = new Category; $mouldCheese->setTitle('Mould cheese'); $mouldCheese->setParent($cheese); $this->em->persist($mouldCheese); $this->em->flush(); } public function testCascadePersistTree() { $politics = new Category(); $politics->setTitle('Politics'); $news = new News('Lorem ipsum', $politics); $this->em->persist($news); $this->em->flush(); $closure = $this->em->createQueryBuilder() ->select('c') ->from(self::CLOSURE, 'c') ->where('c.ancestor = :ancestor') ->setParameter('ancestor', $politics->getId()) ->getQuery() ->getResult(); $this->assertCount(1, $closure); } }