EntityContextTest.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\View\Form;
  16. use ArrayIterator;
  17. use ArrayObject;
  18. use Cake\Collection\Collection;
  19. use Cake\Http\ServerRequest;
  20. use Cake\ORM\Entity;
  21. use Cake\TestSuite\TestCase;
  22. use Cake\Validation\Validator;
  23. use Cake\View\Form\EntityContext;
  24. use TestApp\Model\Entity\ArticlesTag;
  25. use TestApp\Model\Entity\Tag;
  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', 'core.articles_tags', 'core.tags'];
  52. /**
  53. * setup method.
  54. *
  55. * @return void
  56. */
  57. public function setUp()
  58. {
  59. parent::setUp();
  60. $this->request = new ServerRequest();
  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. $this->_setupTables();
  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. */
  145. public function testInvalidTable()
  146. {
  147. $this->expectException(\RuntimeException::class);
  148. $this->expectExceptionMessage('Unable to find table class for current entity');
  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. */
  158. public function testDefaultEntityError()
  159. {
  160. $this->expectException(\RuntimeException::class);
  161. $this->expectExceptionMessage('Unable to find table class for current entity');
  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->setSource('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->setError('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->getError('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->setError('title', 'Required field');
  250. $two = new Article([
  251. 'title' => 'Second post',
  252. 'body' => 'Some text',
  253. 'user' => new Entity(['username' => 'jose'])
  254. ]);
  255. $two->setError('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 default values when entity is an array.
  400. *
  401. * @return void
  402. */
  403. public function testValDefaultArray()
  404. {
  405. $context = new EntityContext($this->request, [
  406. 'entity' => new Article([
  407. 'prop' => ['title' => 'foo']
  408. ]),
  409. 'table' => 'Articles',
  410. ]);
  411. $this->assertEquals('foo', $context->val('prop.title', ['default' => 'bar']));
  412. $this->assertEquals('bar', $context->val('prop.nope', ['default' => 'bar']));
  413. }
  414. /**
  415. * Test reading array values from an entity.
  416. *
  417. * @return void
  418. */
  419. public function testValGetArrayValue()
  420. {
  421. $row = new Article([
  422. 'title' => 'Test entity',
  423. 'types' => [1, 2, 3],
  424. 'tag' => [
  425. 'name' => 'Test tag',
  426. ],
  427. 'author' => new Entity([
  428. 'roles' => ['admin', 'publisher'],
  429. 'aliases' => new ArrayObject(['dave', 'david']),
  430. ])
  431. ]);
  432. $context = new EntityContext($this->request, [
  433. 'entity' => $row,
  434. 'table' => 'Articles',
  435. ]);
  436. $result = $context->val('types');
  437. $this->assertEquals($row->types, $result);
  438. $result = $context->val('author.roles');
  439. $this->assertEquals($row->author->roles, $result);
  440. $result = $context->val('tag.name');
  441. $this->assertEquals($row->tag['name'], $result);
  442. $result = $context->val('author.aliases.0');
  443. $this->assertEquals($row->author->aliases[0], $result, 'ArrayAccess can be read');
  444. $this->assertNull($context->val('author.aliases.3'));
  445. $this->assertNull($context->val('tag.nope'));
  446. $this->assertNull($context->val('author.roles.3'));
  447. }
  448. /**
  449. * Test that val() reads from the request.
  450. *
  451. * @return void
  452. */
  453. public function testValReadsRequest()
  454. {
  455. $this->request = $this->request->withParsedBody([
  456. 'title' => 'New title',
  457. 'notInEntity' => 'yes',
  458. ]);
  459. $row = new Article([
  460. 'title' => 'Test entity',
  461. 'body' => 'Something new'
  462. ]);
  463. $context = new EntityContext($this->request, [
  464. 'entity' => $row,
  465. 'table' => 'Articles',
  466. ]);
  467. $this->assertEquals('New title', $context->val('title'));
  468. $this->assertEquals('yes', $context->val('notInEntity'));
  469. $this->assertEquals($row->body, $context->val('body'));
  470. }
  471. /**
  472. * Test reading values from associated entities.
  473. *
  474. * @return void
  475. */
  476. public function testValAssociated()
  477. {
  478. $row = new Article([
  479. 'title' => 'Test entity',
  480. 'user' => new Entity([
  481. 'username' => 'mark',
  482. 'fname' => 'Mark'
  483. ]),
  484. 'comments' => [
  485. new Entity(['comment' => 'Test comment']),
  486. new Entity(['comment' => 'Second comment']),
  487. ]
  488. ]);
  489. $context = new EntityContext($this->request, [
  490. 'entity' => $row,
  491. 'table' => 'Articles',
  492. ]);
  493. $result = $context->val('user.fname');
  494. $this->assertEquals($row->user->fname, $result);
  495. $result = $context->val('comments.0.comment');
  496. $this->assertEquals($row->comments[0]->comment, $result);
  497. $result = $context->val('comments.1.comment');
  498. $this->assertEquals($row->comments[1]->comment, $result);
  499. $result = $context->val('comments.0.nope');
  500. $this->assertNull($result);
  501. $result = $context->val('comments.0.nope.no_way');
  502. $this->assertNull($result);
  503. }
  504. /**
  505. * Tests that trying to get values from missing associations returns null
  506. *
  507. * @return void
  508. */
  509. public function testValMissingAssociation()
  510. {
  511. $row = new Article([
  512. 'id' => 1
  513. ]);
  514. $context = new EntityContext($this->request, [
  515. 'entity' => $row,
  516. 'table' => 'Articles',
  517. ]);
  518. $result = $context->val('id');
  519. $this->assertEquals($row->id, $result);
  520. $this->assertNull($context->val('profile.id'));
  521. }
  522. /**
  523. * Test reading values from associated entities.
  524. *
  525. * @return void
  526. */
  527. public function testValAssociatedHasMany()
  528. {
  529. $row = new Article([
  530. 'title' => 'First post',
  531. 'user' => new Entity([
  532. 'username' => 'mark',
  533. 'fname' => 'Mark',
  534. 'articles' => [
  535. new Article(['title' => 'First post']),
  536. new Article(['title' => 'Second post']),
  537. ]
  538. ]),
  539. ]);
  540. $context = new EntityContext($this->request, [
  541. 'entity' => $row,
  542. 'table' => 'Articles',
  543. ]);
  544. $result = $context->val('user.articles.0.title');
  545. $this->assertEquals('First post', $result);
  546. $result = $context->val('user.articles.1.title');
  547. $this->assertEquals('Second post', $result);
  548. }
  549. /**
  550. * Test reading values for magic _ids input
  551. *
  552. * @return void
  553. */
  554. public function testValAssociatedDefaultIds()
  555. {
  556. $row = new Article([
  557. 'title' => 'First post',
  558. 'user' => new Entity([
  559. 'username' => 'mark',
  560. 'fname' => 'Mark',
  561. 'groups' => [
  562. new Entity(['title' => 'PHP', 'id' => 1]),
  563. new Entity(['title' => 'Javascript', 'id' => 2]),
  564. ]
  565. ]),
  566. ]);
  567. $context = new EntityContext($this->request, [
  568. 'entity' => $row,
  569. 'table' => 'Articles',
  570. ]);
  571. $result = $context->val('user.groups._ids');
  572. $this->assertEquals([1, 2], $result);
  573. }
  574. /**
  575. * Test reading values for magic _ids input
  576. *
  577. * @return void
  578. */
  579. public function testValAssociatedCustomIds()
  580. {
  581. $this->_setupTables();
  582. $row = new Article([
  583. 'title' => 'First post',
  584. 'user' => new Entity([
  585. 'username' => 'mark',
  586. 'fname' => 'Mark',
  587. 'groups' => [
  588. new Entity(['title' => 'PHP', 'thing' => 1]),
  589. new Entity(['title' => 'Javascript', 'thing' => 4]),
  590. ]
  591. ]),
  592. ]);
  593. $context = new EntityContext($this->request, [
  594. 'entity' => $row,
  595. 'table' => 'Articles',
  596. ]);
  597. $this->getTableLocator()->get('Users')->belongsToMany('Groups');
  598. $this->getTableLocator()->get('Groups')->setPrimaryKey('thing');
  599. $result = $context->val('user.groups._ids');
  600. $this->assertEquals([1, 4], $result);
  601. }
  602. /**
  603. * Test getting default value from table schema.
  604. *
  605. * @return void
  606. */
  607. public function testValSchemaDefault()
  608. {
  609. $table = $this->getTableLocator()->get('Articles');
  610. $column = $table->getSchema()->getColumn('title');
  611. $table->getSchema()->addColumn('title', ['default' => 'default title'] + $column);
  612. $row = $table->newEntity();
  613. $context = new EntityContext($this->request, [
  614. 'entity' => $row,
  615. 'table' => 'Articles',
  616. ]);
  617. $result = $context->val('title');
  618. $this->assertEquals('default title', $result);
  619. }
  620. /**
  621. * Test validator for boolean fields.
  622. *
  623. * @return void
  624. */
  625. public function testIsRequiredBooleanField()
  626. {
  627. $this->_setupTables();
  628. $context = new EntityContext($this->request, [
  629. 'entity' => new Entity(),
  630. 'table' => 'Articles',
  631. ]);
  632. $articles = $this->getTableLocator()->get('Articles');
  633. $articles->getSchema()->addColumn('comments_on', [
  634. 'type' => 'boolean'
  635. ]);
  636. $validator = $articles->getValidator();
  637. $validator->add('comments_on', 'is_bool', [
  638. 'rule' => 'boolean'
  639. ]);
  640. $articles->setValidator('default', $validator);
  641. $this->assertFalse($context->isRequired('title'));
  642. }
  643. /**
  644. * Test validator as a string.
  645. *
  646. * @return void
  647. */
  648. public function testIsRequiredStringValidator()
  649. {
  650. $this->_setupTables();
  651. $context = new EntityContext($this->request, [
  652. 'entity' => new Entity(),
  653. 'table' => 'Articles',
  654. 'validator' => 'create',
  655. ]);
  656. $this->assertTrue($context->isRequired('title'));
  657. $this->assertFalse($context->isRequired('body'));
  658. $this->assertFalse($context->isRequired('Herp.derp.derp'));
  659. $this->assertFalse($context->isRequired('nope'));
  660. $this->assertFalse($context->isRequired(''));
  661. }
  662. /**
  663. * Test isRequired on associated entities.
  664. *
  665. * @return void
  666. */
  667. public function testIsRequiredAssociatedHasMany()
  668. {
  669. $this->_setupTables();
  670. $comments = $this->getTableLocator()->get('Comments');
  671. $validator = $comments->getValidator();
  672. $validator->add('user_id', 'number', [
  673. 'rule' => 'numeric',
  674. ]);
  675. $row = new Article([
  676. 'title' => 'My title',
  677. 'comments' => [
  678. new Entity(['comment' => 'First comment']),
  679. new Entity(['comment' => 'Second comment']),
  680. ]
  681. ]);
  682. $context = new EntityContext($this->request, [
  683. 'entity' => $row,
  684. 'table' => 'Articles',
  685. 'validator' => 'default',
  686. ]);
  687. $this->assertTrue($context->isRequired('comments.0.user_id'));
  688. $this->assertFalse($context->isRequired('comments.0.other'));
  689. $this->assertFalse($context->isRequired('user.0.other'));
  690. $this->assertFalse($context->isRequired(''));
  691. }
  692. /**
  693. * Test isRequired on associated entities with boolean fields
  694. *
  695. * @return void
  696. */
  697. public function testIsRequiredAssociatedHasManyBoolean()
  698. {
  699. $this->_setupTables();
  700. $comments = $this->getTableLocator()->get('Comments');
  701. $comments->getSchema()->addColumn('starred', 'boolean');
  702. $comments->getValidator()->add('starred', 'valid', ['rule' => 'boolean']);
  703. $row = new Article([
  704. 'title' => 'My title',
  705. 'comments' => [
  706. new Entity(['comment' => 'First comment']),
  707. ]
  708. ]);
  709. $context = new EntityContext($this->request, [
  710. 'entity' => $row,
  711. 'table' => 'Articles',
  712. 'validator' => 'default',
  713. ]);
  714. $this->assertFalse($context->isRequired('comments.0.starred'));
  715. }
  716. /**
  717. * Test isRequired on associated entities with custom validators.
  718. *
  719. * Ensures that missing associations use the correct entity class
  720. * so provider methods work correctly.
  721. *
  722. * @return void
  723. */
  724. public function testIsRequiredAssociatedCustomValidator()
  725. {
  726. $this->_setupTables();
  727. $users = $this->getTableLocator()->get('Users');
  728. $articles = $this->getTableLocator()->get('Articles');
  729. $validator = $articles->getValidator();
  730. $validator->notEmpty('title', 'nope', function ($context) {
  731. return $context['providers']['entity']->isRequired();
  732. });
  733. $articles->setValidator('default', $validator);
  734. $row = new Entity([
  735. 'username' => 'mark'
  736. ]);
  737. $context = new EntityContext($this->request, [
  738. 'entity' => $row,
  739. 'table' => 'Users',
  740. 'validator' => 'default',
  741. ]);
  742. $this->assertTrue($context->isRequired('articles.0.title'));
  743. }
  744. /**
  745. * Test isRequired on associated entities.
  746. *
  747. * @return void
  748. */
  749. public function testIsRequiredAssociatedHasManyMissingObject()
  750. {
  751. $this->_setupTables();
  752. $comments = $this->getTableLocator()->get('Comments');
  753. $validator = $comments->getValidator();
  754. $validator->allowEmpty('comment', function ($context) {
  755. return $context['providers']['entity']->isNew();
  756. });
  757. $row = new Article([
  758. 'title' => 'My title',
  759. 'comments' => [
  760. new Entity(['comment' => 'First comment'], ['markNew' => false]),
  761. ]
  762. ]);
  763. $context = new EntityContext($this->request, [
  764. 'entity' => $row,
  765. 'table' => 'Articles',
  766. 'validator' => 'default',
  767. ]);
  768. $this->assertTrue(
  769. $context->isRequired('comments.0.comment'),
  770. 'comment is required as object is not new'
  771. );
  772. $this->assertFalse(
  773. $context->isRequired('comments.1.comment'),
  774. 'comment is not required as missing object is "new"'
  775. );
  776. }
  777. /**
  778. * Test isRequired on associated entities with custom validators.
  779. *
  780. * @return void
  781. */
  782. public function testIsRequiredAssociatedValidator()
  783. {
  784. $this->_setupTables();
  785. $row = new Article([
  786. 'title' => 'My title',
  787. 'comments' => [
  788. new Entity(['comment' => 'First comment']),
  789. new Entity(['comment' => 'Second comment']),
  790. ]
  791. ]);
  792. $context = new EntityContext($this->request, [
  793. 'entity' => $row,
  794. 'table' => 'Articles',
  795. 'validator' => [
  796. 'Articles' => 'create',
  797. 'Comments' => 'custom'
  798. ]
  799. ]);
  800. $this->assertTrue($context->isRequired('title'));
  801. $this->assertFalse($context->isRequired('body'));
  802. $this->assertTrue($context->isRequired('comments.0.comment'));
  803. $this->assertTrue($context->isRequired('comments.1.comment'));
  804. }
  805. /**
  806. * Test isRequired on associated entities.
  807. *
  808. * @return void
  809. */
  810. public function testIsRequiredAssociatedBelongsTo()
  811. {
  812. $this->_setupTables();
  813. $row = new Article([
  814. 'title' => 'My title',
  815. 'user' => new Entity(['username' => 'Mark']),
  816. ]);
  817. $context = new EntityContext($this->request, [
  818. 'entity' => $row,
  819. 'table' => 'Articles',
  820. 'validator' => [
  821. 'Articles' => 'create',
  822. 'Users' => 'custom'
  823. ]
  824. ]);
  825. $this->assertTrue($context->isRequired('user.username'));
  826. $this->assertFalse($context->isRequired('user.first_name'));
  827. }
  828. /**
  829. * Test isRequired on associated join table entities.
  830. *
  831. * @return void
  832. */
  833. public function testIsRequiredAssociatedJoinTable()
  834. {
  835. $this->_setupTables();
  836. $row = new Article([
  837. 'tags' => [
  838. new Tag([
  839. '_joinData' => new ArticlesTag([
  840. 'article_id' => 1,
  841. 'tag_id' => 2
  842. ])
  843. ])
  844. ],
  845. ]);
  846. $context = new EntityContext($this->request, [
  847. 'entity' => $row,
  848. 'table' => 'Articles',
  849. ]);
  850. $this->assertTrue($context->isRequired('tags.0._joinData.article_id'));
  851. $this->assertTrue($context->isRequired('tags.0._joinData.tag_id'));
  852. }
  853. /**
  854. * Test type() basic
  855. *
  856. * @return void
  857. */
  858. public function testType()
  859. {
  860. $this->_setupTables();
  861. $row = new Article([
  862. 'title' => 'My title',
  863. 'body' => 'Some content',
  864. ]);
  865. $context = new EntityContext($this->request, [
  866. 'entity' => $row,
  867. 'table' => 'Articles',
  868. ]);
  869. $this->assertEquals('string', $context->type('title'));
  870. $this->assertEquals('text', $context->type('body'));
  871. $this->assertEquals('integer', $context->type('user_id'));
  872. $this->assertNull($context->type('nope'));
  873. }
  874. /**
  875. * Test getting types for associated records.
  876. *
  877. * @return void
  878. */
  879. public function testTypeAssociated()
  880. {
  881. $this->_setupTables();
  882. $row = new Article([
  883. 'title' => 'My title',
  884. 'user' => new Entity(['username' => 'Mark']),
  885. ]);
  886. $context = new EntityContext($this->request, [
  887. 'entity' => $row,
  888. 'table' => 'Articles',
  889. ]);
  890. $this->assertEquals('string', $context->type('user.username'));
  891. $this->assertEquals('text', $context->type('user.bio'));
  892. $this->assertNull($context->type('user.nope'));
  893. }
  894. /**
  895. * Test getting types for associated join data records.
  896. *
  897. * @return void
  898. */
  899. public function testTypeAssociatedJoinData()
  900. {
  901. $this->_setupTables();
  902. $row = new Article([
  903. 'tags' => [
  904. new Tag([
  905. '_joinData' => new ArticlesTag([
  906. 'article_id' => 1,
  907. 'tag_id' => 2
  908. ])
  909. ])
  910. ],
  911. ]);
  912. $context = new EntityContext($this->request, [
  913. 'entity' => $row,
  914. 'table' => 'Articles',
  915. ]);
  916. $this->assertEquals('integer', $context->type('tags.0._joinData.article_id'));
  917. $this->assertNull($context->type('tags.0._joinData.non_existent'));
  918. // tests the fallback behavior
  919. $this->assertEquals('integer', $context->type('tags.0._joinData._joinData.article_id'));
  920. $this->assertEquals('integer', $context->type('tags.0._joinData.non_existent.article_id'));
  921. $this->assertNull($context->type('tags.0._joinData._joinData.non_existent'));
  922. $this->assertNull($context->type('tags.0._joinData.non_existent'));
  923. }
  924. /**
  925. * Test attributes for fields.
  926. *
  927. * @return void
  928. */
  929. public function testAttributes()
  930. {
  931. $this->_setupTables();
  932. $row = new Article([
  933. 'title' => 'My title',
  934. 'user' => new Entity(['username' => 'Mark']),
  935. 'tags' => [
  936. new Tag([
  937. '_joinData' => new ArticlesTag([
  938. 'article_id' => 1,
  939. 'tag_id' => 2
  940. ])
  941. ])
  942. ],
  943. ]);
  944. $context = new EntityContext($this->request, [
  945. 'entity' => $row,
  946. 'table' => 'Articles',
  947. ]);
  948. $expected = [
  949. 'length' => 255, 'precision' => null
  950. ];
  951. $this->assertEquals($expected, $context->attributes('title'));
  952. $expected = [
  953. 'length' => null, 'precision' => null
  954. ];
  955. $this->assertEquals($expected, $context->attributes('body'));
  956. $expected = [
  957. 'length' => 10, 'precision' => 3
  958. ];
  959. $this->assertEquals($expected, $context->attributes('user.rating'));
  960. $expected = [
  961. 'length' => 11, 'precision' => null
  962. ];
  963. $this->assertEquals($expected, $context->attributes('tags.0._joinData.article_id'));
  964. }
  965. /**
  966. * Test hasError
  967. *
  968. * @return void
  969. */
  970. public function testHasError()
  971. {
  972. $this->_setupTables();
  973. $row = new Article([
  974. 'title' => 'My title',
  975. 'user' => new Entity(['username' => 'Mark']),
  976. ]);
  977. $row->setError('title', []);
  978. $row->setError('body', 'Gotta have one');
  979. $row->setError('user_id', ['Required field']);
  980. $context = new EntityContext($this->request, [
  981. 'entity' => $row,
  982. 'table' => 'Articles',
  983. ]);
  984. $this->assertFalse($context->hasError('title'));
  985. $this->assertFalse($context->hasError('nope'));
  986. $this->assertTrue($context->hasError('body'));
  987. $this->assertTrue($context->hasError('user_id'));
  988. }
  989. /**
  990. * Test hasError on associated records
  991. *
  992. * @return void
  993. */
  994. public function testHasErrorAssociated()
  995. {
  996. $this->_setupTables();
  997. $row = new Article([
  998. 'title' => 'My title',
  999. 'user' => new Entity(['username' => 'Mark']),
  1000. ]);
  1001. $row->setError('title', []);
  1002. $row->setError('body', 'Gotta have one');
  1003. $row->user->setError('username', ['Required']);
  1004. $context = new EntityContext($this->request, [
  1005. 'entity' => $row,
  1006. 'table' => 'Articles',
  1007. ]);
  1008. $this->assertTrue($context->hasError('user.username'));
  1009. $this->assertFalse($context->hasError('user.nope'));
  1010. $this->assertFalse($context->hasError('no.nope'));
  1011. }
  1012. /**
  1013. * Test error
  1014. *
  1015. * @return void
  1016. */
  1017. public function testError()
  1018. {
  1019. $this->_setupTables();
  1020. $row = new Article([
  1021. 'title' => 'My title',
  1022. 'user' => new Entity(['username' => 'Mark']),
  1023. ]);
  1024. $row->setError('title', []);
  1025. $row->setError('body', 'Gotta have one');
  1026. $row->setError('user_id', ['Required field']);
  1027. $row->user->setError('username', ['Required']);
  1028. $context = new EntityContext($this->request, [
  1029. 'entity' => $row,
  1030. 'table' => 'Articles',
  1031. ]);
  1032. $this->assertEquals([], $context->error('title'));
  1033. $expected = ['Gotta have one'];
  1034. $this->assertEquals($expected, $context->error('body'));
  1035. $expected = ['Required'];
  1036. $this->assertEquals($expected, $context->error('user.username'));
  1037. }
  1038. /**
  1039. * Test error on associated entities.
  1040. *
  1041. * @return void
  1042. */
  1043. public function testErrorAssociatedHasMany()
  1044. {
  1045. $this->_setupTables();
  1046. $comments = $this->getTableLocator()->get('Comments');
  1047. $row = new Article([
  1048. 'title' => 'My title',
  1049. 'comments' => [
  1050. new Entity(['comment' => '']),
  1051. new Entity(['comment' => 'Second comment']),
  1052. ]
  1053. ]);
  1054. $row->comments[0]->setError('comment', ['Is required']);
  1055. $row->comments[0]->setError('article_id', ['Is required']);
  1056. $context = new EntityContext($this->request, [
  1057. 'entity' => $row,
  1058. 'table' => 'Articles',
  1059. 'validator' => 'default',
  1060. ]);
  1061. $this->assertEquals([], $context->error('title'));
  1062. $this->assertEquals([], $context->error('comments.0.user_id'));
  1063. $this->assertEquals([], $context->error('comments.0'));
  1064. $this->assertEquals(['Is required'], $context->error('comments.0.comment'));
  1065. $this->assertEquals(['Is required'], $context->error('comments.0.article_id'));
  1066. $this->assertEquals([], $context->error('comments.1'));
  1067. $this->assertEquals([], $context->error('comments.1.comment'));
  1068. $this->assertEquals([], $context->error('comments.1.article_id'));
  1069. }
  1070. /**
  1071. * Test error on associated join table entities.
  1072. *
  1073. * @return void
  1074. */
  1075. public function testErrorAssociatedJoinTable()
  1076. {
  1077. $this->_setupTables();
  1078. $row = new Article([
  1079. 'tags' => [
  1080. new Tag([
  1081. '_joinData' => new ArticlesTag([
  1082. 'article_id' => 1
  1083. ])
  1084. ])
  1085. ],
  1086. ]);
  1087. $row->tags[0]->_joinData->setError('tag_id', ['Is required']);
  1088. $context = new EntityContext($this->request, [
  1089. 'entity' => $row,
  1090. 'table' => 'Articles',
  1091. ]);
  1092. $this->assertEquals([], $context->error('tags.0._joinData.article_id'));
  1093. $this->assertEquals(['Is required'], $context->error('tags.0._joinData.tag_id'));
  1094. }
  1095. /**
  1096. * Setup tables for tests.
  1097. *
  1098. * @return void
  1099. */
  1100. protected function _setupTables()
  1101. {
  1102. $articles = $this->getTableLocator()->get('Articles');
  1103. $articles->belongsTo('Users');
  1104. $articles->belongsToMany('Tags');
  1105. $articles->hasMany('Comments');
  1106. $articles->setEntityClass(__NAMESPACE__ . '\Article');
  1107. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  1108. $comments = $this->getTableLocator()->get('Comments');
  1109. $users = $this->getTableLocator()->get('Users');
  1110. $users->hasMany('Articles');
  1111. $articles->setSchema([
  1112. 'id' => ['type' => 'integer', 'length' => 11, 'null' => false],
  1113. 'title' => ['type' => 'string', 'length' => 255],
  1114. 'user_id' => ['type' => 'integer', 'length' => 11, 'null' => false],
  1115. 'body' => ['type' => 'crazy_text', 'baseType' => 'text'],
  1116. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
  1117. ]);
  1118. $articlesTags->setSchema([
  1119. 'article_id' => ['type' => 'integer', 'length' => 11, 'null' => false],
  1120. 'tag_id' => ['type' => 'integer', 'length' => 11, 'null' => false],
  1121. '_constraints' => ['unique_tag' => ['type' => 'primary', 'columns' => ['article_id', 'tag_id']]]
  1122. ]);
  1123. $users->setSchema([
  1124. 'id' => ['type' => 'integer', 'length' => 11],
  1125. 'username' => ['type' => 'string', 'length' => 255],
  1126. 'bio' => ['type' => 'text'],
  1127. 'rating' => ['type' => 'decimal', 'length' => 10, 'precision' => 3],
  1128. ]);
  1129. $validator = new Validator();
  1130. $validator->add('title', 'minlength', [
  1131. 'rule' => ['minlength', 10]
  1132. ])
  1133. ->add('body', 'maxlength', [
  1134. 'rule' => ['maxlength', 1000]
  1135. ])->allowEmpty('body');
  1136. $articles->setValidator('create', $validator);
  1137. $validator = new Validator();
  1138. $validator->add('username', 'length', [
  1139. 'rule' => ['minlength', 10]
  1140. ]);
  1141. $users->setValidator('custom', $validator);
  1142. $validator = new Validator();
  1143. $validator->add('comment', 'length', [
  1144. 'rule' => ['minlength', 10]
  1145. ]);
  1146. $comments->setValidator('custom', $validator);
  1147. $validator = new Validator();
  1148. $validator->requirePresence('article_id', 'create');
  1149. $validator->requirePresence('tag_id', 'create');
  1150. $articlesTags->setValidator('default', $validator);
  1151. }
  1152. /**
  1153. * Test the fieldnames method.
  1154. *
  1155. * @return void
  1156. */
  1157. public function testFieldNames()
  1158. {
  1159. $context = new EntityContext($this->request, [
  1160. 'entity' => new Entity(),
  1161. 'table' => 'Articles',
  1162. ]);
  1163. $articles = $this->getTableLocator()->get('Articles');
  1164. $this->assertEquals($articles->getSchema()->columns(), $context->fieldNames());
  1165. }
  1166. /**
  1167. * Test automatic entity provider setting
  1168. *
  1169. * @return void
  1170. */
  1171. public function testValidatorEntityProvider()
  1172. {
  1173. $row = new Article([
  1174. 'title' => 'Test entity',
  1175. 'body' => 'Something new'
  1176. ]);
  1177. $context = new EntityContext($this->request, [
  1178. 'entity' => $row,
  1179. 'table' => 'Articles',
  1180. ]);
  1181. $context->isRequired('title');
  1182. $articles = $this->getTableLocator()->get('Articles');
  1183. $this->assertSame($row, $articles->getValidator()->getProvider('entity'));
  1184. $row = new Article([
  1185. 'title' => 'First post',
  1186. 'user' => new Entity([
  1187. 'username' => 'mark',
  1188. 'fname' => 'Mark',
  1189. 'articles' => [
  1190. new Article(['title' => 'First post']),
  1191. new Article(['title' => 'Second post']),
  1192. ]
  1193. ]),
  1194. ]);
  1195. $context = new EntityContext($this->request, [
  1196. 'entity' => $row,
  1197. 'table' => 'Articles',
  1198. ]);
  1199. $validator = $articles->getValidator();
  1200. $context->isRequired('user.articles.0.title');
  1201. $this->assertSame($row->user->articles[0], $validator->getProvider('entity'));
  1202. $context->isRequired('user.articles.1.title');
  1203. $this->assertSame($row->user->articles[1], $validator->getProvider('entity'));
  1204. $context->isRequired('title');
  1205. $this->assertSame($row, $validator->getProvider('entity'));
  1206. }
  1207. }