ClosureTreeTest.php 12KB

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