EntityContextTest.php 39 KB

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