ClosureTreeTest.php 10 KB

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