RulesCheckerIntegrationTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\ORM\Entity;
  17. use Cake\ORM\RulesChecker;
  18. use Cake\ORM\TableRegistry;
  19. use Cake\TestSuite\TestCase;
  20. /**
  21. * Tests the integration between the ORM and the domain checker
  22. */
  23. class RulesCheckerIntegrationTest extends TestCase {
  24. /**
  25. * Fixtures to be loaded
  26. *
  27. * @var array
  28. */
  29. public $fixtures = ['core.articles', 'core.authors', 'core.tags', 'core.articles_tags'];
  30. /**
  31. * Tear down
  32. *
  33. * @return void
  34. */
  35. public function tearDown() {
  36. parent::tearDown();
  37. TableRegistry::clear();
  38. }
  39. /**
  40. * Tests saving belongsTo association and get a validation error
  41. *
  42. * @group save
  43. * @return void
  44. */
  45. public function testsSaveBelongsToWithValidationError() {
  46. $entity = new Entity([
  47. 'title' => 'A Title',
  48. 'body' => 'A body'
  49. ]);
  50. $entity->author = new Entity([
  51. 'name' => 'Jose'
  52. ]);
  53. $table = TableRegistry::get('articles');
  54. $table->belongsTo('authors');
  55. $table->association('authors')
  56. ->target()
  57. ->rulesChecker()
  58. ->add(function (Entity $author, array $options) use ($table) {
  59. $this->assertSame($options['repository'], $table->association('authors')->target());
  60. return false;
  61. }, ['errorField' => 'name', 'message' => 'This is an error']);
  62. $this->assertFalse($table->save($entity));
  63. $this->assertTrue($entity->isNew());
  64. $this->assertTrue($entity->author->isNew());
  65. $this->assertNull($entity->get('author_id'));
  66. $this->assertNotEmpty($entity->author->errors('name'));
  67. $this->assertEquals(['This is an error'], $entity->author->errors('name'));
  68. }
  69. /**
  70. * Tests saving hasOne association and returning a validation error will
  71. * abort the saving process
  72. *
  73. * @group save
  74. * @return void
  75. */
  76. public function testSaveHasOneWithValidationError() {
  77. $entity = new \Cake\ORM\Entity([
  78. 'name' => 'Jose'
  79. ]);
  80. $entity->article = new \Cake\ORM\Entity([
  81. 'title' => 'A Title',
  82. 'body' => 'A body'
  83. ]);
  84. $table = TableRegistry::get('authors');
  85. $table->hasOne('articles');
  86. $table->association('articles')
  87. ->target()
  88. ->rulesChecker()
  89. ->add(function (Entity $entity) {
  90. return false;
  91. }, ['errorField' => 'title', 'message' => 'This is an error']);
  92. $this->assertFalse($table->save($entity));
  93. $this->assertTrue($entity->isNew());
  94. $this->assertTrue($entity->article->isNew());
  95. $this->assertNull($entity->article->id);
  96. $this->assertNull($entity->article->get('author_id'));
  97. $this->assertFalse($entity->article->dirty('author_id'));
  98. $this->assertNotEmpty($entity->article->errors('title'));
  99. }
  100. /**
  101. * Tests saving multiple entities in a hasMany association and getting and
  102. * error while saving one of them. It should abort all the save operation
  103. * when options are set to defaults
  104. *
  105. * @return void
  106. */
  107. public function testSaveHasManyWithErrorsAtomic() {
  108. $entity = new \Cake\ORM\Entity([
  109. 'name' => 'Jose'
  110. ]);
  111. $entity->articles = [
  112. new \Cake\ORM\Entity([
  113. 'title' => '1',
  114. 'body' => 'A body'
  115. ]),
  116. new \Cake\ORM\Entity([
  117. 'title' => 'Another Title',
  118. 'body' => 'Another body'
  119. ])
  120. ];
  121. $table = TableRegistry::get('authors');
  122. $table->hasMany('articles');
  123. $table->association('articles')
  124. ->target()
  125. ->rulesChecker()
  126. ->add(function (Entity $entity, $options) use ($table) {
  127. $this->assertSame($table, $options['_sourceTable']);
  128. return $entity->title === '1';
  129. }, ['errorField' => 'title', 'message' => 'This is an error']);
  130. $this->assertFalse($table->save($entity));
  131. $this->assertTrue($entity->isNew());
  132. $this->assertTrue($entity->articles[0]->isNew());
  133. $this->assertTrue($entity->articles[1]->isNew());
  134. $this->assertNull($entity->articles[0]->id);
  135. $this->assertNull($entity->articles[1]->id);
  136. $this->assertNull($entity->articles[0]->author_id);
  137. $this->assertNull($entity->articles[1]->author_id);
  138. $this->assertEmpty($entity->articles[0]->errors());
  139. $this->assertNotEmpty($entity->articles[1]->errors());
  140. }
  141. /**
  142. * Tests that it is possible to continue saving hasMany associations
  143. * even if any of the records fail validation when atomic is set
  144. * to false
  145. *
  146. * @return void
  147. */
  148. public function testSaveHasManyWithErrorsNonAtomic() {
  149. $entity = new \Cake\ORM\Entity([
  150. 'name' => 'Jose'
  151. ]);
  152. $entity->articles = [
  153. new \Cake\ORM\Entity([
  154. 'title' => 'A title',
  155. 'body' => 'A body'
  156. ]),
  157. new \Cake\ORM\Entity([
  158. 'title' => '1',
  159. 'body' => 'Another body'
  160. ])
  161. ];
  162. $table = TableRegistry::get('authors');
  163. $table->hasMany('articles');
  164. $table->association('articles')
  165. ->target()
  166. ->rulesChecker()
  167. ->add(function (Entity $article) {
  168. return is_numeric($article->title);
  169. }, ['errorField' => 'title', 'message' => 'This is an error']);
  170. $result = $table->save($entity, ['atomic' => false]);
  171. $this->assertSame($entity, $result);
  172. $this->assertFalse($entity->isNew());
  173. $this->assertTrue($entity->articles[0]->isNew());
  174. $this->assertFalse($entity->articles[1]->isNew());
  175. $this->assertEquals(4, $entity->articles[1]->id);
  176. $this->assertNull($entity->articles[0]->id);
  177. $this->assertNotEmpty($entity->articles[0]->errors('title'));
  178. }
  179. /**
  180. * Tests saving belongsToMany records with a validation error in a joint entity
  181. *
  182. * @group save
  183. * @return void
  184. */
  185. public function testSaveBelongsToManyWithValidationErrorInJointEntity() {
  186. $entity = new \Cake\ORM\Entity([
  187. 'title' => 'A Title',
  188. 'body' => 'A body'
  189. ]);
  190. $entity->tags = [
  191. new \Cake\ORM\Entity([
  192. 'name' => 'Something New'
  193. ]),
  194. new \Cake\ORM\Entity([
  195. 'name' => '100'
  196. ])
  197. ];
  198. $table = TableRegistry::get('articles');
  199. $table->belongsToMany('tags');
  200. $table->association('tags')
  201. ->junction()
  202. ->rulesChecker()
  203. ->add(function (Entity $entity) {
  204. return $entity->article_id > 4;
  205. });
  206. $this->assertFalse($table->save($entity));
  207. $this->assertTrue($entity->isNew());
  208. $this->assertTrue($entity->tags[0]->isNew());
  209. $this->assertTrue($entity->tags[1]->isNew());
  210. $this->assertNull($entity->tags[0]->id);
  211. $this->assertNull($entity->tags[1]->id);
  212. $this->assertNull($entity->tags[0]->_joinData);
  213. $this->assertNull($entity->tags[1]->_joinData);
  214. }
  215. /**
  216. * Tests saving belongsToMany records with a validation error in a joint entity
  217. * and atomic set to false
  218. *
  219. * @group save
  220. * @return void
  221. */
  222. public function testSaveBelongsToManyWithValidationErrorInJointEntityNonAtomic() {
  223. $entity = new \Cake\ORM\Entity([
  224. 'title' => 'A Title',
  225. 'body' => 'A body'
  226. ]);
  227. $entity->tags = [
  228. new \Cake\ORM\Entity([
  229. 'name' => 'Something New'
  230. ]),
  231. new \Cake\ORM\Entity([
  232. 'name' => 'New one'
  233. ])
  234. ];
  235. $table = TableRegistry::get('articles');
  236. $table->belongsToMany('tags');
  237. $table->association('tags')
  238. ->junction()
  239. ->rulesChecker()
  240. ->add(function (Entity $entity) {
  241. return $entity->tag_id > 4;
  242. });
  243. $this->assertSame($entity, $table->save($entity, ['atomic' => false]));
  244. $this->assertFalse($entity->isNew());
  245. $this->assertFalse($entity->tags[0]->isNew());
  246. $this->assertFalse($entity->tags[1]->isNew());
  247. $this->assertEquals(4, $entity->tags[0]->id);
  248. $this->assertEquals(5, $entity->tags[1]->id);
  249. $this->assertTrue($entity->tags[0]->_joinData->isNew());
  250. $this->assertEquals(4, $entity->tags[1]->_joinData->article_id);
  251. $this->assertEquals(5, $entity->tags[1]->_joinData->tag_id);
  252. }
  253. /**
  254. * Tests the isUnique domain rule
  255. *
  256. * @group save
  257. * @return void
  258. */
  259. public function testIsUniqueDomainRule() {
  260. $entity = new Entity([
  261. 'name' => 'larry'
  262. ]);
  263. $table = TableRegistry::get('Authors');
  264. $rules = $table->rulesChecker();
  265. $rules->add($rules->isUnique(['name']));
  266. $this->assertFalse($table->save($entity));
  267. $this->assertEquals(['This value is already in use'], $entity->errors('name'));
  268. $entity->name = 'jose';
  269. $this->assertSame($entity, $table->save($entity));
  270. $entity = $table->get(1);
  271. $entity->dirty('name', true);
  272. $this->assertSame($entity, $table->save($entity));
  273. }
  274. /**
  275. * Tests isUnique with multiple fields
  276. *
  277. * @group save
  278. * @return void
  279. */
  280. public function testIsUniqueMultipleFields() {
  281. $entity = new Entity([
  282. 'author_id' => 1,
  283. 'title' => 'First Article'
  284. ]);
  285. $table = TableRegistry::get('Articles');
  286. $rules = $table->rulesChecker();
  287. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  288. $this->assertFalse($table->save($entity));
  289. $this->assertEquals(['title' => ['Nope']], $entity->errors());
  290. $entity->clean();
  291. $entity->author_id = 2;
  292. $this->assertSame($entity, $table->save($entity));
  293. }
  294. /**
  295. * Tests the existsIn domain rule
  296. *
  297. * @group save
  298. * @return void
  299. */
  300. public function testExistsInDomainRule() {
  301. $entity = new Entity([
  302. 'title' => 'An Article',
  303. 'author_id' => 500
  304. ]);
  305. $table = TableRegistry::get('Articles');
  306. $table->belongsTo('Authors');
  307. $rules = $table->rulesChecker();
  308. $rules->add($rules->existsIn('author_id', 'Authors'));
  309. $this->assertFalse($table->save($entity));
  310. $this->assertEquals(['This value does not exist'], $entity->errors('author_id'));
  311. }
  312. /**
  313. * Tests the existsIn domain rule when passing an object
  314. *
  315. * @group save
  316. * @return void
  317. */
  318. public function testExistsInDomainRuleWithObject() {
  319. $entity = new Entity([
  320. 'title' => 'An Article',
  321. 'author_id' => 500
  322. ]);
  323. $table = TableRegistry::get('Articles');
  324. $rules = $table->rulesChecker();
  325. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  326. $this->assertFalse($table->save($entity));
  327. $this->assertEquals(['Nope'], $entity->errors('author_id'));
  328. }
  329. /**
  330. * Tests the checkRules save option
  331. *
  332. * @group save
  333. * @return void
  334. */
  335. public function testSkipRulesChecking() {
  336. $entity = new Entity([
  337. 'title' => 'An Article',
  338. 'author_id' => 500
  339. ]);
  340. $table = TableRegistry::get('Articles');
  341. $rules = $table->rulesChecker();
  342. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  343. $this->assertSame($entity, $table->save($entity, ['checkRules' => false]));
  344. }
  345. /**
  346. * Tests the beforeRules event
  347. *
  348. * @group save
  349. * @return void
  350. */
  351. public function testUseBeforeRules() {
  352. $entity = new Entity([
  353. 'title' => 'An Article',
  354. 'author_id' => 500
  355. ]);
  356. $table = TableRegistry::get('Articles');
  357. $rules = $table->rulesChecker();
  358. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  359. $table->eventManager()->attach(
  360. function ($event, Entity $entity, \ArrayObject $options, $operation, RulesChecker $check) {
  361. $this->assertEquals(
  362. ['atomic' => true, 'associated' => true, 'checkRules' => true],
  363. $options->getArrayCopy()
  364. );
  365. $this->assertEquals('create', $operation);
  366. $this->assertSame($event->subject()->rulesChecker(), $check);
  367. $event->stopPropagation();
  368. return true;
  369. },
  370. 'Model.beforeRules'
  371. );
  372. $this->assertSame($entity, $table->save($entity));
  373. }
  374. /**
  375. * Tests the afterRules event
  376. *
  377. * @group save
  378. * @return void
  379. */
  380. public function testUseAfterRules() {
  381. $entity = new Entity([
  382. 'title' => 'An Article',
  383. 'author_id' => 500
  384. ]);
  385. $table = TableRegistry::get('Articles');
  386. $rules = $table->rulesChecker();
  387. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  388. $table->eventManager()->attach(
  389. function ($event, Entity $entity, \ArrayObject $options, $result, $operation, RulesChecker $check) {
  390. $this->assertEquals(
  391. ['atomic' => true, 'associated' => true, 'checkRules' => true],
  392. $options->getArrayCopy()
  393. );
  394. $this->assertEquals('create', $operation);
  395. $this->assertSame($event->subject()->rulesChecker(), $check);
  396. $this->assertFalse($result);
  397. $event->stopPropagation();
  398. return true;
  399. },
  400. 'Model.afterRules'
  401. );
  402. $this->assertSame($entity, $table->save($entity));
  403. }
  404. /**
  405. * Tests that rules can be changed using the buildRules event
  406. *
  407. * @group save
  408. * @return void
  409. */
  410. public function testUseBuildRulesEvent() {
  411. $entity = new Entity([
  412. 'title' => 'An Article',
  413. 'author_id' => 500
  414. ]);
  415. $table = TableRegistry::get('Articles');
  416. $table->eventManager()->attach(function ($event, $rules) {
  417. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  418. }, 'Model.buildRules');
  419. $this->assertFalse($table->save($entity));
  420. }
  421. /**
  422. * Tests isUnique with untouched fields
  423. *
  424. * @group save
  425. * @return void
  426. */
  427. public function testIsUniqueWithCleanFields() {
  428. $table = TableRegistry::get('Articles');
  429. $entity = $table->get(1);
  430. $rules = $table->rulesChecker();
  431. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  432. $entity->body = 'Foo';
  433. $this->assertSame($entity, $table->save($entity));
  434. $entity->title = 'Third Article';
  435. $this->assertFalse($table->save($entity));
  436. }
  437. /**
  438. * Tests the existsIn rule when passing non dirty fields
  439. *
  440. * @group save
  441. * @return void
  442. */
  443. public function testExistsInWithCleanFields() {
  444. $table = TableRegistry::get('Articles');
  445. $table->belongsTo('Authors');
  446. $rules = $table->rulesChecker();
  447. $rules->add($rules->existsIn('author_id', 'Authors'));
  448. $entity = $table->get(1);
  449. $entity->title = 'Foo';
  450. $entity->author_id = 1000;
  451. $entity->dirty('author_id', false);
  452. $this->assertSame($entity, $table->save($entity));
  453. }
  454. /**
  455. * Tests using rules to prevent delete operations
  456. *
  457. * @group delete
  458. * @return void
  459. */
  460. public function testDeleteRules() {
  461. $table = TableRegistry::get('Articles');
  462. $rules = $table->rulesChecker();
  463. $rules->addDelete(function ($entity) {
  464. return false;
  465. });
  466. $entity = $table->get(1);
  467. $this->assertFalse($table->delete($entity));
  468. }
  469. /**
  470. * Checks that it is possible to pass custom options to rules when saving
  471. *
  472. * @group save
  473. * @return void
  474. */
  475. public function testCustomOptionsPassingSave() {
  476. $entity = new Entity([
  477. 'name' => 'jose'
  478. ]);
  479. $table = TableRegistry::get('Authors');
  480. $rules = $table->rulesChecker();
  481. $rules->add(function ($entity, $options) {
  482. $this->assertEquals('bar', $options['foo']);
  483. $this->assertEquals('option', $options['another']);
  484. return false;
  485. }, ['another' => 'option']);
  486. $this->assertFalse($table->save($entity, ['foo' => 'bar']));
  487. }
  488. /**
  489. * Tests passing custom options to rules from delete
  490. *
  491. * @group delete
  492. * @return void
  493. */
  494. public function testCustomOptionsPassingDelete() {
  495. $table = TableRegistry::get('Articles');
  496. $rules = $table->rulesChecker();
  497. $rules->addDelete(function ($entity, $options) {
  498. $this->assertEquals('bar', $options['foo']);
  499. $this->assertEquals('option', $options['another']);
  500. return false;
  501. }, ['another' => 'option']);
  502. $entity = $table->get(1);
  503. $this->assertFalse($table->delete($entity, ['foo' => 'bar']));
  504. }
  505. /**
  506. * Tests that using existsIn for a hasMany association will not be called
  507. * as the foreign key for the association was automatically validated already.
  508. *
  509. * @group save
  510. * @return void
  511. */
  512. public function testAvoidExistsInOnAutomaticSaving() {
  513. $entity = new \Cake\ORM\Entity([
  514. 'name' => 'Jose'
  515. ]);
  516. $entity->articles = [
  517. new \Cake\ORM\Entity([
  518. 'title' => '1',
  519. 'body' => 'A body'
  520. ]),
  521. new \Cake\ORM\Entity([
  522. 'title' => 'Another Title',
  523. 'body' => 'Another body'
  524. ])
  525. ];
  526. $table = TableRegistry::get('authors');
  527. $table->hasMany('articles');
  528. $table->association('articles')->belongsTo('authors');
  529. $checker = $table->association('articles')->target()->rulesChecker();
  530. $checker->add(function ($entity, $options) use ($checker) {
  531. $rule = $checker->existsIn('author_id', 'authors');
  532. $id = $entity->author_id;
  533. $entity->author_id = 5000;
  534. $result = $rule($entity, $options);
  535. $this->assertTrue($result);
  536. $entity->author_id = $id;
  537. return true;
  538. });
  539. $this->assertSame($entity, $table->save($entity));
  540. }
  541. }