EntityContextTest.php 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156
  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\View\Form;
  16. use ArrayIterator;
  17. use ArrayObject;
  18. use Cake\Collection\Collection;
  19. use Cake\Network\Request;
  20. use Cake\ORM\Entity;
  21. use Cake\ORM\Table;
  22. use Cake\ORM\TableRegistry;
  23. use Cake\TestSuite\TestCase;
  24. use Cake\Validation\Validator;
  25. use Cake\View\Form\EntityContext;
  26. /**
  27. * Test stub.
  28. */
  29. class Article extends Entity
  30. {
  31. /**
  32. * Testing stub method.
  33. *
  34. * @return bool
  35. */
  36. public function isRequired()
  37. {
  38. return true;
  39. }
  40. }
  41. /**
  42. * Entity context test case.
  43. */
  44. class EntityContextTest extends TestCase
  45. {
  46. /**
  47. * Fixtures to use.
  48. *
  49. * @var array
  50. */
  51. public $fixtures = ['core.articles', 'core.comments'];
  52. /**
  53. * setup method.
  54. *
  55. * @return void
  56. */
  57. public function setUp()
  58. {
  59. parent::setUp();
  60. $this->request = new Request();
  61. }
  62. /**
  63. * Test getting primary key data.
  64. *
  65. * @return void
  66. */
  67. public function testPrimaryKey()
  68. {
  69. $row = new Article();
  70. $context = new EntityContext($this->request, [
  71. 'entity' => $row,
  72. ]);
  73. $this->assertEquals(['id'], $context->primaryKey());
  74. }
  75. /**
  76. * Test isPrimaryKey
  77. *
  78. * @return void
  79. */
  80. public function testIsPrimaryKey()
  81. {
  82. $row = new Article();
  83. $context = new EntityContext($this->request, [
  84. 'entity' => $row,
  85. ]);
  86. $this->assertTrue($context->isPrimaryKey('id'));
  87. $this->assertFalse($context->isPrimaryKey('title'));
  88. $this->assertTrue($context->isPrimaryKey('1.id'));
  89. $this->assertTrue($context->isPrimaryKey('Articles.1.id'));
  90. $this->assertTrue($context->isPrimaryKey('comments.0.id'));
  91. $this->assertTrue($context->isPrimaryKey('1.comments.0.id'));
  92. $this->assertFalse($context->isPrimaryKey('1.comments.0.comment'));
  93. $this->assertFalse($context->isPrimaryKey('Articles.1.comments.0.comment'));
  94. }
  95. /**
  96. * Test isCreate on a single entity.
  97. *
  98. * @return void
  99. */
  100. public function testIsCreateSingle()
  101. {
  102. $row = new Article();
  103. $context = new EntityContext($this->request, [
  104. 'entity' => $row,
  105. ]);
  106. $this->assertTrue($context->isCreate());
  107. $row->isNew(false);
  108. $this->assertFalse($context->isCreate());
  109. $row->isNew(true);
  110. $this->assertTrue($context->isCreate());
  111. }
  112. /**
  113. * Test isCreate on a collection.
  114. *
  115. * @dataProvider collectionProvider
  116. * @return void
  117. */
  118. public function testIsCreateCollection($collection)
  119. {
  120. $context = new EntityContext($this->request, [
  121. 'entity' => $collection,
  122. ]);
  123. $this->assertTrue($context->isCreate());
  124. }
  125. /**
  126. * Test an invalid table scope throws an error.
  127. *
  128. * @expectedException \RuntimeException
  129. * @expectedExceptionMessage Unable to find table class for current entity
  130. */
  131. public function testInvalidTable()
  132. {
  133. $row = new \StdClass();
  134. $context = new EntityContext($this->request, [
  135. 'entity' => $row,
  136. ]);
  137. }
  138. /**
  139. * Tests that passing a plain entity will give an error as it cannot be matched
  140. *
  141. * @expectedException \RuntimeException
  142. * @expectedExceptionMessage Unable to find table class for current entity
  143. */
  144. public function testDefaultEntityError()
  145. {
  146. $context = new EntityContext($this->request, [
  147. 'entity' => new \Cake\ORM\Entity,
  148. ]);
  149. }
  150. /**
  151. * Tests that the table can be derived from the entity source if it is present
  152. *
  153. * @return void
  154. */
  155. public function testTableFromEntitySource()
  156. {
  157. $entity = new Entity;
  158. $entity->source('Articles');
  159. $context = new EntityContext($this->request, [
  160. 'entity' => $entity,
  161. ]);
  162. $expected = ['id', 'author_id', 'title', 'body', 'published'];
  163. $this->assertEquals($expected, $context->fieldNames());
  164. }
  165. /**
  166. * Test operations with no entity.
  167. *
  168. * @return void
  169. */
  170. public function testOperationsNoEntity()
  171. {
  172. $context = new EntityContext($this->request, [
  173. 'table' => 'Articles'
  174. ]);
  175. $this->assertNull($context->val('title'));
  176. $this->assertFalse($context->isRequired('title'));
  177. $this->assertFalse($context->hasError('title'));
  178. $this->assertEquals('string', $context->type('title'));
  179. $this->assertEquals([], $context->error('title'));
  180. $attrs = $context->attributes('title');
  181. $this->assertArrayHasKey('length', $attrs);
  182. $this->assertArrayHasKey('precision', $attrs);
  183. }
  184. /**
  185. * Test operations that lack a table argument.
  186. *
  187. * @return void
  188. */
  189. public function testOperationsNoTableArg()
  190. {
  191. $row = new Article([
  192. 'title' => 'Test entity',
  193. 'body' => 'Something new'
  194. ]);
  195. $row->errors('title', ['Title is required.']);
  196. $context = new EntityContext($this->request, [
  197. 'entity' => $row,
  198. ]);
  199. $result = $context->val('title');
  200. $this->assertEquals($row->title, $result);
  201. $result = $context->error('title');
  202. $this->assertEquals($row->errors('title'), $result);
  203. $this->assertTrue($context->hasError('title'));
  204. }
  205. /**
  206. * Test collection operations that lack a table argument.
  207. *
  208. * @dataProvider collectionProvider
  209. * @return void
  210. */
  211. public function testCollectionOperationsNoTableArg($collection)
  212. {
  213. $context = new EntityContext($this->request, [
  214. 'entity' => $collection,
  215. ]);
  216. $result = $context->val('0.title');
  217. $this->assertEquals('First post', $result);
  218. $result = $context->error('1.body');
  219. $this->assertEquals(['Not long enough'], $result);
  220. }
  221. /**
  222. * Data provider for testing collections.
  223. *
  224. * @return array
  225. */
  226. public static function collectionProvider()
  227. {
  228. $one = new Article([
  229. 'title' => 'First post',
  230. 'body' => 'Stuff',
  231. 'user' => new Entity(['username' => 'mark'])
  232. ]);
  233. $one->errors('title', 'Required field');
  234. $two = new Article([
  235. 'title' => 'Second post',
  236. 'body' => 'Some text',
  237. 'user' => new Entity(['username' => 'jose'])
  238. ]);
  239. $two->errors('body', 'Not long enough');
  240. return [
  241. 'array' => [[$one, $two]],
  242. 'basic iterator' => [new ArrayObject([$one, $two])],
  243. 'array iterator' => [new ArrayIterator([$one, $two])],
  244. 'collection' => [new Collection([$one, $two])],
  245. ];
  246. }
  247. /**
  248. * Test operations on a collection of entities.
  249. *
  250. * @dataProvider collectionProvider
  251. * @return void
  252. */
  253. public function testValOnCollections($collection)
  254. {
  255. $context = new EntityContext($this->request, [
  256. 'entity' => $collection,
  257. 'table' => 'Articles',
  258. ]);
  259. $result = $context->val('0.title');
  260. $this->assertEquals('First post', $result);
  261. $result = $context->val('0.user.username');
  262. $this->assertEquals('mark', $result);
  263. $result = $context->val('1.title');
  264. $this->assertEquals('Second post', $result);
  265. $result = $context->val('1.user.username');
  266. $this->assertEquals('jose', $result);
  267. $this->assertNull($context->val('nope'));
  268. $this->assertNull($context->val('99.title'));
  269. }
  270. /**
  271. * Test operations on a collection of entities when prefixing with the
  272. * table name
  273. *
  274. * @dataProvider collectionProvider
  275. * @return void
  276. */
  277. public function testValOnCollectionsWithRootName($collection)
  278. {
  279. $context = new EntityContext($this->request, [
  280. 'entity' => $collection,
  281. 'table' => 'Articles',
  282. ]);
  283. $result = $context->val('Articles.0.title');
  284. $this->assertEquals('First post', $result);
  285. $result = $context->val('Articles.0.user.username');
  286. $this->assertEquals('mark', $result);
  287. $result = $context->val('Articles.1.title');
  288. $this->assertEquals('Second post', $result);
  289. $result = $context->val('Articles.1.user.username');
  290. $this->assertEquals('jose', $result);
  291. $this->assertNull($context->val('Articles.99.title'));
  292. }
  293. /**
  294. * Test error operations on a collection of entities.
  295. *
  296. * @dataProvider collectionProvider
  297. * @return void
  298. */
  299. public function testErrorsOnCollections($collection)
  300. {
  301. $context = new EntityContext($this->request, [
  302. 'entity' => $collection,
  303. 'table' => 'Articles',
  304. ]);
  305. $this->assertTrue($context->hasError('0.title'));
  306. $this->assertEquals(['Required field'], $context->error('0.title'));
  307. $this->assertFalse($context->hasError('0.body'));
  308. $this->assertFalse($context->hasError('1.title'));
  309. $this->assertEquals(['Not long enough'], $context->error('1.body'));
  310. $this->assertTrue($context->hasError('1.body'));
  311. $this->assertFalse($context->hasError('nope'));
  312. $this->assertFalse($context->hasError('99.title'));
  313. }
  314. /**
  315. * Test schema operations on a collection of entities.
  316. *
  317. * @dataProvider collectionProvider
  318. * @return void
  319. */
  320. public function testSchemaOnCollections($collection)
  321. {
  322. $this->_setupTables();
  323. $context = new EntityContext($this->request, [
  324. 'entity' => $collection,
  325. 'table' => 'Articles',
  326. ]);
  327. $this->assertEquals('string', $context->type('0.title'));
  328. $this->assertEquals('text', $context->type('1.body'));
  329. $this->assertEquals('string', $context->type('0.user.username'));
  330. $this->assertEquals('string', $context->type('1.user.username'));
  331. $this->assertEquals('string', $context->type('99.title'));
  332. $this->assertNull($context->type('0.nope'));
  333. $expected = ['length' => 255, 'precision' => null];
  334. $this->assertEquals($expected, $context->attributes('0.user.username'));
  335. }
  336. /**
  337. * Test validation operations on a collection of entities.
  338. *
  339. * @dataProvider collectionProvider
  340. * @return void
  341. */
  342. public function testValidatorsOnCollections($collection)
  343. {
  344. $this->_setupTables();
  345. $context = new EntityContext($this->request, [
  346. 'entity' => $collection,
  347. 'table' => 'Articles',
  348. 'validator' => [
  349. 'Articles' => 'create',
  350. 'Users' => 'custom',
  351. ]
  352. ]);
  353. $this->assertFalse($context->isRequired('nope'));
  354. $this->assertTrue($context->isRequired('0.title'));
  355. $this->assertTrue($context->isRequired('0.user.username'));
  356. $this->assertFalse($context->isRequired('1.body'));
  357. $this->assertTrue($context->isRequired('99.title'));
  358. $this->assertFalse($context->isRequired('99.nope'));
  359. }
  360. /**
  361. * Test reading data.
  362. *
  363. * @return void
  364. */
  365. public function testValBasic()
  366. {
  367. $row = new Article([
  368. 'title' => 'Test entity',
  369. 'body' => 'Something new'
  370. ]);
  371. $context = new EntityContext($this->request, [
  372. 'entity' => $row,
  373. 'table' => 'Articles',
  374. ]);
  375. $result = $context->val('title');
  376. $this->assertEquals($row->title, $result);
  377. $result = $context->val('body');
  378. $this->assertEquals($row->body, $result);
  379. $result = $context->val('nope');
  380. $this->assertNull($result);
  381. }
  382. /**
  383. * Test reading array values from an entity.
  384. *
  385. * @return void
  386. */
  387. public function testValGetArrayValue()
  388. {
  389. $row = new Article([
  390. 'title' => 'Test entity',
  391. 'types' => [1, 2, 3],
  392. 'author' => new Entity([
  393. 'roles' => ['admin', 'publisher']
  394. ])
  395. ]);
  396. $context = new EntityContext($this->request, [
  397. 'entity' => $row,
  398. 'table' => 'Articles',
  399. ]);
  400. $result = $context->val('types');
  401. $this->assertEquals($row->types, $result);
  402. $result = $context->val('author.roles');
  403. $this->assertEquals($row->author->roles, $result);
  404. }
  405. /**
  406. * Test that val() reads from the request.
  407. *
  408. * @return void
  409. */
  410. public function testValReadsRequest()
  411. {
  412. $this->request->data = [
  413. 'title' => 'New title',
  414. 'notInEntity' => 'yes',
  415. ];
  416. $row = new Article([
  417. 'title' => 'Test entity',
  418. 'body' => 'Something new'
  419. ]);
  420. $context = new EntityContext($this->request, [
  421. 'entity' => $row,
  422. 'table' => 'Articles',
  423. ]);
  424. $this->assertEquals('New title', $context->val('title'));
  425. $this->assertEquals('yes', $context->val('notInEntity'));
  426. $this->assertEquals($row->body, $context->val('body'));
  427. }
  428. /**
  429. * Test reading values from associated entities.
  430. *
  431. * @return void
  432. */
  433. public function testValAssociated()
  434. {
  435. $row = new Article([
  436. 'title' => 'Test entity',
  437. 'user' => new Entity([
  438. 'username' => 'mark',
  439. 'fname' => 'Mark'
  440. ]),
  441. 'comments' => [
  442. new Entity(['comment' => 'Test comment']),
  443. new Entity(['comment' => 'Second comment']),
  444. ]
  445. ]);
  446. $context = new EntityContext($this->request, [
  447. 'entity' => $row,
  448. 'table' => 'Articles',
  449. ]);
  450. $result = $context->val('user.fname');
  451. $this->assertEquals($row->user->fname, $result);
  452. $result = $context->val('comments.0.comment');
  453. $this->assertEquals($row->comments[0]->comment, $result);
  454. $result = $context->val('comments.1.comment');
  455. $this->assertEquals($row->comments[1]->comment, $result);
  456. $result = $context->val('comments.0.nope');
  457. $this->assertNull($result);
  458. $result = $context->val('comments.0.nope.no_way');
  459. $this->assertNull($result);
  460. }
  461. /**
  462. * Tests that trying to get values from missing associations returns null
  463. *
  464. * @return void
  465. */
  466. public function testValMissingAssociation()
  467. {
  468. $row = new Article([
  469. 'id' => 1
  470. ]);
  471. $context = new EntityContext($this->request, [
  472. 'entity' => $row,
  473. 'table' => 'Articles',
  474. ]);
  475. $result = $context->val('id');
  476. $this->assertEquals($row->id, $result);
  477. $this->assertNull($context->val('profile.id'));
  478. }
  479. /**
  480. * Test reading values from associated entities.
  481. *
  482. * @return void
  483. */
  484. public function testValAssociatedHasMany()
  485. {
  486. $row = new Article([
  487. 'title' => 'First post',
  488. 'user' => new Entity([
  489. 'username' => 'mark',
  490. 'fname' => 'Mark',
  491. 'articles' => [
  492. new Article(['title' => 'First post']),
  493. new Article(['title' => 'Second post']),
  494. ]
  495. ]),
  496. ]);
  497. $context = new EntityContext($this->request, [
  498. 'entity' => $row,
  499. 'table' => 'Articles',
  500. ]);
  501. $result = $context->val('user.articles.0.title');
  502. $this->assertEquals('First post', $result);
  503. $result = $context->val('user.articles.1.title');
  504. $this->assertEquals('Second post', $result);
  505. }
  506. /**
  507. * Test reading values for magic _ids input
  508. *
  509. * @return void
  510. */
  511. public function testValAssociatedDefaultIds()
  512. {
  513. $row = new Article([
  514. 'title' => 'First post',
  515. 'user' => new Entity([
  516. 'username' => 'mark',
  517. 'fname' => 'Mark',
  518. 'groups' => [
  519. new Entity(['title' => 'PHP', 'id' => 1]),
  520. new Entity(['title' => 'Javascript', 'id' => 2]),
  521. ]
  522. ]),
  523. ]);
  524. $context = new EntityContext($this->request, [
  525. 'entity' => $row,
  526. 'table' => 'Articles',
  527. ]);
  528. $result = $context->val('user.groups._ids');
  529. $this->assertEquals([1, 2], $result);
  530. }
  531. /**
  532. * Test reading values for magic _ids input
  533. *
  534. * @return void
  535. */
  536. public function testValAssociatedCustomIds()
  537. {
  538. $row = new Article([
  539. 'title' => 'First post',
  540. 'user' => new Entity([
  541. 'username' => 'mark',
  542. 'fname' => 'Mark',
  543. 'groups' => [
  544. new Entity(['title' => 'PHP', 'thing' => 1]),
  545. new Entity(['title' => 'Javascript', 'thing' => 4]),
  546. ]
  547. ]),
  548. ]);
  549. $context = new EntityContext($this->request, [
  550. 'entity' => $row,
  551. 'table' => 'Articles',
  552. ]);
  553. TableRegistry::get('Users')->belongsToMany('Groups');
  554. TableRegistry::get('Groups')->primaryKey('thing');
  555. $result = $context->val('user.groups._ids');
  556. $this->assertEquals([1, 4], $result);
  557. }
  558. /**
  559. * Test validator for boolean fields.
  560. *
  561. * @return void
  562. */
  563. public function testIsRequiredBooleanField()
  564. {
  565. $this->_setupTables();
  566. $context = new EntityContext($this->request, [
  567. 'entity' => new Entity(),
  568. 'table' => 'Articles',
  569. ]);
  570. $articles = TableRegistry::get('Articles');
  571. $articles->schema()->addColumn('comments_on', [
  572. 'type' => 'boolean'
  573. ]);
  574. $validator = $articles->validator();
  575. $validator->add('comments_on', 'is_bool', [
  576. 'rule' => 'boolean'
  577. ]);
  578. $articles->validator('default', $validator);
  579. $this->assertFalse($context->isRequired('title'));
  580. }
  581. /**
  582. * Test validator as a string.
  583. *
  584. * @return void
  585. */
  586. public function testIsRequiredStringValidator()
  587. {
  588. $this->_setupTables();
  589. $context = new EntityContext($this->request, [
  590. 'entity' => new Entity(),
  591. 'table' => 'Articles',
  592. 'validator' => 'create',
  593. ]);
  594. $this->assertTrue($context->isRequired('title'));
  595. $this->assertFalse($context->isRequired('body'));
  596. $this->assertFalse($context->isRequired('Herp.derp.derp'));
  597. $this->assertFalse($context->isRequired('nope'));
  598. $this->assertFalse($context->isRequired(''));
  599. }
  600. /**
  601. * Test isRequired on associated entities.
  602. *
  603. * @return void
  604. */
  605. public function testIsRequiredAssociatedHasMany()
  606. {
  607. $this->_setupTables();
  608. $comments = TableRegistry::get('Comments');
  609. $validator = $comments->validator();
  610. $validator->add('user_id', 'number', [
  611. 'rule' => 'numeric',
  612. ]);
  613. $row = new Article([
  614. 'title' => 'My title',
  615. 'comments' => [
  616. new Entity(['comment' => 'First comment']),
  617. new Entity(['comment' => 'Second comment']),
  618. ]
  619. ]);
  620. $context = new EntityContext($this->request, [
  621. 'entity' => $row,
  622. 'table' => 'Articles',
  623. 'validator' => 'default',
  624. ]);
  625. $this->assertTrue($context->isRequired('comments.0.user_id'));
  626. $this->assertFalse($context->isRequired('comments.0.other'));
  627. $this->assertFalse($context->isRequired('user.0.other'));
  628. $this->assertFalse($context->isRequired(''));
  629. }
  630. /**
  631. * Test isRequired on associated entities with custom validators.
  632. *
  633. * Ensures that missing associations use the correct entity class
  634. * so provider methods work correctly.
  635. *
  636. * @return void
  637. */
  638. public function testIsRequiredAssociatedCustomValidator()
  639. {
  640. $this->_setupTables();
  641. $users = TableRegistry::get('Users');
  642. $articles = TableRegistry::get('Articles');
  643. $validator = $articles->validator();
  644. $validator->notEmpty('title', 'nope', function ($context) {
  645. return $context['providers']['entity']->isRequired();
  646. });
  647. $articles->validator('default', $validator);
  648. $row = new Entity([
  649. 'username' => 'mark'
  650. ]);
  651. $context = new EntityContext($this->request, [
  652. 'entity' => $row,
  653. 'table' => 'Users',
  654. 'validator' => 'default',
  655. ]);
  656. $this->assertTrue($context->isRequired('articles.0.title'));
  657. }
  658. /**
  659. * Test isRequired on associated entities.
  660. *
  661. * @return void
  662. */
  663. public function testIsRequiredAssociatedHasManyMissingObject()
  664. {
  665. $this->_setupTables();
  666. $comments = TableRegistry::get('Comments');
  667. $validator = $comments->validator();
  668. $validator->allowEmpty('comment', function ($context) {
  669. return $context['providers']['entity']->isNew();
  670. });
  671. $row = new Article([
  672. 'title' => 'My title',
  673. 'comments' => [
  674. new Entity(['comment' => 'First comment'], ['markNew' => false]),
  675. ]
  676. ]);
  677. $context = new EntityContext($this->request, [
  678. 'entity' => $row,
  679. 'table' => 'Articles',
  680. 'validator' => 'default',
  681. ]);
  682. $this->assertTrue(
  683. $context->isRequired('comments.0.comment'),
  684. 'comment is required as object is not new'
  685. );
  686. $this->assertFalse(
  687. $context->isRequired('comments.1.comment'),
  688. 'comment is not required as missing object is "new"'
  689. );
  690. }
  691. /**
  692. * Test isRequired on associated entities with custom validators.
  693. *
  694. * @return void
  695. */
  696. public function testIsRequiredAssociatedValidator()
  697. {
  698. $this->_setupTables();
  699. $row = new Article([
  700. 'title' => 'My title',
  701. 'comments' => [
  702. new Entity(['comment' => 'First comment']),
  703. new Entity(['comment' => 'Second comment']),
  704. ]
  705. ]);
  706. $context = new EntityContext($this->request, [
  707. 'entity' => $row,
  708. 'table' => 'Articles',
  709. 'validator' => [
  710. 'Articles' => 'create',
  711. 'Comments' => 'custom'
  712. ]
  713. ]);
  714. $this->assertTrue($context->isRequired('title'));
  715. $this->assertFalse($context->isRequired('body'));
  716. $this->assertTrue($context->isRequired('comments.0.comment'));
  717. $this->assertTrue($context->isRequired('comments.1.comment'));
  718. }
  719. /**
  720. * Test isRequired on associated entities.
  721. *
  722. * @return void
  723. */
  724. public function testIsRequiredAssociatedBelongsTo()
  725. {
  726. $this->_setupTables();
  727. $row = new Article([
  728. 'title' => 'My title',
  729. 'user' => new Entity(['username' => 'Mark']),
  730. ]);
  731. $context = new EntityContext($this->request, [
  732. 'entity' => $row,
  733. 'table' => 'Articles',
  734. 'validator' => [
  735. 'Articles' => 'create',
  736. 'Users' => 'custom'
  737. ]
  738. ]);
  739. $this->assertTrue($context->isRequired('user.username'));
  740. $this->assertFalse($context->isRequired('user.first_name'));
  741. }
  742. /**
  743. * Test type() basic
  744. *
  745. * @return void
  746. */
  747. public function testType()
  748. {
  749. $this->_setupTables();
  750. $row = new Article([
  751. 'title' => 'My title',
  752. 'body' => 'Some content',
  753. ]);
  754. $context = new EntityContext($this->request, [
  755. 'entity' => $row,
  756. 'table' => 'Articles',
  757. ]);
  758. $this->assertEquals('string', $context->type('title'));
  759. $this->assertEquals('text', $context->type('body'));
  760. $this->assertEquals('integer', $context->type('user_id'));
  761. $this->assertNull($context->type('nope'));
  762. }
  763. /**
  764. * Test getting types for associated records.
  765. *
  766. * @return void
  767. */
  768. public function testTypeAssociated()
  769. {
  770. $this->_setupTables();
  771. $row = new Article([
  772. 'title' => 'My title',
  773. 'user' => new Entity(['username' => 'Mark']),
  774. ]);
  775. $context = new EntityContext($this->request, [
  776. 'entity' => $row,
  777. 'table' => 'Articles',
  778. ]);
  779. $this->assertEquals('string', $context->type('user.username'));
  780. $this->assertEquals('text', $context->type('user.bio'));
  781. $this->assertNull($context->type('user.nope'));
  782. }
  783. /**
  784. * Test attributes for fields.
  785. *
  786. * @return void
  787. */
  788. public function testAttributes()
  789. {
  790. $this->_setupTables();
  791. $row = new Article([
  792. 'title' => 'My title',
  793. 'user' => new Entity(['username' => 'Mark']),
  794. ]);
  795. $context = new EntityContext($this->request, [
  796. 'entity' => $row,
  797. 'table' => 'Articles',
  798. ]);
  799. $expected = [
  800. 'length' => 255, 'precision' => null
  801. ];
  802. $this->assertEquals($expected, $context->attributes('title'));
  803. $expected = [
  804. 'length' => null, 'precision' => null
  805. ];
  806. $this->assertEquals($expected, $context->attributes('body'));
  807. $expected = [
  808. 'length' => 10, 'precision' => 3
  809. ];
  810. $this->assertEquals($expected, $context->attributes('user.rating'));
  811. }
  812. /**
  813. * Test hasError
  814. *
  815. * @return void
  816. */
  817. public function testHasError()
  818. {
  819. $this->_setupTables();
  820. $row = new Article([
  821. 'title' => 'My title',
  822. 'user' => new Entity(['username' => 'Mark']),
  823. ]);
  824. $row->errors('title', []);
  825. $row->errors('body', 'Gotta have one');
  826. $row->errors('user_id', ['Required field']);
  827. $context = new EntityContext($this->request, [
  828. 'entity' => $row,
  829. 'table' => 'Articles',
  830. ]);
  831. $this->assertFalse($context->hasError('title'));
  832. $this->assertFalse($context->hasError('nope'));
  833. $this->assertTrue($context->hasError('body'));
  834. $this->assertTrue($context->hasError('user_id'));
  835. }
  836. /**
  837. * Test hasError on associated records
  838. *
  839. * @return void
  840. */
  841. public function testHasErrorAssociated()
  842. {
  843. $this->_setupTables();
  844. $row = new Article([
  845. 'title' => 'My title',
  846. 'user' => new Entity(['username' => 'Mark']),
  847. ]);
  848. $row->errors('title', []);
  849. $row->errors('body', 'Gotta have one');
  850. $row->user->errors('username', ['Required']);
  851. $context = new EntityContext($this->request, [
  852. 'entity' => $row,
  853. 'table' => 'Articles',
  854. ]);
  855. $this->assertTrue($context->hasError('user.username'));
  856. $this->assertFalse($context->hasError('user.nope'));
  857. $this->assertFalse($context->hasError('no.nope'));
  858. }
  859. /**
  860. * Test error
  861. *
  862. * @return void
  863. */
  864. public function testError()
  865. {
  866. $this->_setupTables();
  867. $row = new Article([
  868. 'title' => 'My title',
  869. 'user' => new Entity(['username' => 'Mark']),
  870. ]);
  871. $row->errors('title', []);
  872. $row->errors('body', 'Gotta have one');
  873. $row->errors('user_id', ['Required field']);
  874. $row->user->errors('username', ['Required']);
  875. $context = new EntityContext($this->request, [
  876. 'entity' => $row,
  877. 'table' => 'Articles',
  878. ]);
  879. $this->assertEquals([], $context->error('title'));
  880. $expected = ['Gotta have one'];
  881. $this->assertEquals($expected, $context->error('body'));
  882. $expected = ['Required'];
  883. $this->assertEquals($expected, $context->error('user.username'));
  884. }
  885. /**
  886. * Test error on associated entities.
  887. *
  888. * @return void
  889. */
  890. public function testErrorAssociatedHasMany()
  891. {
  892. $this->_setupTables();
  893. $comments = TableRegistry::get('Comments');
  894. $row = new Article([
  895. 'title' => 'My title',
  896. 'comments' => [
  897. new Entity(['comment' => '']),
  898. new Entity(['comment' => 'Second comment']),
  899. ]
  900. ]);
  901. $row->comments[0]->errors('comment', ['Is required']);
  902. $row->comments[0]->errors('article_id', ['Is required']);
  903. $context = new EntityContext($this->request, [
  904. 'entity' => $row,
  905. 'table' => 'Articles',
  906. 'validator' => 'default',
  907. ]);
  908. $this->assertEquals([], $context->error('title'));
  909. $this->assertEquals([], $context->error('comments.0.user_id'));
  910. $this->assertEquals([], $context->error('comments.0'));
  911. $this->assertEquals(['Is required'], $context->error('comments.0.comment'));
  912. $this->assertEquals(['Is required'], $context->error('comments.0.article_id'));
  913. $this->assertEquals([], $context->error('comments.1'));
  914. $this->assertEquals([], $context->error('comments.1.comment'));
  915. $this->assertEquals([], $context->error('comments.1.article_id'));
  916. }
  917. /**
  918. * Setup tables for tests.
  919. *
  920. * @return void
  921. */
  922. protected function _setupTables()
  923. {
  924. $articles = TableRegistry::get('Articles');
  925. $articles->belongsTo('Users');
  926. $articles->hasMany('Comments');
  927. $articles->entityClass(__NAMESPACE__ . '\Article');
  928. $comments = TableRegistry::get('Comments');
  929. $users = TableRegistry::get('Users');
  930. $users->hasMany('Articles');
  931. $articles->schema([
  932. 'id' => ['type' => 'integer', 'length' => 11, 'null' => false],
  933. 'title' => ['type' => 'string', 'length' => 255],
  934. 'user_id' => ['type' => 'integer', 'length' => 11, 'null' => false],
  935. 'body' => ['type' => 'text']
  936. ]);
  937. $users->schema([
  938. 'id' => ['type' => 'integer', 'length' => 11],
  939. 'username' => ['type' => 'string', 'length' => 255],
  940. 'bio' => ['type' => 'text'],
  941. 'rating' => ['type' => 'decimal', 'length' => 10, 'precision' => 3],
  942. ]);
  943. $validator = new Validator();
  944. $validator->add('title', 'minlength', [
  945. 'rule' => ['minlength', 10]
  946. ])
  947. ->add('body', 'maxlength', [
  948. 'rule' => ['maxlength', 1000]
  949. ])->allowEmpty('body');
  950. $articles->validator('create', $validator);
  951. $validator = new Validator();
  952. $validator->add('username', 'length', [
  953. 'rule' => ['minlength', 10]
  954. ]);
  955. $users->validator('custom', $validator);
  956. $validator = new Validator();
  957. $validator->add('comment', 'length', [
  958. 'rule' => ['minlength', 10]
  959. ]);
  960. $comments->validator('custom', $validator);
  961. }
  962. /**
  963. * Test the fieldnames method.
  964. *
  965. * @return void
  966. */
  967. public function testFieldNames()
  968. {
  969. $context = new EntityContext($this->request, [
  970. 'entity' => new Entity(),
  971. 'table' => 'Articles',
  972. ]);
  973. $articles = TableRegistry::get('Articles');
  974. $this->assertEquals($articles->schema()->columns(), $context->fieldNames());
  975. }
  976. /**
  977. * Test automatic entity provider setting
  978. *
  979. * @return void
  980. */
  981. public function testValidatorEntityProvider()
  982. {
  983. $row = new Article([
  984. 'title' => 'Test entity',
  985. 'body' => 'Something new'
  986. ]);
  987. $context = new EntityContext($this->request, [
  988. 'entity' => $row,
  989. 'table' => 'Articles',
  990. ]);
  991. $context->isRequired('title');
  992. $articles = TableRegistry::get('Articles');
  993. $this->assertSame($row, $articles->validator()->provider('entity'));
  994. $row = new Article([
  995. 'title' => 'First post',
  996. 'user' => new Entity([
  997. 'username' => 'mark',
  998. 'fname' => 'Mark',
  999. 'articles' => [
  1000. new Article(['title' => 'First post']),
  1001. new Article(['title' => 'Second post']),
  1002. ]
  1003. ]),
  1004. ]);
  1005. $context = new EntityContext($this->request, [
  1006. 'entity' => $row,
  1007. 'table' => 'Articles',
  1008. ]);
  1009. $validator = $articles->validator();
  1010. $context->isRequired('user.articles.0.title');
  1011. $this->assertSame($row->user->articles[0], $validator->provider('entity'));
  1012. $context->isRequired('user.articles.1.title');
  1013. $this->assertSame($row->user->articles[1], $validator->provider('entity'));
  1014. $context->isRequired('title');
  1015. $this->assertSame($row, $validator->provider('entity'));
  1016. }
  1017. }