EntityContextTest.php 36 KB

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