RulesCheckerIntegrationTest.php 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\Event\Event;
  17. use Cake\ORM\Entity;
  18. use Cake\ORM\RulesChecker;
  19. use Cake\ORM\TableRegistry;
  20. use Cake\TestSuite\TestCase;
  21. /**
  22. * Tests the integration between the ORM and the domain checker
  23. */
  24. class RulesCheckerIntegrationTest extends TestCase
  25. {
  26. /**
  27. * Fixtures to be loaded
  28. *
  29. * @var array
  30. */
  31. public $fixtures = [
  32. 'core.articles', 'core.articles_tags', 'core.authors', 'core.tags',
  33. 'core.special_tags', 'core.categories', 'core.site_articles', 'core.site_authors'
  34. ];
  35. /**
  36. * Tear down
  37. *
  38. * @return void
  39. */
  40. public function tearDown()
  41. {
  42. parent::tearDown();
  43. TableRegistry::clear();
  44. }
  45. /**
  46. * Tests saving belongsTo association and get a validation error
  47. *
  48. * @group save
  49. * @return void
  50. */
  51. public function testsSaveBelongsToWithValidationError()
  52. {
  53. $entity = new Entity([
  54. 'title' => 'A Title',
  55. 'body' => 'A body'
  56. ]);
  57. $entity->author = new Entity([
  58. 'name' => 'Jose'
  59. ]);
  60. $table = TableRegistry::get('articles');
  61. $table->belongsTo('authors');
  62. $table->association('authors')
  63. ->target()
  64. ->rulesChecker()
  65. ->add(
  66. function (Entity $author, array $options) use ($table) {
  67. $this->assertSame($options['repository'], $table->association('authors')->target());
  68. return false;
  69. },
  70. ['errorField' => 'name', 'message' => 'This is an error']
  71. );
  72. $this->assertFalse($table->save($entity));
  73. $this->assertTrue($entity->isNew());
  74. $this->assertTrue($entity->author->isNew());
  75. $this->assertNull($entity->get('author_id'));
  76. $this->assertNotEmpty($entity->author->errors('name'));
  77. $this->assertEquals(['This is an error'], $entity->author->errors('name'));
  78. }
  79. /**
  80. * Tests saving hasOne association and returning a validation error will
  81. * abort the saving process
  82. *
  83. * @group save
  84. * @return void
  85. */
  86. public function testSaveHasOneWithValidationError()
  87. {
  88. $entity = new Entity([
  89. 'name' => 'Jose'
  90. ]);
  91. $entity->article = new Entity([
  92. 'title' => 'A Title',
  93. 'body' => 'A body'
  94. ]);
  95. $table = TableRegistry::get('authors');
  96. $table->hasOne('articles');
  97. $table->association('articles')
  98. ->target()
  99. ->rulesChecker()
  100. ->add(
  101. function (Entity $entity) {
  102. return false;
  103. },
  104. ['errorField' => 'title', 'message' => 'This is an error']
  105. );
  106. $this->assertFalse($table->save($entity));
  107. $this->assertTrue($entity->isNew());
  108. $this->assertTrue($entity->article->isNew());
  109. $this->assertNull($entity->article->id);
  110. $this->assertNull($entity->article->get('author_id'));
  111. $this->assertFalse($entity->article->isDirty('author_id'));
  112. $this->assertNotEmpty($entity->article->errors('title'));
  113. $this->assertSame('A Title', $entity->article->invalid('title'));
  114. }
  115. /**
  116. * Tests saving multiple entities in a hasMany association and getting and
  117. * error while saving one of them. It should abort all the save operation
  118. * when options are set to defaults
  119. *
  120. * @return void
  121. */
  122. public function testSaveHasManyWithErrorsAtomic()
  123. {
  124. $entity = new Entity([
  125. 'name' => 'Jose'
  126. ]);
  127. $entity->articles = [
  128. new Entity([
  129. 'title' => '1',
  130. 'body' => 'A body'
  131. ]),
  132. new Entity([
  133. 'title' => 'Another Title',
  134. 'body' => 'Another body'
  135. ])
  136. ];
  137. $table = TableRegistry::get('authors');
  138. $table->hasMany('articles');
  139. $table->association('articles')
  140. ->target()
  141. ->rulesChecker()
  142. ->add(
  143. function (Entity $entity, $options) use ($table) {
  144. $this->assertSame($table, $options['_sourceTable']);
  145. return $entity->title === '1';
  146. },
  147. ['errorField' => 'title', 'message' => 'This is an error']
  148. );
  149. $this->assertFalse($table->save($entity));
  150. $this->assertTrue($entity->isNew());
  151. $this->assertTrue($entity->articles[0]->isNew());
  152. $this->assertTrue($entity->articles[1]->isNew());
  153. $this->assertNull($entity->articles[0]->id);
  154. $this->assertNull($entity->articles[1]->id);
  155. $this->assertNull($entity->articles[0]->author_id);
  156. $this->assertNull($entity->articles[1]->author_id);
  157. $this->assertEmpty($entity->articles[0]->errors());
  158. $this->assertNotEmpty($entity->articles[1]->errors());
  159. }
  160. /**
  161. * Tests that it is possible to continue saving hasMany associations
  162. * even if any of the records fail validation when atomic is set
  163. * to false
  164. *
  165. * @return void
  166. */
  167. public function testSaveHasManyWithErrorsNonAtomic()
  168. {
  169. $entity = new Entity([
  170. 'name' => 'Jose'
  171. ]);
  172. $entity->articles = [
  173. new Entity([
  174. 'title' => 'A title',
  175. 'body' => 'A body'
  176. ]),
  177. new Entity([
  178. 'title' => '1',
  179. 'body' => 'Another body'
  180. ])
  181. ];
  182. $table = TableRegistry::get('authors');
  183. $table->hasMany('articles');
  184. $table->association('articles')
  185. ->target()
  186. ->rulesChecker()
  187. ->add(
  188. function (Entity $article) {
  189. return is_numeric($article->title);
  190. },
  191. ['errorField' => 'title', 'message' => 'This is an error']
  192. );
  193. $result = $table->save($entity, ['atomic' => false]);
  194. $this->assertSame($entity, $result);
  195. $this->assertFalse($entity->isNew());
  196. $this->assertTrue($entity->articles[0]->isNew());
  197. $this->assertFalse($entity->articles[1]->isNew());
  198. $this->assertEquals(4, $entity->articles[1]->id);
  199. $this->assertNull($entity->articles[0]->id);
  200. $this->assertNotEmpty($entity->articles[0]->errors('title'));
  201. }
  202. /**
  203. * Tests saving belongsToMany records with a validation error in a joint entity
  204. *
  205. * @group save
  206. * @return void
  207. */
  208. public function testSaveBelongsToManyWithValidationErrorInJointEntity()
  209. {
  210. $entity = new Entity([
  211. 'title' => 'A Title',
  212. 'body' => 'A body'
  213. ]);
  214. $entity->tags = [
  215. new Entity([
  216. 'name' => 'Something New'
  217. ]),
  218. new Entity([
  219. 'name' => '100'
  220. ])
  221. ];
  222. $table = TableRegistry::get('articles');
  223. $table->belongsToMany('tags');
  224. $table->association('tags')
  225. ->junction()
  226. ->rulesChecker()
  227. ->add(function (Entity $entity) {
  228. return $entity->article_id > 4;
  229. });
  230. $this->assertFalse($table->save($entity));
  231. $this->assertTrue($entity->isNew());
  232. $this->assertTrue($entity->tags[0]->isNew());
  233. $this->assertTrue($entity->tags[1]->isNew());
  234. $this->assertNull($entity->tags[0]->id);
  235. $this->assertNull($entity->tags[1]->id);
  236. $this->assertNull($entity->tags[0]->_joinData);
  237. $this->assertNull($entity->tags[1]->_joinData);
  238. }
  239. /**
  240. * Tests saving belongsToMany records with a validation error in a joint entity
  241. * and atomic set to false
  242. *
  243. * @group save
  244. * @return void
  245. */
  246. public function testSaveBelongsToManyWithValidationErrorInJointEntityNonAtomic()
  247. {
  248. $entity = new Entity([
  249. 'title' => 'A Title',
  250. 'body' => 'A body'
  251. ]);
  252. $entity->tags = [
  253. new Entity([
  254. 'name' => 'Something New'
  255. ]),
  256. new Entity([
  257. 'name' => 'New one'
  258. ])
  259. ];
  260. $table = TableRegistry::get('articles');
  261. $table->belongsToMany('tags');
  262. $table->association('tags')
  263. ->junction()
  264. ->rulesChecker()
  265. ->add(function (Entity $entity) {
  266. return $entity->tag_id > 4;
  267. });
  268. $this->assertSame($entity, $table->save($entity, ['atomic' => false]));
  269. $this->assertFalse($entity->isNew());
  270. $this->assertFalse($entity->tags[0]->isNew());
  271. $this->assertFalse($entity->tags[1]->isNew());
  272. $this->assertEquals(4, $entity->tags[0]->id);
  273. $this->assertEquals(5, $entity->tags[1]->id);
  274. $this->assertTrue($entity->tags[0]->_joinData->isNew());
  275. $this->assertEquals(4, $entity->tags[1]->_joinData->article_id);
  276. $this->assertEquals(5, $entity->tags[1]->_joinData->tag_id);
  277. }
  278. /**
  279. * Test adding rule with name
  280. *
  281. * @group save
  282. * @return void
  283. */
  284. public function testAddingRuleWithName()
  285. {
  286. $entity = new Entity([
  287. 'name' => 'larry'
  288. ]);
  289. $table = TableRegistry::get('Authors');
  290. $rules = $table->rulesChecker();
  291. $rules->add(
  292. function () {
  293. return false;
  294. },
  295. 'ruleName',
  296. ['errorField' => 'name']
  297. );
  298. $this->assertFalse($table->save($entity));
  299. $this->assertEquals(['ruleName' => 'invalid'], $entity->errors('name'));
  300. }
  301. /**
  302. * Ensure that add(isUnique()) only invokes a rule once.
  303. *
  304. * @return void
  305. */
  306. public function testIsUniqueRuleSingleInvocation()
  307. {
  308. $entity = new Entity([
  309. 'name' => 'larry'
  310. ]);
  311. $table = TableRegistry::get('Authors');
  312. $rules = $table->rulesChecker();
  313. $rules->add($rules->isUnique(['name']), '_isUnique', ['errorField' => 'title']);
  314. $this->assertFalse($table->save($entity));
  315. $this->assertEquals(
  316. ['_isUnique' => 'This value is already in use'],
  317. $entity->errors('title'),
  318. 'Provided field should have errors'
  319. );
  320. $this->assertEmpty($entity->errors('name'), 'Errors should not apply to original field.');
  321. }
  322. /**
  323. * Tests the isUnique domain rule
  324. *
  325. * @group save
  326. * @return void
  327. */
  328. public function testIsUniqueDomainRule()
  329. {
  330. $entity = new Entity([
  331. 'name' => 'larry'
  332. ]);
  333. $table = TableRegistry::get('Authors');
  334. $rules = $table->rulesChecker();
  335. $rules->add($rules->isUnique(['name']));
  336. $this->assertFalse($table->save($entity));
  337. $this->assertEquals(['_isUnique' => 'This value is already in use'], $entity->errors('name'));
  338. $entity->name = 'jose';
  339. $this->assertSame($entity, $table->save($entity));
  340. $entity = $table->get(1);
  341. $entity->setDirty('name', true);
  342. $this->assertSame($entity, $table->save($entity));
  343. }
  344. /**
  345. * Tests isUnique with multiple fields
  346. *
  347. * @group save
  348. * @return void
  349. */
  350. public function testIsUniqueMultipleFields()
  351. {
  352. $entity = new Entity([
  353. 'author_id' => 1,
  354. 'title' => 'First Article'
  355. ]);
  356. $table = TableRegistry::get('Articles');
  357. $rules = $table->rulesChecker();
  358. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  359. $this->assertFalse($table->save($entity));
  360. $this->assertEquals(['title' => ['_isUnique' => 'Nope']], $entity->errors());
  361. $entity->clean();
  362. $entity->author_id = 2;
  363. $this->assertSame($entity, $table->save($entity));
  364. }
  365. /**
  366. * Tests isUnique with allowMultipleNulls
  367. *
  368. * @group save
  369. * @return void
  370. */
  371. public function testIsUniqueAllowMultipleNulls()
  372. {
  373. $entity = new Entity([
  374. 'article_id' => 11,
  375. 'tag_id' => 11,
  376. 'author_id' => null
  377. ]);
  378. $table = TableRegistry::get('SpecialTags');
  379. $rules = $table->rulesChecker();
  380. $rules->add($rules->isUnique(['author_id'], [
  381. 'allowMultipleNulls' => false,
  382. 'message' => 'All fields are required'
  383. ]));
  384. $this->assertFalse($table->save($entity));
  385. $this->assertEquals(['_isUnique' => 'All fields are required'], $entity->errors('author_id'));
  386. $entity->author_id = 11;
  387. $this->assertSame($entity, $table->save($entity));
  388. $entity = $table->get(1);
  389. $entity->setDirty('author_id', true);
  390. $this->assertSame($entity, $table->save($entity));
  391. }
  392. /**
  393. * Tests isUnique with multiple fields and allowMultipleNulls
  394. *
  395. * @group save
  396. * @return void
  397. */
  398. public function testIsUniqueMultipleFieldsAllowMultipleNulls()
  399. {
  400. $entity = new Entity([
  401. 'article_id' => 10,
  402. 'tag_id' => 12,
  403. 'author_id' => null
  404. ]);
  405. $table = TableRegistry::get('SpecialTags');
  406. $rules = $table->rulesChecker();
  407. $rules->add($rules->isUnique(['author_id', 'article_id'], [
  408. 'allowMultipleNulls' => false,
  409. 'message' => 'Nope'
  410. ]));
  411. $this->assertFalse($table->save($entity));
  412. $this->assertEquals(['author_id' => ['_isUnique' => 'Nope']], $entity->errors());
  413. $entity->clean();
  414. $entity->article_id = 10;
  415. $entity->tag_id = 12;
  416. $entity->author_id = 12;
  417. $this->assertSame($entity, $table->save($entity));
  418. }
  419. /**
  420. * Tests isUnique with multiple fields emulates SQL UNIQUE keys
  421. *
  422. * @group save
  423. * @return void
  424. */
  425. public function testIsUniqueMultipleFieldsOneIsNull()
  426. {
  427. $entity = new Entity([
  428. 'author_id' => null,
  429. 'title' => 'First Article'
  430. ]);
  431. $table = TableRegistry::get('Articles');
  432. $rules = $table->rulesChecker();
  433. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  434. $this->assertSame($entity, $table->save($entity));
  435. // Make a matching record
  436. $entity = new Entity([
  437. 'author_id' => null,
  438. 'title' => 'New Article'
  439. ]);
  440. $this->assertSame($entity, $table->save($entity));
  441. }
  442. /**
  443. * Tests the existsIn domain rule
  444. *
  445. * @group save
  446. * @return void
  447. */
  448. public function testExistsInDomainRule()
  449. {
  450. $entity = new Entity([
  451. 'title' => 'An Article',
  452. 'author_id' => 500
  453. ]);
  454. $table = TableRegistry::get('Articles');
  455. $table->belongsTo('Authors');
  456. $rules = $table->rulesChecker();
  457. $rules->add($rules->existsIn('author_id', 'Authors'));
  458. $this->assertFalse($table->save($entity));
  459. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  460. }
  461. /**
  462. * Ensure that add(existsIn()) only invokes a rule once.
  463. *
  464. * @return void
  465. */
  466. public function testExistsInRuleSingleInvocation()
  467. {
  468. $entity = new Entity([
  469. 'title' => 'larry',
  470. 'author_id' => 500,
  471. ]);
  472. $table = TableRegistry::get('Articles');
  473. $table->belongsTo('Authors');
  474. $rules = $table->rulesChecker();
  475. $rules->add($rules->existsIn('author_id', 'Authors'), '_existsIn', ['errorField' => 'other']);
  476. $this->assertFalse($table->save($entity));
  477. $this->assertEquals(
  478. ['_existsIn' => 'This value does not exist'],
  479. $entity->errors('other'),
  480. 'Provided field should have errors'
  481. );
  482. $this->assertEmpty($entity->errors('author_id'), 'Errors should not apply to original field.');
  483. }
  484. /**
  485. * Tests the existsIn domain rule when passing an object
  486. *
  487. * @group save
  488. * @return void
  489. */
  490. public function testExistsInDomainRuleWithObject()
  491. {
  492. $entity = new Entity([
  493. 'title' => 'An Article',
  494. 'author_id' => 500
  495. ]);
  496. $table = TableRegistry::get('Articles');
  497. $rules = $table->rulesChecker();
  498. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  499. $this->assertFalse($table->save($entity));
  500. $this->assertEquals(['_existsIn' => 'Nope'], $entity->errors('author_id'));
  501. }
  502. /**
  503. * ExistsIn uses the schema to verify that nullable fields are ok.
  504. *
  505. * @return
  506. */
  507. public function testExistsInNullValue()
  508. {
  509. $entity = new Entity([
  510. 'title' => 'An Article',
  511. 'author_id' => null
  512. ]);
  513. $table = TableRegistry::get('Articles');
  514. $table->belongsTo('Authors');
  515. $rules = $table->rulesChecker();
  516. $rules->add($rules->existsIn('author_id', 'Authors'));
  517. $this->assertEquals($entity, $table->save($entity));
  518. $this->assertEquals([], $entity->errors('author_id'));
  519. }
  520. /**
  521. * Test ExistsIn on a new entity that doesn't have the field populated.
  522. *
  523. * This use case is important for saving records and their
  524. * associated belongsTo records in one pass.
  525. *
  526. * @return void
  527. */
  528. public function testExistsInNotNullValueNewEntity()
  529. {
  530. $entity = new Entity([
  531. 'name' => 'A Category',
  532. ]);
  533. $table = TableRegistry::get('Categories');
  534. $table->belongsTo('Categories', [
  535. 'foreignKey' => 'parent_id',
  536. 'bindingKey' => 'id',
  537. ]);
  538. $rules = $table->rulesChecker();
  539. $rules->add($rules->existsIn('parent_id', 'Categories'));
  540. $this->assertTrue($table->checkRules($entity, RulesChecker::CREATE));
  541. $this->assertEmpty($entity->errors('parent_id'));
  542. }
  543. /**
  544. * Tests exists in uses the bindingKey of the association
  545. *
  546. * @return
  547. */
  548. public function testExistsInWithBindingKey()
  549. {
  550. $entity = new Entity([
  551. 'title' => 'An Article',
  552. ]);
  553. $table = TableRegistry::get('Articles');
  554. $table->belongsTo('Authors', [
  555. 'bindingKey' => 'name',
  556. 'foreignKey' => 'title'
  557. ]);
  558. $rules = $table->rulesChecker();
  559. $rules->add($rules->existsIn('title', 'Authors'));
  560. $this->assertFalse($table->save($entity));
  561. $this->assertNotEmpty($entity->errors('title'));
  562. $entity->clean();
  563. $entity->title = 'larry';
  564. $this->assertEquals($entity, $table->save($entity));
  565. }
  566. /**
  567. * Tests existsIn with invalid associations
  568. *
  569. * @group save
  570. * @return void
  571. */
  572. public function testExistsInInvalidAssociation()
  573. {
  574. $this->expectException(\RuntimeException::class);
  575. $this->expectExceptionMessage('ExistsIn rule for \'author_id\' is invalid. \'NotValid\' is not associated with \'Cake\ORM\Table\'.');
  576. $entity = new Entity([
  577. 'title' => 'An Article',
  578. 'author_id' => 500
  579. ]);
  580. $table = TableRegistry::get('Articles');
  581. $table->belongsTo('Authors');
  582. $rules = $table->rulesChecker();
  583. $rules->add($rules->existsIn('author_id', 'NotValid'));
  584. $table->save($entity);
  585. }
  586. /**
  587. * Tests the checkRules save option
  588. *
  589. * @group save
  590. * @return void
  591. */
  592. public function testSkipRulesChecking()
  593. {
  594. $entity = new Entity([
  595. 'title' => 'An Article',
  596. 'author_id' => 500
  597. ]);
  598. $table = TableRegistry::get('Articles');
  599. $rules = $table->rulesChecker();
  600. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  601. $this->assertSame($entity, $table->save($entity, ['checkRules' => false]));
  602. }
  603. /**
  604. * Tests the beforeRules event
  605. *
  606. * @group save
  607. * @return void
  608. */
  609. public function testUseBeforeRules()
  610. {
  611. $entity = new Entity([
  612. 'title' => 'An Article',
  613. 'author_id' => 500
  614. ]);
  615. $table = TableRegistry::get('Articles');
  616. $rules = $table->rulesChecker();
  617. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  618. $table->getEventManager()->on(
  619. 'Model.beforeRules',
  620. function (Event $event, Entity $entity, \ArrayObject $options, $operation) {
  621. $this->assertEquals(
  622. [
  623. 'atomic' => true,
  624. 'associated' => true,
  625. 'checkRules' => true,
  626. 'checkExisting' => true,
  627. '_primary' => true,
  628. ],
  629. $options->getArrayCopy()
  630. );
  631. $this->assertEquals('create', $operation);
  632. $event->stopPropagation();
  633. return true;
  634. }
  635. );
  636. $this->assertSame($entity, $table->save($entity));
  637. }
  638. /**
  639. * Tests the afterRules event
  640. *
  641. * @group save
  642. * @return void
  643. */
  644. public function testUseAfterRules()
  645. {
  646. $entity = new Entity([
  647. 'title' => 'An Article',
  648. 'author_id' => 500
  649. ]);
  650. $table = TableRegistry::get('Articles');
  651. $rules = $table->rulesChecker();
  652. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  653. $table->getEventManager()->on(
  654. 'Model.afterRules',
  655. function (Event $event, Entity $entity, \ArrayObject $options, $result, $operation) {
  656. $this->assertEquals(
  657. [
  658. 'atomic' => true,
  659. 'associated' => true,
  660. 'checkRules' => true,
  661. 'checkExisting' => true,
  662. '_primary' => true,
  663. ],
  664. $options->getArrayCopy()
  665. );
  666. $this->assertEquals('create', $operation);
  667. $this->assertFalse($result);
  668. $event->stopPropagation();
  669. return true;
  670. }
  671. );
  672. $this->assertSame($entity, $table->save($entity));
  673. }
  674. /**
  675. * Tests that rules can be changed using the buildRules event
  676. *
  677. * @group save
  678. * @return void
  679. */
  680. public function testUseBuildRulesEvent()
  681. {
  682. $entity = new Entity([
  683. 'title' => 'An Article',
  684. 'author_id' => 500
  685. ]);
  686. $table = TableRegistry::get('Articles');
  687. $table->getEventManager()->on('Model.buildRules', function (Event $event, RulesChecker $rules) {
  688. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  689. });
  690. $this->assertFalse($table->save($entity));
  691. }
  692. /**
  693. * Tests isUnique with untouched fields
  694. *
  695. * @group save
  696. * @return void
  697. */
  698. public function testIsUniqueWithCleanFields()
  699. {
  700. $table = TableRegistry::get('Articles');
  701. $entity = $table->get(1);
  702. $rules = $table->rulesChecker();
  703. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  704. $entity->body = 'Foo';
  705. $this->assertSame($entity, $table->save($entity));
  706. $entity->title = 'Third Article';
  707. $this->assertFalse($table->save($entity));
  708. }
  709. /**
  710. * Tests isUnique rule with conflicting columns
  711. *
  712. * @group save
  713. * @return void
  714. */
  715. public function testIsUniqueAliasPrefix()
  716. {
  717. $entity = new Entity([
  718. 'title' => 'An Article',
  719. 'author_id' => 1
  720. ]);
  721. $table = TableRegistry::get('Articles');
  722. $table->belongsTo('Authors');
  723. $rules = $table->rulesChecker();
  724. $rules->add($rules->isUnique(['author_id']));
  725. $table->Authors->getEventManager()->on('Model.beforeFind', function (Event $event, $query) {
  726. $query->leftJoin(['a2' => 'authors']);
  727. });
  728. $this->assertFalse($table->save($entity));
  729. $this->assertEquals(['_isUnique' => 'This value is already in use'], $entity->errors('author_id'));
  730. }
  731. /**
  732. * Tests the existsIn rule when passing non dirty fields
  733. *
  734. * @group save
  735. * @return void
  736. */
  737. public function testExistsInWithCleanFields()
  738. {
  739. $table = TableRegistry::get('Articles');
  740. $table->belongsTo('Authors');
  741. $rules = $table->rulesChecker();
  742. $rules->add($rules->existsIn('author_id', 'Authors'));
  743. $entity = $table->get(1);
  744. $entity->title = 'Foo';
  745. $entity->author_id = 1000;
  746. $entity->setDirty('author_id', false);
  747. $this->assertSame($entity, $table->save($entity));
  748. }
  749. /**
  750. * Tests the existsIn with conflicting columns
  751. *
  752. * @group save
  753. * @return void
  754. */
  755. public function testExistsInAliasPrefix()
  756. {
  757. $entity = new Entity([
  758. 'title' => 'An Article',
  759. 'author_id' => 500
  760. ]);
  761. $table = TableRegistry::get('Articles');
  762. $table->belongsTo('Authors');
  763. $rules = $table->rulesChecker();
  764. $rules->add($rules->existsIn('author_id', 'Authors'));
  765. $table->Authors->getEventManager()->on('Model.beforeFind', function (Event $event, $query) {
  766. $query->leftJoin(['a2' => 'authors']);
  767. });
  768. $this->assertFalse($table->save($entity));
  769. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  770. }
  771. /**
  772. * Tests that using an array in existsIn() sets the error message correctly
  773. *
  774. * @return void
  775. */
  776. public function testExistsInErrorWithArrayField()
  777. {
  778. $entity = new Entity([
  779. 'title' => 'An Article',
  780. 'author_id' => 500
  781. ]);
  782. $table = TableRegistry::get('Articles');
  783. $table->belongsTo('Authors');
  784. $rules = $table->rulesChecker();
  785. $rules->add($rules->existsIn(['author_id'], 'Authors'));
  786. $this->assertFalse($table->save($entity));
  787. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  788. }
  789. /**
  790. * Tests new allowNullableNulls flag with author id set to null
  791. *
  792. * @return
  793. */
  794. public function testExistsInAllowNullableNullsWithAuthorIdNullA()
  795. {
  796. $entity = new Entity([
  797. 'id' => 10,
  798. 'author_id' => null,
  799. 'site_id' => 1,
  800. 'name' => 'New Site Article without Author',
  801. ]);
  802. $table = TableRegistry::get('SiteArticles');
  803. $table->belongsTo('SiteAuthors');
  804. $rules = $table->rulesChecker();
  805. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  806. 'allowNullableNulls' => true
  807. ]));
  808. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  809. }
  810. /**
  811. * Tests new allowNullableNulls flag with author id set to null
  812. *
  813. * @return
  814. */
  815. public function testExistsInAllowNullableNullsWithAuthorIdNullB()
  816. {
  817. $entity = new Entity([
  818. 'id' => 10,
  819. 'author_id' => null,
  820. 'site_id' => 1,
  821. 'name' => 'New Site Article without Author',
  822. ]);
  823. $table = TableRegistry::get('SiteArticles');
  824. $table->belongsTo('SiteAuthors');
  825. $rules = $table->rulesChecker();
  826. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  827. 'allowNullableNulls' => false
  828. ]));
  829. $this->assertFalse($table->save($entity));
  830. }
  831. /**
  832. * Tests new allowNullableNulls flag with author id set to null
  833. *
  834. * @return
  835. */
  836. public function testExistsInAllowNullableNullsWithAuthorIdNullC()
  837. {
  838. $entity = new Entity([
  839. 'id' => 10,
  840. 'author_id' => null,
  841. 'site_id' => 1,
  842. 'name' => 'New Site Article without Author',
  843. ]);
  844. $table = TableRegistry::get('SiteArticles');
  845. $table->belongsTo('SiteAuthors');
  846. $rules = $table->rulesChecker();
  847. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors'));
  848. $this->assertFalse($table->save($entity));
  849. }
  850. /**
  851. * Tests new allowNullableNulls flag with author id set to null
  852. *
  853. * @return
  854. */
  855. public function testExistsInAllowNullableNullsWithAuthorIdNullD()
  856. {
  857. $entity = new Entity([
  858. 'id' => 10,
  859. 'author_id' => null,
  860. 'site_id' => 1,
  861. 'name' => 'New Site Article without Author',
  862. ]);
  863. $table = TableRegistry::get('SiteArticles');
  864. $table->belongsTo('SiteAuthors');
  865. $rules = $table->rulesChecker();
  866. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  867. 'allowNullableNulls' => false,
  868. 'message' => 'Niente'
  869. ]));
  870. $this->assertFalse($table->save($entity));
  871. $this->assertEquals(['author_id' => ['_existsIn' => 'Niente']], $entity->errors());
  872. }
  873. /**
  874. * Tests new allowNullableNulls flag with author id set to null
  875. *
  876. * @return
  877. */
  878. public function testExistsInAllowNullableNullsWithAuthorIdNullE()
  879. {
  880. $entity = new Entity([
  881. 'id' => 10,
  882. 'author_id' => null,
  883. 'site_id' => 1,
  884. 'name' => 'New Site Article without Author',
  885. ]);
  886. $table = TableRegistry::get('SiteArticles');
  887. $table->belongsTo('SiteAuthors');
  888. $rules = $table->rulesChecker();
  889. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  890. 'allowNullableNulls' => true,
  891. 'message' => 'Niente'
  892. ]));
  893. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  894. }
  895. /**
  896. * Tests new allowNullableNulls flag with author id set to 1
  897. *
  898. * @return
  899. */
  900. public function testExistsInAllowNullableNullsWithAuthorId1A()
  901. {
  902. $entity = new Entity([
  903. 'id' => 10,
  904. 'author_id' => 1,
  905. 'site_id' => 1,
  906. 'name' => 'New Site Article with Author',
  907. ]);
  908. $table = TableRegistry::get('SiteArticles');
  909. $table->belongsTo('SiteAuthors');
  910. $rules = $table->rulesChecker();
  911. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', ['allowNullableNulls' => true]));
  912. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  913. }
  914. /**
  915. * Tests new allowNullableNulls flag with author id set to 1
  916. *
  917. * @return
  918. */
  919. public function testExistsInAllowNullableNullsWithAuthorIdB()
  920. {
  921. $entity = new Entity([
  922. 'id' => 10,
  923. 'author_id' => 1,
  924. 'site_id' => 1,
  925. 'name' => 'New Site Article with Author',
  926. ]);
  927. $table = TableRegistry::get('SiteArticles');
  928. $table->belongsTo('SiteAuthors');
  929. $rules = $table->rulesChecker();
  930. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', ['allowNullableNulls' => false]));
  931. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  932. }
  933. /**
  934. * Tests new allowNullableNulls flag with author id set to 1
  935. *
  936. * @return
  937. */
  938. public function testExistsInAllowNullableNullsWithAuthorId1C()
  939. {
  940. $entity = new Entity([
  941. 'id' => 10,
  942. 'author_id' => 1,
  943. 'site_id' => 1,
  944. 'name' => 'New Site Article with Author',
  945. ]);
  946. $table = TableRegistry::get('SiteArticles');
  947. $table->belongsTo('SiteAuthors');
  948. $rules = $table->rulesChecker();
  949. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors'));
  950. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  951. }
  952. /**
  953. * Tests new allowNullableNulls flag with author id set to 1
  954. *
  955. * @return
  956. */
  957. public function testExistsInAllowNullableNullsWithAuthorId1E()
  958. {
  959. $entity = new Entity([
  960. 'id' => 10,
  961. 'author_id' => 1,
  962. 'site_id' => 1,
  963. 'name' => 'New Site Article with Author',
  964. ]);
  965. $table = TableRegistry::get('SiteArticles');
  966. $table->belongsTo('SiteAuthors');
  967. $rules = $table->rulesChecker();
  968. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  969. 'allowNullableNulls' => true,
  970. 'message' => 'will not error']));
  971. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  972. }
  973. /**
  974. * Tests new allowNullableNulls flag with author id set to 1
  975. *
  976. * @return
  977. */
  978. public function testExistsInAllowNullableNullsWithAuthorId1F()
  979. {
  980. $entity = new Entity([
  981. 'id' => 10,
  982. 'author_id' => 1,
  983. 'site_id' => 1,
  984. 'name' => 'New Site Article with Author',
  985. ]);
  986. $table = TableRegistry::get('SiteArticles');
  987. $table->belongsTo('SiteAuthors');
  988. $rules = $table->rulesChecker();
  989. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  990. 'allowNullableNulls' => false,
  991. 'message' => 'will not error']));
  992. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  993. }
  994. /**
  995. * Tests new allowNullableNulls flag with author id set to 99999999 (does not exist)
  996. *
  997. * @return
  998. */
  999. public function testExistsInAllowNullableNullsWithAuthorId1G()
  1000. {
  1001. $entity = new Entity([
  1002. 'id' => 10,
  1003. 'author_id' => 99999999,
  1004. 'site_id' => 1,
  1005. 'name' => 'New Site Article with Author',
  1006. ]);
  1007. $table = TableRegistry::get('SiteArticles');
  1008. $table->belongsTo('SiteAuthors');
  1009. $rules = $table->rulesChecker();
  1010. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  1011. 'allowNullableNulls' => true,
  1012. 'message' => 'will error']));
  1013. $this->assertFalse($table->save($entity));
  1014. $this->assertEquals(['author_id' => ['_existsIn' => 'will error']], $entity->errors());
  1015. }
  1016. /**
  1017. * Tests new allowNullableNulls flag with author id set to 99999999 (does not exist)
  1018. * and site_id set to 99999999 (does not exist)
  1019. *
  1020. * @return
  1021. */
  1022. public function testExistsInAllowNullableNullsWithAuthorId1H()
  1023. {
  1024. $entity = new Entity([
  1025. 'id' => 10,
  1026. 'author_id' => 99999999,
  1027. 'site_id' => 99999999,
  1028. 'name' => 'New Site Article with Author',
  1029. ]);
  1030. $table = TableRegistry::get('SiteArticles');
  1031. $table->belongsTo('SiteAuthors');
  1032. $rules = $table->rulesChecker();
  1033. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  1034. 'allowNullableNulls' => true,
  1035. 'message' => 'will error']));
  1036. $this->assertFalse($table->save($entity));
  1037. $this->assertEquals(['author_id' => ['_existsIn' => 'will error']], $entity->errors());
  1038. }
  1039. /**
  1040. * Tests new allowNullableNulls flag with author id set to 1 (does exist)
  1041. * and site_id set to 99999999 (does not exist)
  1042. *
  1043. * @return
  1044. */
  1045. public function testExistsInAllowNullableNullsWithAuthorId1I()
  1046. {
  1047. $entity = new Entity([
  1048. 'id' => 10,
  1049. 'author_id' => 1,
  1050. 'site_id' => 99999999,
  1051. 'name' => 'New Site Article with Author',
  1052. ]);
  1053. $table = TableRegistry::get('SiteArticles');
  1054. $table->belongsTo('SiteAuthors');
  1055. $rules = $table->rulesChecker();
  1056. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  1057. 'allowNullableNulls' => true,
  1058. 'message' => 'will error']));
  1059. $this->assertFalse($table->save($entity));
  1060. $this->assertEquals(['author_id' => ['_existsIn' => 'will error']], $entity->errors());
  1061. }
  1062. /**
  1063. * Tests using rules to prevent delete operations
  1064. *
  1065. * @group delete
  1066. * @return void
  1067. */
  1068. public function testDeleteRules()
  1069. {
  1070. $table = TableRegistry::get('Articles');
  1071. $rules = $table->rulesChecker();
  1072. $rules->addDelete(function ($entity) {
  1073. return false;
  1074. });
  1075. $entity = $table->get(1);
  1076. $this->assertFalse($table->delete($entity));
  1077. }
  1078. /**
  1079. * Checks that it is possible to pass custom options to rules when saving
  1080. *
  1081. * @group save
  1082. * @return void
  1083. */
  1084. public function testCustomOptionsPassingSave()
  1085. {
  1086. $entity = new Entity([
  1087. 'name' => 'jose'
  1088. ]);
  1089. $table = TableRegistry::get('Authors');
  1090. $rules = $table->rulesChecker();
  1091. $rules->add(function ($entity, $options) {
  1092. $this->assertEquals('bar', $options['foo']);
  1093. $this->assertEquals('option', $options['another']);
  1094. return false;
  1095. }, ['another' => 'option']);
  1096. $this->assertFalse($table->save($entity, ['foo' => 'bar']));
  1097. }
  1098. /**
  1099. * Tests passing custom options to rules from delete
  1100. *
  1101. * @group delete
  1102. * @return void
  1103. */
  1104. public function testCustomOptionsPassingDelete()
  1105. {
  1106. $table = TableRegistry::get('Articles');
  1107. $rules = $table->rulesChecker();
  1108. $rules->addDelete(function ($entity, $options) {
  1109. $this->assertEquals('bar', $options['foo']);
  1110. $this->assertEquals('option', $options['another']);
  1111. return false;
  1112. }, ['another' => 'option']);
  1113. $entity = $table->get(1);
  1114. $this->assertFalse($table->delete($entity, ['foo' => 'bar']));
  1115. }
  1116. /**
  1117. * Test adding rules that return error string
  1118. *
  1119. * @group save
  1120. * @return void
  1121. */
  1122. public function testCustomErrorMessageFromRule()
  1123. {
  1124. $entity = new Entity([
  1125. 'name' => 'larry'
  1126. ]);
  1127. $table = TableRegistry::get('Authors');
  1128. $rules = $table->rulesChecker();
  1129. $rules->add(function () {
  1130. return 'So much nope';
  1131. }, ['errorField' => 'name']);
  1132. $this->assertFalse($table->save($entity));
  1133. $this->assertEquals(['So much nope'], $entity->errors('name'));
  1134. }
  1135. /**
  1136. * Test adding rules with no errorField do not accept strings
  1137. *
  1138. * @group save
  1139. * @return void
  1140. */
  1141. public function testCustomErrorMessageFromRuleNoErrorField()
  1142. {
  1143. $entity = new Entity([
  1144. 'name' => 'larry'
  1145. ]);
  1146. $table = TableRegistry::get('Authors');
  1147. $rules = $table->rulesChecker();
  1148. $rules->add(function () {
  1149. return 'So much nope';
  1150. });
  1151. $this->assertFalse($table->save($entity));
  1152. $this->assertEmpty($entity->errors());
  1153. }
  1154. /**
  1155. * Tests that using existsIn for a hasMany association will not be called
  1156. * as the foreign key for the association was automatically validated already.
  1157. *
  1158. * @group save
  1159. * @return void
  1160. */
  1161. public function testAvoidExistsInOnAutomaticSaving()
  1162. {
  1163. $entity = new Entity([
  1164. 'name' => 'Jose'
  1165. ]);
  1166. $entity->articles = [
  1167. new Entity([
  1168. 'title' => '1',
  1169. 'body' => 'A body'
  1170. ]),
  1171. new Entity([
  1172. 'title' => 'Another Title',
  1173. 'body' => 'Another body'
  1174. ])
  1175. ];
  1176. $table = TableRegistry::get('authors');
  1177. $table->hasMany('articles');
  1178. $table->association('articles')->belongsTo('authors');
  1179. $checker = $table->association('articles')->target()->rulesChecker();
  1180. $checker->add(function ($entity, $options) use ($checker) {
  1181. $rule = $checker->existsIn('author_id', 'authors');
  1182. $id = $entity->author_id;
  1183. $entity->author_id = 5000;
  1184. $result = $rule($entity, $options);
  1185. $this->assertTrue($result);
  1186. $entity->author_id = $id;
  1187. return true;
  1188. });
  1189. $this->assertSame($entity, $table->save($entity));
  1190. }
  1191. /**
  1192. * Tests the existsIn domain rule respects the conditions set for the associations
  1193. *
  1194. * @group save
  1195. * @return void
  1196. */
  1197. public function testExistsInDomainRuleWithAssociationConditions()
  1198. {
  1199. $entity = new Entity([
  1200. 'title' => 'An Article',
  1201. 'author_id' => 1
  1202. ]);
  1203. $table = TableRegistry::get('Articles');
  1204. $table->belongsTo('Authors', [
  1205. 'conditions' => ['Authors.name !=' => 'mariano']
  1206. ]);
  1207. $rules = $table->rulesChecker();
  1208. $rules->add($rules->existsIn('author_id', 'Authors'));
  1209. $this->assertFalse($table->save($entity));
  1210. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  1211. }
  1212. /**
  1213. * Tests that associated items have a count of X.
  1214. *
  1215. * @return void
  1216. */
  1217. public function testCountOfAssociatedItems()
  1218. {
  1219. $entity = new Entity([
  1220. 'title' => 'A Title',
  1221. 'body' => 'A body'
  1222. ]);
  1223. $entity->tags = [
  1224. new Entity([
  1225. 'name' => 'Something New'
  1226. ]),
  1227. new Entity([
  1228. 'name' => '100'
  1229. ])
  1230. ];
  1231. TableRegistry::get('ArticlesTags');
  1232. $table = TableRegistry::get('articles');
  1233. $table->belongsToMany('tags');
  1234. $rules = $table->rulesChecker();
  1235. $rules->add($rules->validCount('tags', 3));
  1236. $this->assertFalse($table->save($entity));
  1237. $this->assertEquals($entity->errors(), [
  1238. 'tags' => [
  1239. '_validCount' => 'The count does not match >3'
  1240. ]
  1241. ]);
  1242. // Testing that undesired types fail
  1243. $entity->tags = null;
  1244. $this->assertFalse($table->save($entity));
  1245. $entity->tags = new \stdClass();
  1246. $this->assertFalse($table->save($entity));
  1247. $entity->tags = 'string';
  1248. $this->assertFalse($table->save($entity));
  1249. $entity->tags = 123456;
  1250. $this->assertFalse($table->save($entity));
  1251. $entity->tags = 0.512;
  1252. $this->assertFalse($table->save($entity));
  1253. }
  1254. }