EntityTest.php 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721
  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\ORM;
  17. use Cake\Datasource\Exception\MissingPropertyException;
  18. use Cake\ORM\Entity;
  19. use Cake\TestSuite\TestCase;
  20. use Exception;
  21. use InvalidArgumentException;
  22. use stdClass;
  23. use TestApp\Model\Entity\Extending;
  24. use TestApp\Model\Entity\NonExtending;
  25. use TestApp\Model\Entity\VirtualUser;
  26. /**
  27. * Entity test case.
  28. */
  29. class EntityTest extends TestCase
  30. {
  31. /**
  32. * Tests setting a single property in an entity without custom setters
  33. */
  34. public function testSetOneParamNoSetters(): void
  35. {
  36. $entity = new Entity();
  37. $this->assertNull($entity->getOriginal('foo'));
  38. $entity->set('foo', 'bar', ['asOriginal' => true]);
  39. $this->assertSame('bar', $entity->foo);
  40. $this->assertSame('bar', $entity->getOriginal('foo'));
  41. $entity->set('foo', 'baz');
  42. $this->assertSame('baz', $entity->foo);
  43. $this->assertSame('bar', $entity->getOriginal('foo'));
  44. $entity->set('id', 1, ['asOriginal' => true]);
  45. $this->assertSame(1, $entity->id);
  46. $this->assertSame(1, $entity->getOriginal('id'));
  47. $this->assertSame('bar', $entity->getOriginal('foo'));
  48. }
  49. /**
  50. * Tests setting multiple properties without custom setters
  51. */
  52. public function testSetMultiplePropertiesNoSetters(): void
  53. {
  54. $entity = new Entity();
  55. $entity->setAccess('*', true);
  56. $entity->set(['foo' => 'bar', 'id' => 1], ['asOriginal' => true]);
  57. $this->assertSame('bar', $entity->foo);
  58. $this->assertSame(1, $entity->id);
  59. $entity->set(['foo' => 'baz', 'id' => 2, 'thing' => 3]);
  60. $this->assertSame('baz', $entity->foo);
  61. $this->assertSame(2, $entity->id);
  62. $this->assertSame(3, $entity->thing);
  63. $this->assertSame('bar', $entity->getOriginal('foo'));
  64. $this->assertSame(1, $entity->getOriginal('id'));
  65. $entity->set(['foo', 'bar']);
  66. $this->assertSame('foo', $entity->get('0'));
  67. $this->assertSame('bar', $entity->get('1'));
  68. $entity->set(['sample']);
  69. $this->assertSame('sample', $entity->get('0'));
  70. }
  71. /**
  72. * Test that getOriginal() retains falsey values.
  73. */
  74. public function testGetOriginal(): void
  75. {
  76. $entity = new Entity(
  77. ['false' => false, 'null' => null, 'zero' => 0, 'empty' => ''],
  78. ['markNew' => true]
  79. );
  80. $this->assertNull($entity->getOriginal('null'));
  81. $this->assertFalse($entity->getOriginal('false'));
  82. $this->assertSame(0, $entity->getOriginal('zero'));
  83. $this->assertSame('', $entity->getOriginal('empty'));
  84. $entity->set(['false' => 'y', 'null' => 'y', 'zero' => 'y', 'empty' => '']);
  85. $this->assertNull($entity->getOriginal('null'));
  86. $this->assertFalse($entity->getOriginal('false'));
  87. $this->assertSame(0, $entity->getOriginal('zero'));
  88. $this->assertSame('', $entity->getOriginal('empty'));
  89. }
  90. /**
  91. * Test that getOriginal throws an exception for fields without original value
  92. * when called with second parameter "false"
  93. */
  94. public function testGetOriginalFallback(): void
  95. {
  96. $entity = new Entity(
  97. ['foo' => 'foo', 'bar' => 'bar'],
  98. ['markNew' => true]
  99. );
  100. $this->assertNull($entity->getOriginal('baz', true));
  101. $this->expectException(InvalidArgumentException::class);
  102. $this->expectExceptionMessage('Cannot retrieve original value for field `baz`');
  103. $entity->getOriginal('baz', false);
  104. }
  105. /**
  106. * Test extractOriginal()
  107. */
  108. public function testExtractOriginal(): void
  109. {
  110. $entity = new Entity([
  111. 'id' => 1,
  112. 'title' => 'original',
  113. 'body' => 'no',
  114. 'null' => null,
  115. ], ['markNew' => true]);
  116. $entity->set('body', 'updated body');
  117. $result = $entity->extractOriginal(['id', 'title', 'body', 'null']);
  118. $expected = [
  119. 'id' => 1,
  120. 'title' => 'original',
  121. 'body' => 'no',
  122. 'null' => null,
  123. ];
  124. $this->assertEquals($expected, $result);
  125. $result = $entity->extractOriginalChanged(['id', 'title', 'body', 'null']);
  126. $expected = [
  127. 'body' => 'no',
  128. ];
  129. $this->assertEquals($expected, $result);
  130. $entity->set('null', 'not null');
  131. $result = $entity->extractOriginalChanged(['id', 'title', 'body', 'null']);
  132. $expected = [
  133. 'null' => null,
  134. 'body' => 'no',
  135. ];
  136. $this->assertEquals($expected, $result);
  137. }
  138. /**
  139. * Test that all original values are returned properly
  140. */
  141. public function testExtractOriginalValues(): void
  142. {
  143. $entity = new Entity([
  144. 'id' => 1,
  145. 'title' => 'original',
  146. 'body' => 'no',
  147. 'null' => null,
  148. ], ['markNew' => true]);
  149. $entity->set('body', 'updated body');
  150. $result = $entity->getOriginalValues();
  151. $expected = [
  152. 'id' => 1,
  153. 'title' => 'original',
  154. 'body' => 'no',
  155. 'null' => null,
  156. ];
  157. $this->assertEquals($expected, $result);
  158. }
  159. /**
  160. * Tests setting a single property using a setter function
  161. */
  162. public function testSetOneParamWithSetter(): void
  163. {
  164. $entity = new class extends Entity {
  165. protected function _setName(?string $name): string
  166. {
  167. return 'Dr. ' . $name;
  168. }
  169. };
  170. $entity->set('name', 'Jones');
  171. $this->assertSame('Dr. Jones', $entity->name);
  172. }
  173. /**
  174. * Tests setting multiple properties using a setter function
  175. */
  176. public function testMultipleWithSetter(): void
  177. {
  178. $entity = new class extends Entity {
  179. protected function _setName(?string $name): string
  180. {
  181. return 'Dr. ' . $name;
  182. }
  183. protected function _setStuff(?array $stuff): array
  184. {
  185. return ['c', 'd'];
  186. }
  187. };
  188. $entity->setAccess('*', true);
  189. $entity->set(['name' => 'Jones', 'stuff' => ['a', 'b']]);
  190. $this->assertSame('Dr. Jones', $entity->name);
  191. $this->assertEquals(['c', 'd'], $entity->stuff);
  192. }
  193. /**
  194. * Tests that it is possible to bypass the setters
  195. */
  196. public function testBypassSetters(): void
  197. {
  198. $entity = new class extends Entity {
  199. protected function _setName(?string $name): string
  200. {
  201. throw new Exception('_setName should not have been called');
  202. }
  203. protected function _setStuff(?array $stuff): array
  204. {
  205. throw new Exception('_setStuff should not have been called');
  206. }
  207. };
  208. $entity->setAccess('*', true);
  209. $entity->set('name', 'Jones', ['setter' => false]);
  210. $this->assertSame('Jones', $entity->name);
  211. $entity->set('stuff', 'Thing', ['setter' => false]);
  212. $this->assertSame('Thing', $entity->stuff);
  213. $entity->set(['name' => 'foo', 'stuff' => 'bar'], ['setter' => false]);
  214. $this->assertSame('bar', $entity->stuff);
  215. }
  216. /**
  217. * Tests that the constructor will set initial properties
  218. */
  219. public function testConstructor(): void
  220. {
  221. $entity = $this->getMockBuilder(Entity::class)
  222. ->onlyMethods(['set'])
  223. ->disableOriginalConstructor()
  224. ->getMock();
  225. $entity->expects($this->exactly(2))
  226. ->method('set')
  227. ->with(
  228. ...self::withConsecutive(
  229. [
  230. ['a' => 'b', 'c' => 'd'], ['setter' => true, 'guard' => false],
  231. ],
  232. [['foo' => 'bar'], ['setter' => false, 'guard' => false]]
  233. )
  234. );
  235. $entity->__construct(['a' => 'b', 'c' => 'd']);
  236. $entity->__construct(['foo' => 'bar'], ['useSetters' => false]);
  237. }
  238. /**
  239. * Tests that the constructor will set initial properties and pass the guard
  240. * option along
  241. */
  242. public function testConstructorWithGuard(): void
  243. {
  244. $entity = $this->getMockBuilder(Entity::class)
  245. ->onlyMethods(['set'])
  246. ->disableOriginalConstructor()
  247. ->getMock();
  248. $entity->expects($this->once())
  249. ->method('set')
  250. ->with(['foo' => 'bar'], ['setter' => true, 'guard' => true]);
  251. $entity->__construct(['foo' => 'bar'], ['guard' => true]);
  252. }
  253. /**
  254. * Tests getting properties with no custom getters
  255. */
  256. public function testGetNoGetters(): void
  257. {
  258. $entity = new Entity(['id' => 1, 'foo' => 'bar']);
  259. $this->assertSame(1, $entity->get('id'));
  260. $this->assertSame('bar', $entity->get('foo'));
  261. }
  262. public function testRequirePresenceException(): void
  263. {
  264. $this->expectException(MissingPropertyException::class);
  265. $this->expectExceptionMessage('Property `not_present` does not exist for the entity `Cake\ORM\Entity`');
  266. $entity = new Entity();
  267. $entity->requireFieldPresence();
  268. $entity->get('not_present');
  269. }
  270. public function testRequirePresenceNoException(): void
  271. {
  272. $entity = new Entity(['is_present' => null]);
  273. $entity->requireFieldPresence();
  274. $this->assertNull($entity->get('is_present'));
  275. $entity = new VirtualUser();
  276. $entity->requireFieldPresence();
  277. $this->assertSame('bonus', $entity->get('bonus'));
  278. }
  279. /**
  280. * Tests get with custom getter
  281. */
  282. public function testGetCustomGetters(): void
  283. {
  284. $entity = new class extends Entity {
  285. protected function _getName(string $name): string
  286. {
  287. return 'Dr. ' . $name;
  288. }
  289. };
  290. $entity->set('name', 'Jones');
  291. $this->assertSame('Dr. Jones', $entity->get('name'));
  292. $this->assertSame('Dr. Jones', $entity->get('name'));
  293. }
  294. /**
  295. * Tests get with custom getter
  296. */
  297. public function testGetCustomGettersAfterSet(): void
  298. {
  299. $entity = new class extends Entity {
  300. protected function _getName(string $name): string
  301. {
  302. return 'Dr. ' . $name;
  303. }
  304. };
  305. $entity->set('name', 'Jones');
  306. $this->assertSame('Dr. Jones', $entity->get('name'));
  307. $this->assertSame('Dr. Jones', $entity->get('name'));
  308. $entity->set('name', 'Mark');
  309. $this->assertSame('Dr. Mark', $entity->get('name'));
  310. $this->assertSame('Dr. Mark', $entity->get('name'));
  311. }
  312. /**
  313. * Tests that the get cache is cleared by unset.
  314. */
  315. public function testGetCacheClearedByUnset(): void
  316. {
  317. $entity = new class extends Entity {
  318. protected function _getName(?string $name): string
  319. {
  320. return 'Dr. ' . $name;
  321. }
  322. };
  323. $entity->set('name', 'Jones');
  324. $this->assertSame('Dr. Jones', $entity->get('name'));
  325. $entity->unset('name');
  326. $this->assertSame('Dr. ', $entity->get('name'));
  327. }
  328. /**
  329. * Test getting camelcased virtual fields.
  330. */
  331. public function testGetCamelCasedProperties(): void
  332. {
  333. $entity = new class extends Entity {
  334. protected function _getListIdName(): string
  335. {
  336. return 'A name';
  337. }
  338. };
  339. $entity->setVirtual(['ListIdName']);
  340. $this->assertSame('A name', $entity->list_id_name, 'underscored virtual field should be accessible');
  341. $this->assertSame('A name', $entity->listIdName, 'Camelbacked virtual field should be accessible');
  342. }
  343. /**
  344. * Test magic property setting with no custom setter
  345. */
  346. public function testMagicSet(): void
  347. {
  348. $entity = new Entity();
  349. $entity->name = 'Jones';
  350. $this->assertSame('Jones', $entity->name);
  351. $entity->name = 'George';
  352. $this->assertSame('George', $entity->name);
  353. }
  354. /**
  355. * Tests magic set with custom setter function
  356. */
  357. public function testMagicSetWithSetter(): void
  358. {
  359. $entity = new class extends Entity {
  360. protected function _setName(?string $name): string
  361. {
  362. return 'Dr. ' . $name;
  363. }
  364. };
  365. $entity->name = 'Jones';
  366. $this->assertSame('Dr. Jones', $entity->name);
  367. }
  368. /**
  369. * Tests magic set with custom setter function using a Title cased property
  370. */
  371. public function testMagicSetWithSetterTitleCase(): void
  372. {
  373. $entity = new class extends Entity {
  374. protected function _setName(?string $name): string
  375. {
  376. return 'Dr. ' . $name;
  377. }
  378. };
  379. $entity->Name = 'Jones';
  380. $this->assertSame('Dr. Jones', $entity->Name);
  381. }
  382. /**
  383. * Tests the magic getter with a custom getter function
  384. */
  385. public function testMagicGetWithGetter(): void
  386. {
  387. $entity = new class extends Entity {
  388. protected function _getName(string $name): string
  389. {
  390. return 'Dr. ' . $name;
  391. }
  392. };
  393. $entity->set('name', 'Jones');
  394. $this->assertSame('Dr. Jones', $entity->name);
  395. }
  396. /**
  397. * Tests magic get with custom getter function using a Title cased property
  398. */
  399. public function testMagicGetWithGetterTitleCase(): void
  400. {
  401. $entity = new class extends Entity {
  402. protected function _getName(string $name): string
  403. {
  404. return 'Dr. ' . $name;
  405. }
  406. };
  407. $entity->set('Name', 'Jones');
  408. $this->assertSame('Dr. Jones', $entity->Name);
  409. }
  410. /**
  411. * Test indirectly modifying internal properties
  412. */
  413. public function testIndirectModification(): void
  414. {
  415. $entity = new Entity();
  416. $entity->things = ['a', 'b'];
  417. $entity->things[] = 'c';
  418. $this->assertEquals(['a', 'b', 'c'], $entity->things);
  419. }
  420. /**
  421. * Tests has() method
  422. */
  423. public function testHas(): void
  424. {
  425. $entity = new Entity(['id' => 1, 'name' => 'Juan', 'foo' => null]);
  426. $this->assertTrue($entity->has('id'));
  427. $this->assertTrue($entity->has('name'));
  428. $this->assertTrue($entity->has('foo'));
  429. $this->assertFalse($entity->has('last_name'));
  430. $this->assertTrue($entity->has(['id']));
  431. $this->assertTrue($entity->has(['id', 'name']));
  432. $this->assertTrue($entity->has(['id', 'foo']));
  433. $this->assertFalse($entity->has(['id', 'nope']));
  434. $entity = new class extends Entity {
  435. protected function _getThings()
  436. {
  437. throw new Exception('_getThings() should not have been called');
  438. }
  439. };
  440. $this->assertTrue($entity->has('things'));
  441. }
  442. /**
  443. * Tests unset one property at a time
  444. */
  445. public function testUnset(): void
  446. {
  447. $entity = new Entity(['id' => 1, 'name' => 'bar']);
  448. $entity->unset('id');
  449. $this->assertFalse($entity->has('id'));
  450. $this->assertTrue($entity->has('name'));
  451. $entity->unset('name');
  452. $this->assertFalse($entity->has('id'));
  453. }
  454. /**
  455. * Unsetting a property should not mark it as dirty.
  456. */
  457. public function testUnsetMakesClean(): void
  458. {
  459. $entity = new Entity(['id' => 1, 'name' => 'bar']);
  460. $this->assertTrue($entity->isDirty('name'));
  461. $entity->unset('name');
  462. $this->assertFalse($entity->isDirty('name'), 'Removed properties are not dirty.');
  463. }
  464. /**
  465. * Tests unset with multiple properties
  466. */
  467. public function testUnsetMultiple(): void
  468. {
  469. $entity = new Entity(['id' => 1, 'name' => 'bar', 'thing' => 2]);
  470. $entity->unset(['id', 'thing']);
  471. $this->assertFalse($entity->has('id'));
  472. $this->assertTrue($entity->has('name'));
  473. $this->assertFalse($entity->has('thing'));
  474. }
  475. /**
  476. * Tests the magic __isset() method
  477. */
  478. public function testMagicIsset(): void
  479. {
  480. $entity = new Entity(['id' => 1, 'name' => 'Juan', 'foo' => null]);
  481. $this->assertTrue(isset($entity->id));
  482. $this->assertTrue(isset($entity->name));
  483. $this->assertFalse(isset($entity->foo));
  484. $this->assertFalse(isset($entity->thing));
  485. }
  486. /**
  487. * Tests the magic __unset() method
  488. */
  489. public function testMagicUnset(): void
  490. {
  491. $entity = $this->getMockBuilder(Entity::class)
  492. ->onlyMethods(['unset'])
  493. ->getMock();
  494. $entity->expects($this->once())
  495. ->method('unset')
  496. ->with('foo');
  497. unset($entity->foo);
  498. }
  499. /**
  500. * Tests isset with array access
  501. */
  502. public function testIssetArrayAccess(): void
  503. {
  504. $entity = new Entity(['id' => 1, 'name' => 'Juan', 'foo' => null]);
  505. $this->assertArrayHasKey('id', $entity);
  506. $this->assertArrayHasKey('name', $entity);
  507. $this->assertArrayNotHasKey('foo', $entity);
  508. $this->assertArrayNotHasKey('thing', $entity);
  509. }
  510. /**
  511. * Tests get property with array access
  512. */
  513. public function testGetArrayAccess(): void
  514. {
  515. $entity = $this->getMockBuilder(Entity::class)
  516. ->onlyMethods(['get'])
  517. ->getMock();
  518. $entity->expects($this->exactly(2))
  519. ->method('get')
  520. ->with(
  521. ...self::withConsecutive(['foo'], ['bar'])
  522. )
  523. ->willReturn('worked', 'worked too');
  524. $this->assertSame('worked', $entity['foo']);
  525. $this->assertSame('worked too', $entity['bar']);
  526. }
  527. /**
  528. * Tests set with array access
  529. */
  530. public function testSetArrayAccess(): void
  531. {
  532. $entity = $this->getMockBuilder(Entity::class)
  533. ->onlyMethods(['set'])
  534. ->getMock();
  535. $entity->setAccess('*', true);
  536. $entity->expects($this->exactly(2))
  537. ->method('set')
  538. ->with(
  539. ...self::withConsecutive(['foo', 1], ['bar', 2])
  540. )
  541. ->willReturnSelf();
  542. $entity['foo'] = 1;
  543. $entity['bar'] = 2;
  544. }
  545. /**
  546. * Tests unset with array access
  547. */
  548. public function testUnsetArrayAccess(): void
  549. {
  550. /** @var \Cake\ORM\Entity|\PHPUnit\Framework\MockObject\MockObject $entity */
  551. $entity = $this->getMockBuilder(Entity::class)
  552. ->onlyMethods(['unset'])
  553. ->getMock();
  554. $entity->expects($this->once())
  555. ->method('unset')
  556. ->with('foo');
  557. unset($entity['foo']);
  558. }
  559. /**
  560. * Tests that the method cache will only report the methods for the called class,
  561. * this is, calling methods defined in another entity will not cause a fatal error
  562. * when trying to call directly an inexistent method in another class
  563. */
  564. public function testMethodCache(): void
  565. {
  566. $entity = new class extends Entity {
  567. protected function _setFoo(?string $name): string
  568. {
  569. return 'Dr. ' . $name;
  570. }
  571. protected function _getBar(string $bar): string
  572. {
  573. return 'Dir. ' . $bar;
  574. }
  575. };
  576. $entity2 = new class extends Entity {
  577. protected function _setBar(?string $name): string
  578. {
  579. return 'DrDr. ' . $name;
  580. }
  581. };
  582. $entity = $entity->set('foo', 'Someone');
  583. $this->assertEquals('Dr. Someone', $entity->get('foo'));
  584. $entity2 = $entity2->set('bar', 'Someone');
  585. $this->assertEquals('DrDr. Someone', $entity2->get('bar'));
  586. }
  587. /**
  588. * Tests that long properties in the entity are inflected correctly
  589. */
  590. public function testSetGetLongPropertyNames(): void
  591. {
  592. $entity = new class extends Entity {
  593. protected function _setVeryLongProperty(?string $name): string
  594. {
  595. return 'Dr. ' . $name;
  596. }
  597. protected function _getVeryLongProperty(?string $veryLongProperty): string
  598. {
  599. return 'Dir. ' . $veryLongProperty;
  600. }
  601. };
  602. $this->assertEquals('Dir. ', $entity->get('very_long_property'));
  603. $entity->set('very_long_property', 'Someone');
  604. $this->assertEquals('Dir. Dr. Someone', $entity->get('very_long_property'));
  605. }
  606. /**
  607. * Tests serializing an entity as JSON
  608. */
  609. public function testJsonSerialize(): void
  610. {
  611. $data = ['name' => 'James', 'age' => 20, 'phones' => ['123', '457']];
  612. $entity = new Entity($data);
  613. $this->assertEquals(json_encode($data), json_encode($entity));
  614. }
  615. /**
  616. * Tests serializing an entity as PHP
  617. */
  618. public function testPhpSerialize(): void
  619. {
  620. $data = ['name' => 'James', 'age' => 20, 'phones' => ['123', '457']];
  621. $entity = new Entity($data);
  622. $copy = unserialize(serialize($entity));
  623. $this->assertInstanceOf(Entity::class, $copy);
  624. $this->assertEquals($data, $copy->toArray());
  625. }
  626. /**
  627. * Tests that jsonSerialize is called recursively for contained entities
  628. */
  629. public function testJsonSerializeRecursive(): void
  630. {
  631. $phone = $this->getMockBuilder(Entity::class)
  632. ->onlyMethods(['jsonSerialize'])
  633. ->getMock();
  634. $phone->expects($this->once())->method('jsonSerialize')->willReturn(['something']);
  635. $data = ['name' => 'James', 'age' => 20, 'phone' => $phone];
  636. $entity = new Entity($data);
  637. $expected = ['name' => 'James', 'age' => 20, 'phone' => ['something']];
  638. $this->assertEquals(json_encode($expected), json_encode($entity));
  639. }
  640. /**
  641. * Tests the extract method
  642. */
  643. public function testExtract(): void
  644. {
  645. $entity = new Entity([
  646. 'id' => 1,
  647. 'title' => 'Foo',
  648. 'author_id' => 3,
  649. ]);
  650. $expected = ['author_id' => 3, 'title' => 'Foo',];
  651. $this->assertEquals($expected, $entity->extract(['author_id', 'title']));
  652. $expected = ['id' => 1];
  653. $this->assertEquals($expected, $entity->extract(['id']));
  654. $expected = [];
  655. $this->assertEquals($expected, $entity->extract([]));
  656. $expected = ['id' => 1, 'craziness' => null];
  657. $this->assertEquals($expected, $entity->extract(['id', 'craziness']));
  658. }
  659. /**
  660. * Tests isDirty() method on a newly created object
  661. */
  662. public function testIsDirty(): void
  663. {
  664. $entity = new Entity([
  665. 'id' => 1,
  666. 'title' => 'Foo',
  667. 'author_id' => 3,
  668. ]);
  669. $this->assertTrue($entity->isDirty('id'));
  670. $this->assertTrue($entity->isDirty('title'));
  671. $this->assertTrue($entity->isDirty('author_id'));
  672. $this->assertTrue($entity->isDirty());
  673. $entity->setDirty('id', false);
  674. $this->assertFalse($entity->isDirty('id'));
  675. $this->assertTrue($entity->isDirty('title'));
  676. $entity->setDirty('title', false);
  677. $this->assertFalse($entity->isDirty('title'));
  678. $this->assertTrue($entity->isDirty(), 'should be dirty, one field left');
  679. $entity->setDirty('author_id', false);
  680. $this->assertFalse($entity->isDirty(), 'all fields are clean.');
  681. }
  682. /**
  683. * Test setDirty().
  684. */
  685. public function testSetDirty(): void
  686. {
  687. $entity = new Entity([
  688. 'id' => 1,
  689. 'title' => 'Foo',
  690. 'author_id' => 3,
  691. ], ['markClean' => true]);
  692. $this->assertFalse($entity->isDirty());
  693. $this->assertSame($entity, $entity->setDirty('title'));
  694. $this->assertSame($entity, $entity->setDirty('id', false));
  695. $entity->setErrors(['title' => ['badness']]);
  696. $entity->setDirty('title', true);
  697. $this->assertEmpty($entity->getErrors(), 'Making a field dirty clears errors.');
  698. }
  699. /**
  700. * Tests dirty() when altering properties values and adding new ones
  701. */
  702. public function testDirtyChangingProperties(): void
  703. {
  704. $entity = new Entity([
  705. 'title' => 'Foo',
  706. ]);
  707. $entity->setDirty('title', false);
  708. $this->assertFalse($entity->isDirty('title'));
  709. $entity->set('title', 'Foo');
  710. $this->assertTrue($entity->isDirty('title'));
  711. $entity->set('title', 'Foo');
  712. $this->assertTrue($entity->isDirty('title'));
  713. $entity->set('something', 'else');
  714. $this->assertTrue($entity->isDirty('something'));
  715. }
  716. /**
  717. * Tests extract only dirty properties
  718. */
  719. public function testExtractDirty(): void
  720. {
  721. $entity = new Entity([
  722. 'id' => 1,
  723. 'title' => 'Foo',
  724. 'author_id' => 3,
  725. ]);
  726. $entity->setDirty('id', false);
  727. $entity->setDirty('title', false);
  728. $expected = ['author_id' => 3];
  729. $result = $entity->extract(['id', 'title', 'author_id'], true);
  730. $this->assertEquals($expected, $result);
  731. }
  732. /**
  733. * Tests the getDirty method
  734. */
  735. public function testGetDirty(): void
  736. {
  737. $entity = new Entity([
  738. 'id' => 1,
  739. 'title' => 'Foo',
  740. 'author_id' => 3,
  741. ]);
  742. $expected = [
  743. 'id',
  744. 'title',
  745. 'author_id',
  746. ];
  747. $this->assertSame($expected, $entity->getDirty());
  748. }
  749. /**
  750. * Tests the clean method
  751. */
  752. public function testClean(): void
  753. {
  754. $entity = new Entity([
  755. 'id' => 1,
  756. 'title' => 'Foo',
  757. 'author_id' => 3,
  758. ]);
  759. $this->assertTrue($entity->isDirty('id'));
  760. $this->assertTrue($entity->isDirty('title'));
  761. $this->assertTrue($entity->isDirty('author_id'));
  762. $entity->clean();
  763. $this->assertFalse($entity->isDirty('id'));
  764. $this->assertFalse($entity->isDirty('title'));
  765. $this->assertFalse($entity->isDirty('author_id'));
  766. }
  767. /**
  768. * Tests the isNew method
  769. */
  770. public function testIsNew(): void
  771. {
  772. $data = [
  773. 'id' => 1,
  774. 'title' => 'Foo',
  775. 'author_id' => 3,
  776. ];
  777. $entity = new Entity($data);
  778. $this->assertTrue($entity->isNew());
  779. $entity->setNew(true);
  780. $this->assertTrue($entity->isNew());
  781. $entity->setNew(false);
  782. $this->assertFalse($entity->isNew());
  783. }
  784. /**
  785. * Tests the constructor when passing the markClean option
  786. */
  787. public function testConstructorWithClean(): void
  788. {
  789. $entity = $this->getMockBuilder(Entity::class)
  790. ->onlyMethods(['clean'])
  791. ->disableOriginalConstructor()
  792. ->getMock();
  793. $entity->expects($this->never())->method('clean');
  794. $entity->__construct(['a' => 'b', 'c' => 'd']);
  795. $entity = $this->getMockBuilder(Entity::class)
  796. ->onlyMethods(['clean'])
  797. ->disableOriginalConstructor()
  798. ->getMock();
  799. $entity->expects($this->once())->method('clean');
  800. $entity->__construct(['a' => 'b', 'c' => 'd'], ['markClean' => true]);
  801. }
  802. /**
  803. * Tests the constructor when passing the markClean option
  804. */
  805. public function testConstructorWithMarkNew(): void
  806. {
  807. $entity = $this->getMockBuilder(Entity::class)
  808. ->onlyMethods(['setNew', 'clean'])
  809. ->disableOriginalConstructor()
  810. ->getMock();
  811. $entity->expects($this->never())->method('clean');
  812. $entity->__construct(['a' => 'b', 'c' => 'd']);
  813. $entity = $this->getMockBuilder(Entity::class)
  814. ->onlyMethods(['setNew'])
  815. ->disableOriginalConstructor()
  816. ->getMock();
  817. $entity->expects($this->once())->method('setNew');
  818. $entity->__construct(['a' => 'b', 'c' => 'd'], ['markNew' => true]);
  819. }
  820. /**
  821. * Test toArray method.
  822. */
  823. public function testToArray(): void
  824. {
  825. $data = ['name' => 'James', 'age' => 20, 'phones' => ['123', '457']];
  826. $entity = new Entity($data);
  827. $this->assertEquals($data, $entity->toArray());
  828. }
  829. /**
  830. * Test toArray recursive.
  831. */
  832. public function testToArrayRecursive(): void
  833. {
  834. $data = ['id' => 1, 'name' => 'James', 'age' => 20, 'phones' => ['123', '457']];
  835. $user = new Extending($data);
  836. $comments = [
  837. new NonExtending(['user_id' => 1, 'body' => 'Comment 1']),
  838. new NonExtending(['user_id' => 1, 'body' => 'Comment 2']),
  839. ];
  840. $user->comments = $comments;
  841. $user->profile = new Entity(['email' => 'mark@example.com']);
  842. $expected = [
  843. 'id' => 1,
  844. 'name' => 'James',
  845. 'age' => 20,
  846. 'phones' => ['123', '457'],
  847. 'profile' => ['email' => 'mark@example.com'],
  848. 'comments' => [
  849. ['user_id' => 1, 'body' => 'Comment 1'],
  850. ['user_id' => 1, 'body' => 'Comment 2'],
  851. ],
  852. ];
  853. $this->assertEquals($expected, $user->toArray());
  854. }
  855. /**
  856. * Tests that an entity with entities and other misc types can be properly toArray'd
  857. */
  858. public function testToArrayMixed(): void
  859. {
  860. $test = new Entity([
  861. 'id' => 1,
  862. 'foo' => [
  863. new Entity(['hi' => 'test']),
  864. 'notentity' => 1,
  865. ],
  866. ]);
  867. $expected = [
  868. 'id' => 1,
  869. 'foo' => [
  870. ['hi' => 'test'],
  871. 'notentity' => 1,
  872. ],
  873. ];
  874. $this->assertEquals($expected, $test->toArray());
  875. }
  876. /**
  877. * Test that get accessors are called when converting to arrays.
  878. */
  879. public function testToArrayWithAccessor(): void
  880. {
  881. $entity = new class extends Entity {
  882. protected function _getName(?string $name): string
  883. {
  884. return 'Jose';
  885. }
  886. };
  887. $entity->setAccess('*', true);
  888. $entity->set(['name' => 'Mark', 'email' => 'mark@example.com']);
  889. $expected = ['name' => 'Jose', 'email' => 'mark@example.com'];
  890. $this->assertEquals($expected, $entity->toArray());
  891. }
  892. /**
  893. * Test that toArray respects hidden properties.
  894. */
  895. public function testToArrayHiddenProperties(): void
  896. {
  897. $data = ['secret' => 'sauce', 'name' => 'mark', 'id' => 1];
  898. $entity = new Entity($data);
  899. $entity->setHidden(['secret']);
  900. $this->assertEquals(['name' => 'mark', 'id' => 1], $entity->toArray());
  901. }
  902. /**
  903. * Tests setting hidden properties.
  904. */
  905. public function testSetHidden(): void
  906. {
  907. $data = ['secret' => 'sauce', 'name' => 'mark', 'id' => 1];
  908. $entity = new Entity($data);
  909. $entity->setHidden(['secret']);
  910. $result = $entity->getHidden();
  911. $this->assertSame(['secret'], $result);
  912. $entity->setHidden(['name']);
  913. $result = $entity->getHidden();
  914. $this->assertSame(['name'], $result);
  915. }
  916. /**
  917. * Tests setting hidden properties with merging.
  918. */
  919. public function testSetHiddenWithMerge(): void
  920. {
  921. $data = ['secret' => 'sauce', 'name' => 'mark', 'id' => 1];
  922. $entity = new Entity($data);
  923. $entity->setHidden(['secret'], true);
  924. $result = $entity->getHidden();
  925. $this->assertSame(['secret'], $result);
  926. $entity->setHidden(['name'], true);
  927. $result = $entity->getHidden();
  928. $this->assertSame(['secret', 'name'], $result);
  929. $entity->setHidden(['name'], true);
  930. $result = $entity->getHidden();
  931. $this->assertSame(['secret', 'name'], $result);
  932. }
  933. /**
  934. * Test toArray includes 'virtual' properties.
  935. */
  936. public function testToArrayVirtualProperties(): void
  937. {
  938. $entity = new class extends Entity {
  939. protected function _getName(?string $name): string
  940. {
  941. return 'Jose';
  942. }
  943. };
  944. $entity->setAccess('*', true);
  945. $entity->set(['email' => 'mark@example.com']);
  946. $entity->setVirtual(['name']);
  947. $expected = ['name' => 'Jose', 'email' => 'mark@example.com'];
  948. $this->assertEquals($expected, $entity->toArray());
  949. $this->assertEquals(['name'], $entity->getVirtual());
  950. $entity->setHidden(['name']);
  951. $expected = ['email' => 'mark@example.com'];
  952. $this->assertEquals($expected, $entity->toArray());
  953. $this->assertEquals(['name'], $entity->getHidden());
  954. }
  955. /**
  956. * Tests the getVisible() method
  957. */
  958. public function testGetVisible(): void
  959. {
  960. $entity = new Entity();
  961. $entity->foo = 'foo';
  962. $entity->bar = 'bar';
  963. $expected = $entity->getVisible();
  964. $this->assertSame(['foo', 'bar'], $expected);
  965. }
  966. /**
  967. * Tests setting virtual properties with merging.
  968. */
  969. public function testSetVirtualWithMerge(): void
  970. {
  971. $data = ['virtual' => 'sauce', 'name' => 'mark', 'id' => 1];
  972. $entity = new Entity($data);
  973. $entity->setVirtual(['virtual']);
  974. $result = $entity->getVirtual();
  975. $this->assertSame(['virtual'], $result);
  976. $entity->setVirtual(['name'], true);
  977. $result = $entity->getVirtual();
  978. $this->assertSame(['virtual', 'name'], $result);
  979. $entity->setVirtual(['name'], true);
  980. $result = $entity->getVirtual();
  981. $this->assertSame(['virtual', 'name'], $result);
  982. }
  983. /**
  984. * Tests error getters and setters
  985. */
  986. public function testGetErrorAndSetError(): void
  987. {
  988. $entity = new Entity();
  989. $this->assertEmpty($entity->getErrors());
  990. $entity->setError('foo', 'bar');
  991. $this->assertEquals(['bar'], $entity->getError('foo'));
  992. $expected = [
  993. 'foo' => ['bar'],
  994. ];
  995. $result = $entity->getErrors();
  996. $this->assertEquals($expected, $result);
  997. $indexedErrors = [2 => ['foo' => 'bar']];
  998. $entity = new Entity();
  999. $entity->setError('indexes', $indexedErrors);
  1000. $expectedIndexed = [
  1001. 'indexes' => ['2' => ['foo' => 'bar']],
  1002. ];
  1003. $result = $entity->getErrors();
  1004. $this->assertEquals($expectedIndexed, $result);
  1005. }
  1006. /**
  1007. * Tests reading errors from nested validator
  1008. */
  1009. public function testGetErrorNested(): void
  1010. {
  1011. $entity = new Entity();
  1012. $entity->setError('options', ['subpages' => ['_empty' => 'required']]);
  1013. $expected = [
  1014. 'subpages' => ['_empty' => 'required'],
  1015. ];
  1016. $this->assertEquals($expected, $entity->getError('options'));
  1017. $expected = ['_empty' => 'required'];
  1018. $this->assertEquals($expected, $entity->getError('options.subpages'));
  1019. }
  1020. /**
  1021. * Tests that it is possible to get errors for nested entities
  1022. */
  1023. public function testErrorsDeep(): void
  1024. {
  1025. $user = new Entity();
  1026. $owner = new NonExtending();
  1027. $author = new Extending([
  1028. 'foo' => 'bar',
  1029. 'thing' => 'baz',
  1030. 'user' => $user,
  1031. 'owner' => $owner,
  1032. ]);
  1033. $author->setError('thing', ['this is a mistake']);
  1034. $user->setErrors(['a' => ['error1'], 'b' => ['error2']]);
  1035. $owner->setErrors(['c' => ['error3'], 'd' => ['error4']]);
  1036. $expected = ['a' => ['error1'], 'b' => ['error2']];
  1037. $this->assertEquals($expected, $author->getError('user'));
  1038. $expected = ['c' => ['error3'], 'd' => ['error4']];
  1039. $this->assertEquals($expected, $author->getError('owner'));
  1040. $author->set('multiple', [$user, $owner]);
  1041. $expected = [
  1042. ['a' => ['error1'], 'b' => ['error2']],
  1043. ['c' => ['error3'], 'd' => ['error4']],
  1044. ];
  1045. $this->assertEquals($expected, $author->getError('multiple'));
  1046. $expected = [
  1047. 'thing' => $author->getError('thing'),
  1048. 'user' => $author->getError('user'),
  1049. 'owner' => $author->getError('owner'),
  1050. 'multiple' => $author->getError('multiple'),
  1051. ];
  1052. $this->assertEquals($expected, $author->getErrors());
  1053. }
  1054. /**
  1055. * Tests that check if hasErrors() works
  1056. */
  1057. public function testHasErrors(): void
  1058. {
  1059. $entity = new Entity();
  1060. $hasErrors = $entity->hasErrors();
  1061. $this->assertFalse($hasErrors);
  1062. $nestedEntity = new Entity();
  1063. $entity->set([
  1064. 'nested' => $nestedEntity,
  1065. ]);
  1066. $hasErrors = $entity->hasErrors();
  1067. $this->assertFalse($hasErrors);
  1068. $nestedEntity->setError('description', 'oops');
  1069. $hasErrors = $entity->hasErrors();
  1070. $this->assertTrue($hasErrors);
  1071. $hasErrors = $entity->hasErrors(false);
  1072. $this->assertFalse($hasErrors);
  1073. $entity->clean();
  1074. $hasErrors = $entity->hasErrors();
  1075. $this->assertTrue($hasErrors);
  1076. $hasErrors = $entity->hasErrors(false);
  1077. $this->assertFalse($hasErrors);
  1078. $nestedEntity->clean();
  1079. $hasErrors = $entity->hasErrors();
  1080. $this->assertFalse($hasErrors);
  1081. $entity->setError('foo', []);
  1082. $this->assertFalse($entity->hasErrors());
  1083. }
  1084. /**
  1085. * Test that errors can be read with a path.
  1086. */
  1087. public function testErrorPathReading(): void
  1088. {
  1089. $assoc = new Entity();
  1090. $assoc2 = new NonExtending();
  1091. $entity = new Extending([
  1092. 'field' => 'value',
  1093. 'one' => $assoc,
  1094. 'many' => [$assoc2],
  1095. ]);
  1096. $entity->setError('wrong', 'Bad stuff');
  1097. $assoc->setError('nope', 'Terrible things');
  1098. $assoc2->setError('nope', 'Terrible things');
  1099. $this->assertEquals(['Bad stuff'], $entity->getError('wrong'));
  1100. $this->assertEquals(['Terrible things'], $entity->getError('many.0.nope'));
  1101. $this->assertEquals(['Terrible things'], $entity->getError('one.nope'));
  1102. $this->assertEquals(['nope' => ['Terrible things']], $entity->getError('one'));
  1103. $this->assertEquals([0 => ['nope' => ['Terrible things']]], $entity->getError('many'));
  1104. $this->assertEquals(['nope' => ['Terrible things']], $entity->getError('many.0'));
  1105. $this->assertEquals([], $entity->getError('many.0.mistake'));
  1106. $this->assertEquals([], $entity->getError('one.mistake'));
  1107. $this->assertEquals([], $entity->getError('one.1.mistake'));
  1108. $this->assertEquals([], $entity->getError('many.1.nope'));
  1109. }
  1110. /**
  1111. * Tests that changing the value of a property will remove errors
  1112. * stored for it
  1113. */
  1114. public function testDirtyRemovesError(): void
  1115. {
  1116. $entity = new Entity(['a' => 'b']);
  1117. $entity->setError('a', 'is not good');
  1118. $entity->set('a', 'c');
  1119. $this->assertEmpty($entity->getError('a'));
  1120. $entity->setError('a', 'is not good');
  1121. $entity->setDirty('a', true);
  1122. $this->assertEmpty($entity->getError('a'));
  1123. }
  1124. /**
  1125. * Tests that marking an entity as clean will remove errors too
  1126. */
  1127. public function testCleanRemovesErrors(): void
  1128. {
  1129. $entity = new Entity(['a' => 'b']);
  1130. $entity->setError('a', 'is not good');
  1131. $entity->clean();
  1132. $this->assertEmpty($entity->getErrors());
  1133. }
  1134. /**
  1135. * Tests getAccessible() method
  1136. */
  1137. public function testGetAccessible(): void
  1138. {
  1139. $entity = new Entity();
  1140. $entity->setAccess('*', false);
  1141. $entity->setAccess('bar', true);
  1142. $accessible = $entity->getAccessible();
  1143. $expected = [
  1144. '*' => false,
  1145. 'bar' => true,
  1146. ];
  1147. $this->assertSame($expected, $accessible);
  1148. }
  1149. /**
  1150. * Tests isAccessible() and setAccess() methods
  1151. */
  1152. public function testIsAccessible(): void
  1153. {
  1154. $entity = new Entity();
  1155. $entity->setAccess('*', false);
  1156. $this->assertFalse($entity->isAccessible('foo'));
  1157. $this->assertFalse($entity->isAccessible('bar'));
  1158. $this->assertSame($entity, $entity->setAccess('foo', true));
  1159. $this->assertTrue($entity->isAccessible('foo'));
  1160. $this->assertFalse($entity->isAccessible('bar'));
  1161. $this->assertSame($entity, $entity->setAccess('bar', true));
  1162. $this->assertTrue($entity->isAccessible('foo'));
  1163. $this->assertTrue($entity->isAccessible('bar'));
  1164. $this->assertSame($entity, $entity->setAccess('foo', false));
  1165. $this->assertFalse($entity->isAccessible('foo'));
  1166. $this->assertTrue($entity->isAccessible('bar'));
  1167. $this->assertSame($entity, $entity->setAccess('bar', false));
  1168. $this->assertFalse($entity->isAccessible('foo'));
  1169. $this->assertFalse($entity->isAccessible('bar'));
  1170. }
  1171. /**
  1172. * Tests that an array can be used to set
  1173. */
  1174. public function testAccessibleAsArray(): void
  1175. {
  1176. $entity = new Entity();
  1177. $entity->setAccess(['foo', 'bar', 'baz'], true);
  1178. $this->assertTrue($entity->isAccessible('foo'));
  1179. $this->assertTrue($entity->isAccessible('bar'));
  1180. $this->assertTrue($entity->isAccessible('baz'));
  1181. $entity->setAccess('foo', false);
  1182. $this->assertFalse($entity->isAccessible('foo'));
  1183. $this->assertTrue($entity->isAccessible('bar'));
  1184. $this->assertTrue($entity->isAccessible('baz'));
  1185. $entity->setAccess(['foo', 'bar', 'baz'], false);
  1186. $this->assertFalse($entity->isAccessible('foo'));
  1187. $this->assertFalse($entity->isAccessible('bar'));
  1188. $this->assertFalse($entity->isAccessible('baz'));
  1189. }
  1190. /**
  1191. * Tests that a wildcard can be used for setting accessible properties
  1192. */
  1193. public function testAccessibleWildcard(): void
  1194. {
  1195. $entity = new Entity();
  1196. $entity->setAccess(['foo', 'bar', 'baz'], true);
  1197. $this->assertTrue($entity->isAccessible('foo'));
  1198. $this->assertTrue($entity->isAccessible('bar'));
  1199. $this->assertTrue($entity->isAccessible('baz'));
  1200. $entity->setAccess('*', false);
  1201. $this->assertFalse($entity->isAccessible('foo'));
  1202. $this->assertFalse($entity->isAccessible('bar'));
  1203. $this->assertFalse($entity->isAccessible('baz'));
  1204. $this->assertFalse($entity->isAccessible('newOne'));
  1205. $entity->setAccess('*', true);
  1206. $this->assertTrue($entity->isAccessible('foo'));
  1207. $this->assertTrue($entity->isAccessible('bar'));
  1208. $this->assertTrue($entity->isAccessible('baz'));
  1209. $this->assertTrue($entity->isAccessible('newOne2'));
  1210. }
  1211. /**
  1212. * Tests that only accessible properties can be set
  1213. */
  1214. public function testSetWithAccessible(): void
  1215. {
  1216. $entity = new Entity(['foo' => 1, 'bar' => 2]);
  1217. $options = ['guard' => true];
  1218. $entity->setAccess('*', false);
  1219. $entity->setAccess('foo', true);
  1220. $entity->set('bar', 3, $options);
  1221. $entity->set('foo', 4, $options);
  1222. $this->assertSame(2, $entity->get('bar'));
  1223. $this->assertSame(4, $entity->get('foo'));
  1224. $entity->setAccess('bar', true);
  1225. $entity->set('bar', 3, $options);
  1226. $this->assertSame(3, $entity->get('bar'));
  1227. }
  1228. /**
  1229. * Tests that only accessible properties can be set
  1230. */
  1231. public function testSetWithAccessibleWithArray(): void
  1232. {
  1233. $entity = new Entity(['foo' => 1, 'bar' => 2]);
  1234. $options = ['guard' => true];
  1235. $entity->setAccess('*', false);
  1236. $entity->setAccess('foo', true);
  1237. $entity->set(['bar' => 3, 'foo' => 4], $options);
  1238. $this->assertSame(2, $entity->get('bar'));
  1239. $this->assertSame(4, $entity->get('foo'));
  1240. $entity->setAccess('bar', true);
  1241. $entity->set(['bar' => 3, 'foo' => 5], $options);
  1242. $this->assertSame(3, $entity->get('bar'));
  1243. $this->assertSame(5, $entity->get('foo'));
  1244. }
  1245. /**
  1246. * Test that accessible() and single property setting works.
  1247. */
  1248. public function testSetWithAccessibleSingleProperty(): void
  1249. {
  1250. $entity = new Entity(['foo' => 1, 'bar' => 2]);
  1251. $entity->setAccess('*', false);
  1252. $entity->setAccess('title', true);
  1253. $entity->set(['title' => 'test', 'body' => 'Nope']);
  1254. $this->assertSame('test', $entity->title);
  1255. $this->assertNull($entity->body);
  1256. $entity->body = 'Yep';
  1257. $this->assertSame('Yep', $entity->body, 'Single set should bypass guards.');
  1258. $entity->set('body', 'Yes');
  1259. $this->assertSame('Yes', $entity->body, 'Single set should bypass guards.');
  1260. }
  1261. /**
  1262. * Tests the entity's __toString method
  1263. */
  1264. public function testToString(): void
  1265. {
  1266. $entity = new Entity(['foo' => 1, 'bar' => 2]);
  1267. $this->assertEquals(json_encode($entity, JSON_PRETTY_PRINT), (string)$entity);
  1268. }
  1269. /**
  1270. * Tests __debugInfo
  1271. */
  1272. public function testDebugInfo(): void
  1273. {
  1274. $entity = new Entity(['foo' => 'bar'], ['markClean' => true]);
  1275. $entity->somethingElse = 'value';
  1276. $entity->setAccess('id', false);
  1277. $entity->setAccess('name', true);
  1278. $entity->setVirtual(['baz']);
  1279. $entity->setDirty('foo', true);
  1280. $entity->setError('foo', ['An error']);
  1281. $entity->setInvalidField('foo', 'a value');
  1282. $entity->setSource('foos');
  1283. $result = $entity->__debugInfo();
  1284. $expected = [
  1285. 'foo' => 'bar',
  1286. 'somethingElse' => 'value',
  1287. 'baz' => null,
  1288. '[new]' => true,
  1289. '[accessible]' => ['*' => true, 'id' => false, 'name' => true],
  1290. '[dirty]' => ['somethingElse' => true, 'foo' => true],
  1291. '[original]' => [],
  1292. '[originalFields]' => ['foo'],
  1293. '[virtual]' => ['baz'],
  1294. '[hasErrors]' => true,
  1295. '[errors]' => ['foo' => ['An error']],
  1296. '[invalid]' => ['foo' => 'a value'],
  1297. '[repository]' => 'foos',
  1298. ];
  1299. $this->assertSame($expected, $result);
  1300. }
  1301. /**
  1302. * Test the source getter
  1303. */
  1304. public function testGetAndSetSource(): void
  1305. {
  1306. $entity = new Entity();
  1307. $this->assertSame('', $entity->getSource());
  1308. $entity->setSource('foos');
  1309. $this->assertSame('foos', $entity->getSource());
  1310. }
  1311. /**
  1312. * Provides empty values
  1313. *
  1314. * @return array
  1315. */
  1316. public function emptyNamesProvider(): array
  1317. {
  1318. return [[''], [null]];
  1319. }
  1320. /**
  1321. * Tests that trying to get an empty property name throws exception
  1322. */
  1323. public function testEmptyProperties(): void
  1324. {
  1325. $this->expectException(InvalidArgumentException::class);
  1326. $entity = new Entity();
  1327. $entity->get('');
  1328. }
  1329. /**
  1330. * Provides empty values
  1331. */
  1332. public function testIsDirtyFromClone(): void
  1333. {
  1334. $entity = new Entity(
  1335. ['a' => 1, 'b' => 2],
  1336. ['markNew' => false, 'markClean' => true]
  1337. );
  1338. $this->assertFalse($entity->isNew());
  1339. $this->assertFalse($entity->isDirty());
  1340. $cloned = clone $entity;
  1341. $cloned->setNew(true);
  1342. $this->assertTrue($cloned->isDirty());
  1343. $this->assertTrue($cloned->isDirty('a'));
  1344. $this->assertTrue($cloned->isDirty('b'));
  1345. }
  1346. /**
  1347. * Tests getInvalid and setInvalid
  1348. */
  1349. public function testGetSetInvalid(): void
  1350. {
  1351. $entity = new Entity();
  1352. $return = $entity->setInvalid([
  1353. 'title' => 'albert',
  1354. 'body' => 'einstein',
  1355. ]);
  1356. $this->assertSame($entity, $return);
  1357. $this->assertSame([
  1358. 'title' => 'albert',
  1359. 'body' => 'einstein',
  1360. ], $entity->getInvalid());
  1361. $set = $entity->setInvalid([
  1362. 'title' => 'nikola',
  1363. 'body' => 'tesla',
  1364. ]);
  1365. $this->assertSame([
  1366. 'title' => 'albert',
  1367. 'body' => 'einstein',
  1368. ], $set->getInvalid());
  1369. $overwrite = $entity->setInvalid([
  1370. 'title' => 'nikola',
  1371. 'body' => 'tesla',
  1372. ], true);
  1373. $this->assertSame($entity, $overwrite);
  1374. $this->assertSame([
  1375. 'title' => 'nikola',
  1376. 'body' => 'tesla',
  1377. ], $entity->getInvalid());
  1378. }
  1379. /**
  1380. * Tests getInvalidField
  1381. */
  1382. public function testGetSetInvalidField(): void
  1383. {
  1384. $entity = new Entity();
  1385. $return = $entity->setInvalidField('title', 'albert');
  1386. $this->assertSame($entity, $return);
  1387. $this->assertSame('albert', $entity->getInvalidField('title'));
  1388. $overwrite = $entity->setInvalidField('title', 'nikola');
  1389. $this->assertSame($entity, $overwrite);
  1390. $this->assertSame('nikola', $entity->getInvalidField('title'));
  1391. }
  1392. /**
  1393. * Tests getInvalidFieldNull
  1394. */
  1395. public function testGetInvalidFieldNull(): void
  1396. {
  1397. $entity = new Entity();
  1398. $this->assertNull($entity->getInvalidField('foo'));
  1399. }
  1400. /**
  1401. * Test the isEmpty() check
  1402. */
  1403. public function testIsEmpty(): void
  1404. {
  1405. $entity = new Entity([
  1406. 'array' => ['foo' => 'bar'],
  1407. 'emptyArray' => [],
  1408. 'object' => new stdClass(),
  1409. 'string' => 'string',
  1410. 'stringZero' => '0',
  1411. 'emptyString' => '',
  1412. 'intZero' => 0,
  1413. 'intNotZero' => 1,
  1414. 'floatZero' => 0.0,
  1415. 'floatNonZero' => 1.5,
  1416. 'null' => null,
  1417. ]);
  1418. $this->assertFalse($entity->isEmpty('array'));
  1419. $this->assertTrue($entity->isEmpty('emptyArray'));
  1420. $this->assertFalse($entity->isEmpty('object'));
  1421. $this->assertFalse($entity->isEmpty('string'));
  1422. $this->assertFalse($entity->isEmpty('stringZero'));
  1423. $this->assertTrue($entity->isEmpty('emptyString'));
  1424. $this->assertFalse($entity->isEmpty('intZero'));
  1425. $this->assertFalse($entity->isEmpty('intNotZero'));
  1426. $this->assertFalse($entity->isEmpty('floatZero'));
  1427. $this->assertFalse($entity->isEmpty('floatNonZero'));
  1428. $this->assertTrue($entity->isEmpty('null'));
  1429. }
  1430. /**
  1431. * Test hasValue()
  1432. */
  1433. public function testHasValue(): void
  1434. {
  1435. $entity = new Entity([
  1436. 'array' => ['foo' => 'bar'],
  1437. 'emptyArray' => [],
  1438. 'object' => new stdClass(),
  1439. 'string' => 'string',
  1440. 'stringZero' => '0',
  1441. 'emptyString' => '',
  1442. 'intZero' => 0,
  1443. 'intNotZero' => 1,
  1444. 'floatZero' => 0.0,
  1445. 'floatNonZero' => 1.5,
  1446. 'null' => null,
  1447. ]);
  1448. $this->assertTrue($entity->hasValue('array'));
  1449. $this->assertFalse($entity->hasValue('emptyArray'));
  1450. $this->assertTrue($entity->hasValue('object'));
  1451. $this->assertTrue($entity->hasValue('string'));
  1452. $this->assertTrue($entity->hasValue('stringZero'));
  1453. $this->assertFalse($entity->hasValue('emptyString'));
  1454. $this->assertTrue($entity->hasValue('intZero'));
  1455. $this->assertTrue($entity->hasValue('intNotZero'));
  1456. $this->assertTrue($entity->hasValue('floatZero'));
  1457. $this->assertTrue($entity->hasValue('floatNonZero'));
  1458. $this->assertFalse($entity->hasValue('null'));
  1459. }
  1460. /**
  1461. * Test isOriginalField()
  1462. */
  1463. public function testIsOriginalField(): void
  1464. {
  1465. $entity = new Entity(['foo' => null]);
  1466. $return = $entity->isOriginalField('foo');
  1467. $this->assertSame(true, $return);
  1468. $entity = new Entity([]);
  1469. $entity->set('foo', null);
  1470. $return = $entity->isOriginalField('foo');
  1471. $this->assertSame(false, $return);
  1472. $return = $entity->isOriginalField('bar');
  1473. $this->assertSame(false, $return);
  1474. }
  1475. /**
  1476. * Test getOriginalFields()
  1477. */
  1478. public function testGetOriginalFields(): void
  1479. {
  1480. $entity = new Entity(['foo' => 'foo', 'bar' => 'bar']);
  1481. $entity->set('baz', 'baz');
  1482. $return = $entity->getOriginalFields();
  1483. $this->assertEquals(['foo', 'bar'], $return);
  1484. $entity = new Entity([]);
  1485. $entity->set('foo', 'foo');
  1486. $entity->set('bar', 'bar');
  1487. $entity->set('baz', 'baz');
  1488. $return = $entity->getOriginalFields();
  1489. $this->assertEquals([], $return);
  1490. }
  1491. /**
  1492. * Test setOriginalField() inside EntityInterface::setDirty()
  1493. */
  1494. public function testSetOriginalFieldInSetDirty(): void
  1495. {
  1496. $entity = new Entity([]);
  1497. $entity->set('foo', 'bar');
  1498. $return = $entity->isOriginalField('foo');
  1499. $this->assertSame(false, $return);
  1500. $entity->setDirty('foo', false);
  1501. $return = $entity->isOriginalField('foo');
  1502. $this->assertSame(true, $return);
  1503. }
  1504. /**
  1505. * Test setOriginalField() inside EntityInterface::clean()
  1506. */
  1507. public function testSetOriginalFieldInClean(): void
  1508. {
  1509. $entity = new Entity([]);
  1510. $entity->set('foo', 'bar');
  1511. $return = $entity->isOriginalField('foo');
  1512. $this->assertSame(false, $return);
  1513. $entity->clean();
  1514. $return = $entity->isOriginalField('foo');
  1515. $this->assertSame(true, $return);
  1516. }
  1517. }