EntityContextTest.php 44 KB

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