ClosureTreeTest.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. namespace Gedmo\Tree;
  3. use Doctrine\Common\EventManager;
  4. use Tool\BaseTestCaseORM;
  5. use Doctrine\Common\Util\Debug;
  6. use Tree\Fixture\Closure\Category;
  7. use Tree\Fixture\Closure\CategoryClosure;
  8. /**
  9. * These are tests for Tree behavior
  10. *
  11. * @author Gustavo Adrian <comfortablynumb84@gmail.com>
  12. * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  13. * @package Gedmo.Tree
  14. * @link http://www.gediminasm.org
  15. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  16. */
  17. class ClosureTreeTest extends BaseTestCaseORM
  18. {
  19. const CATEGORY = "Tree\\Fixture\\Closure\\Category";
  20. const CLOSURE = "Tree\\Fixture\\Closure\\CategoryClosure";
  21. const PERSON = "Tree\\Fixture\\Closure\\Person";
  22. const USER = "Tree\\Fixture\\Closure\\User";
  23. const PERSON_CLOSURE = "Tree\\Fixture\\Closure\\PersonClosure";
  24. protected function setUp()
  25. {
  26. parent::setUp();
  27. $evm = new EventManager;
  28. $evm->addEventSubscriber(new TreeListener);
  29. $this->getMockSqliteEntityManager($evm);
  30. $this->populate();
  31. }
  32. /*public function testHeavyLoad()
  33. {
  34. $start = microtime(true);
  35. $dumpTime = function($start, $msg) {
  36. $took = microtime(true) - $start;
  37. $minutes = intval($took / 60); $seconds = $took % 60;
  38. echo sprintf("%s --> %02d:%02d", $msg, $minutes, $seconds) . PHP_EOL;
  39. };
  40. $repo = $this->em->getRepository(self::CATEGORY);
  41. $parent = null;
  42. $num = 800;
  43. for($i = 0; $i < 500; $i++) {
  44. $cat = new Category;
  45. $cat->setParent($parent);
  46. $cat->setTitle('cat'.$i);
  47. $this->em->persist($cat);
  48. // siblings
  49. $rnd = rand(0, 3);
  50. for ($j = 0; $j < $rnd; $j++) {
  51. $siblingCat = new Category;
  52. $siblingCat->setTitle('cat'.$i.$j);
  53. $siblingCat->setParent($cat);
  54. $this->em->persist($siblingCat);
  55. }
  56. $num += $rnd;
  57. $parent = $cat;
  58. }
  59. $this->em->flush();
  60. $dumpTime($start, $num.' - inserts took:');
  61. $start = microtime(true);
  62. // test moving
  63. $target = $repo->findOneByTitle('cat300');
  64. $dest = $repo->findOneByTitle('cat2000');
  65. $target->setParent($dest);
  66. $target2 = $repo->findOneByTitle('cat450');
  67. $dest2 = $repo->findOneByTitle('cat2500');
  68. $target2->setParent($dest2);
  69. $this->em->flush();
  70. $dumpTime($start, 'moving took:');
  71. }*/
  72. public function testClosureTree()
  73. {
  74. $repo = $this->em->getRepository(self::CATEGORY);
  75. $closureRepo = $this->em->getRepository(self::CLOSURE);
  76. $food = $repo->findOneByTitle('Food');
  77. $dql = 'SELECT c FROM '.self::CLOSURE.' c';
  78. $dql .= ' WHERE c.ancestor = :ancestor';
  79. $query = $this->em->createQuery($dql);
  80. $query->setParameter('ancestor', $food);
  81. $foodClosures = $query->getResult();
  82. $this->assertEquals(12, count($foodClosures));
  83. foreach ($foodClosures as $closure) {
  84. $descendant = $closure->getDescendant();
  85. if ($descendant === $food) {
  86. $this->assertEquals(0, $closure->getDepth());
  87. continue;
  88. }
  89. $descendantTitle = $descendant->getTitle();
  90. $query->setParameter('ancestor', $descendant);
  91. $descendantClosures = $query->getResult();
  92. switch ($descendantTitle) {
  93. case 'Fruits':
  94. $this->assertEquals(5, count($descendantClosures));
  95. $this->assertEquals(1, $closure->getDepth());
  96. break;
  97. case 'Oranges':
  98. $this->assertEquals(1, count($descendantClosures));
  99. $this->assertEquals(2, $closure->getDepth());
  100. break;
  101. case 'Berries':
  102. $this->assertEquals(2, count($descendantClosures));
  103. $this->assertEquals(2, $closure->getDepth());
  104. break;
  105. case 'Vegitables':
  106. $this->assertEquals(3, count($descendantClosures));
  107. $this->assertEquals(1, $closure->getDepth());
  108. break;
  109. case 'Milk':
  110. $this->assertEquals(3, count($descendantClosures));
  111. $this->assertEquals(1, $closure->getDepth());
  112. break;
  113. case 'Cheese':
  114. $this->assertEquals(2, count($descendantClosures));
  115. $this->assertEquals(2, $closure->getDepth());
  116. break;
  117. case 'Strawberries':
  118. $this->assertEquals(1, count($descendantClosures));
  119. $this->assertEquals(3, $closure->getDepth());
  120. break;
  121. }
  122. }
  123. }
  124. public function testUpdateOfParent()
  125. {
  126. $repo = $this->em->getRepository(self::CATEGORY);
  127. $strawberries = $repo->findOneByTitle('Strawberries');
  128. $cheese = $repo->findOneByTitle('Cheese');
  129. $strawberries->setParent($cheese);
  130. $this->em->persist($strawberries);
  131. $this->em->flush();
  132. $dql = 'SELECT c FROM '.self::CLOSURE.' c';
  133. $dql .= ' WHERE c.descendant = :descendant';
  134. $query = $this->em->createQuery($dql);
  135. $query->setParameter('descendant', $strawberries);
  136. $closures = $query->getResult();
  137. $this->assertTrue($this->hasAncestor($closures, 'Cheese'));
  138. $this->assertTrue($this->hasAncestor($closures, 'Milk'));
  139. $this->assertTrue($this->hasAncestor($closures, 'Food'));
  140. $this->assertFalse($this->hasAncestor($closures, 'Berries'));
  141. $this->assertFalse($this->hasAncestor($closures, 'Fruits'));
  142. }
  143. public function testAnotherUpdateOfParent()
  144. {
  145. $repo = $this->em->getRepository(self::CATEGORY);
  146. $strawberries = $repo->findOneByTitle('Strawberries');
  147. $strawberries->setParent(null);
  148. $this->em->persist($strawberries);
  149. $this->em->flush();
  150. $dql = 'SELECT c FROM '.self::CLOSURE.' c';
  151. $dql .= ' WHERE c.descendant = :descendant';
  152. $query = $this->em->createQuery($dql);
  153. $query->setParameter('descendant', $strawberries);
  154. $closures = $query->getResult();
  155. $this->assertEquals(1, count($closures));
  156. $this->assertTrue($this->hasAncestor($closures, 'Strawberries'));
  157. }
  158. public function testBranchRemoval()
  159. {
  160. $repo = $this->em->getRepository(self::CATEGORY);
  161. $fruits = $repo->findOneByTitle('Fruits');
  162. $id = $fruits->getId();
  163. $this->em->remove($fruits);
  164. $this->em->flush();
  165. $dql = 'SELECT COUNT(c) FROM '.self::CLOSURE.' c';
  166. $dql .= ' JOIN c.descendant d';
  167. $dql .= ' JOIN c.ancestor a';
  168. $dql .= ' WHERE (a.id = :id OR d.id = :id)';
  169. $query = $this->em->createQuery($dql);
  170. $query->setParameter('id', $id);
  171. $this->assertEquals(0, $query->getSingleScalarResult());
  172. // pdo_sqlite will not cascade
  173. }
  174. /**
  175. * @expectedException Gedmo\Exception\UnexpectedValueException
  176. */
  177. public function testSettingParentToChild()
  178. {
  179. $repo = $this->em->getRepository(self::CATEGORY);
  180. $fruits = $repo->findOneByTitle('Fruits');
  181. $strawberries = $repo->findOneByTitle('Strawberries');
  182. $fruits->setParent($strawberries);
  183. $this->em->flush();
  184. }
  185. private function hasAncestor($closures, $name)
  186. {
  187. $result = false;
  188. foreach ($closures as $closure) {
  189. $ancestor = $closure->getAncestor();
  190. if ($ancestor->getTitle() === $name) {
  191. $result = true;
  192. break;
  193. }
  194. }
  195. return $result;
  196. }
  197. protected function getUsedEntityFixtures()
  198. {
  199. return array(
  200. self::CATEGORY,
  201. self::CLOSURE,
  202. self::PERSON,
  203. self::PERSON_CLOSURE,
  204. self::USER
  205. );
  206. }
  207. private function populate()
  208. {
  209. $food = new Category;
  210. $food->setTitle("Food");
  211. $this->em->persist($food);
  212. $fruits = new Category;
  213. $fruits->setTitle('Fruits');
  214. $fruits->setParent($food);
  215. $this->em->persist($fruits);
  216. $oranges = new Category;
  217. $oranges->setTitle('Oranges');
  218. $oranges->setParent($fruits);
  219. $this->em->persist($oranges);
  220. $lemons = new Category;
  221. $lemons->setTitle('Lemons');
  222. $lemons->setParent($fruits);
  223. $this->em->persist($lemons);
  224. $berries = new Category;
  225. $berries->setTitle('Berries');
  226. $berries->setParent($fruits);
  227. $this->em->persist($berries);
  228. $strawberries = new Category;
  229. $strawberries->setTitle('Strawberries');
  230. $strawberries->setParent($berries);
  231. $this->em->persist($strawberries);
  232. $vegitables = new Category;
  233. $vegitables->setTitle('Vegitables');
  234. $vegitables->setParent($food);
  235. $this->em->persist($vegitables);
  236. $cabbages = new Category;
  237. $cabbages->setTitle('Cabbages');
  238. $cabbages->setParent($vegitables);
  239. $this->em->persist($cabbages);
  240. $carrots = new Category;
  241. $carrots->setTitle('Carrots');
  242. $carrots->setParent($vegitables);
  243. $this->em->persist($carrots);
  244. $milk = new Category;
  245. $milk->setTitle('Milk');
  246. $milk->setParent($food);
  247. $this->em->persist($milk);
  248. $cheese = new Category;
  249. $cheese->setTitle('Cheese');
  250. $cheese->setParent($milk);
  251. $this->em->persist($cheese);
  252. $mouldCheese = new Category;
  253. $mouldCheese->setTitle('Mould cheese');
  254. $mouldCheese->setParent($cheese);
  255. $this->em->persist($mouldCheese);
  256. $this->em->flush();
  257. }
  258. }