EntityContextTest.php 35 KB

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