PropertyAccessorTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\PropertyAccess\Tests;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\Cache\Adapter\ArrayAdapter;
  13. use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
  14. use Symfony\Component\PropertyAccess\PropertyAccessor;
  15. use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
  16. use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
  17. use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
  18. use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
  19. use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
  20. use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
  21. use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassTypeErrorInsideCall;
  22. use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
  23. use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
  24. class PropertyAccessorTest extends TestCase
  25. {
  26. /**
  27. * @var PropertyAccessor
  28. */
  29. private $propertyAccessor;
  30. protected function setUp()
  31. {
  32. $this->propertyAccessor = new PropertyAccessor();
  33. }
  34. public function getPathsWithUnexpectedType()
  35. {
  36. return array(
  37. array('', 'foobar'),
  38. array('foo', 'foobar'),
  39. array(null, 'foobar'),
  40. array(123, 'foobar'),
  41. array((object) array('prop' => null), 'prop.foobar'),
  42. array((object) array('prop' => (object) array('subProp' => null)), 'prop.subProp.foobar'),
  43. array(array('index' => null), '[index][foobar]'),
  44. array(array('index' => array('subIndex' => null)), '[index][subIndex][foobar]'),
  45. );
  46. }
  47. public function getPathsWithMissingProperty()
  48. {
  49. return array(
  50. array((object) array('firstName' => 'Bernhard'), 'lastName'),
  51. array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.lastName'),
  52. array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].lastName'),
  53. array(new TestClass('Bernhard'), 'protectedProperty'),
  54. array(new TestClass('Bernhard'), 'privateProperty'),
  55. array(new TestClass('Bernhard'), 'protectedAccessor'),
  56. array(new TestClass('Bernhard'), 'protectedIsAccessor'),
  57. array(new TestClass('Bernhard'), 'protectedHasAccessor'),
  58. array(new TestClass('Bernhard'), 'privateAccessor'),
  59. array(new TestClass('Bernhard'), 'privateIsAccessor'),
  60. array(new TestClass('Bernhard'), 'privateHasAccessor'),
  61. // Properties are not camelized
  62. array(new TestClass('Bernhard'), 'public_property'),
  63. );
  64. }
  65. public function getPathsWithMissingIndex()
  66. {
  67. return array(
  68. array(array('firstName' => 'Bernhard'), '[lastName]'),
  69. array(array(), '[index][lastName]'),
  70. array(array('index' => array()), '[index][lastName]'),
  71. array(array('index' => array('firstName' => 'Bernhard')), '[index][lastName]'),
  72. array((object) array('property' => array('firstName' => 'Bernhard')), 'property[lastName]'),
  73. );
  74. }
  75. /**
  76. * @dataProvider getValidPropertyPaths
  77. */
  78. public function testGetValue($objectOrArray, $path, $value)
  79. {
  80. $this->assertSame($value, $this->propertyAccessor->getValue($objectOrArray, $path));
  81. }
  82. /**
  83. * @dataProvider getPathsWithMissingProperty
  84. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
  85. */
  86. public function testGetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
  87. {
  88. $this->propertyAccessor->getValue($objectOrArray, $path);
  89. }
  90. /**
  91. * @dataProvider getPathsWithMissingIndex
  92. */
  93. public function testGetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $path)
  94. {
  95. $this->assertNull($this->propertyAccessor->getValue($objectOrArray, $path));
  96. }
  97. /**
  98. * @dataProvider getPathsWithMissingIndex
  99. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
  100. */
  101. public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
  102. {
  103. $this->propertyAccessor = new PropertyAccessor(false, true);
  104. $this->propertyAccessor->getValue($objectOrArray, $path);
  105. }
  106. /**
  107. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
  108. */
  109. public function testGetValueThrowsExceptionIfNotArrayAccess()
  110. {
  111. $this->propertyAccessor->getValue(new \stdClass(), '[index]');
  112. }
  113. public function testGetValueReadsMagicGet()
  114. {
  115. $this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'magicProperty'));
  116. }
  117. public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
  118. {
  119. $object = new \ArrayObject();
  120. $array = array('child' => array('index' => $object));
  121. $this->assertNull($this->propertyAccessor->getValue($array, '[child][index][foo][bar]'));
  122. $this->assertSame(array(), $object->getArrayCopy());
  123. }
  124. // https://github.com/symfony/symfony/pull/4450
  125. public function testGetValueReadsMagicGetThatReturnsConstant()
  126. {
  127. $this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicGet('Bernhard'), 'constantMagicProperty'));
  128. }
  129. public function testGetValueNotModifyObject()
  130. {
  131. $object = new \stdClass();
  132. $object->firstName = array('Bernhard');
  133. $this->assertNull($this->propertyAccessor->getValue($object, 'firstName[1]'));
  134. $this->assertSame(array('Bernhard'), $object->firstName);
  135. }
  136. public function testGetValueNotModifyObjectException()
  137. {
  138. $propertyAccessor = new PropertyAccessor(false, true);
  139. $object = new \stdClass();
  140. $object->firstName = array('Bernhard');
  141. try {
  142. $propertyAccessor->getValue($object, 'firstName[1]');
  143. } catch (NoSuchIndexException $e) {
  144. }
  145. $this->assertSame(array('Bernhard'), $object->firstName);
  146. }
  147. /**
  148. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
  149. */
  150. public function testGetValueDoesNotReadMagicCallByDefault()
  151. {
  152. $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty');
  153. }
  154. public function testGetValueReadsMagicCallIfEnabled()
  155. {
  156. $this->propertyAccessor = new PropertyAccessor(true);
  157. $this->assertSame('Bernhard', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
  158. }
  159. // https://github.com/symfony/symfony/pull/4450
  160. public function testGetValueReadsMagicCallThatReturnsConstant()
  161. {
  162. $this->propertyAccessor = new PropertyAccessor(true);
  163. $this->assertSame('constant value', $this->propertyAccessor->getValue(new TestClassMagicCall('Bernhard'), 'constantMagicCallProperty'));
  164. }
  165. /**
  166. * @dataProvider getPathsWithUnexpectedType
  167. * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
  168. * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
  169. */
  170. public function testGetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
  171. {
  172. $this->propertyAccessor->getValue($objectOrArray, $path);
  173. }
  174. /**
  175. * @dataProvider getValidPropertyPaths
  176. */
  177. public function testSetValue($objectOrArray, $path)
  178. {
  179. $this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
  180. $this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
  181. }
  182. /**
  183. * @dataProvider getPathsWithMissingProperty
  184. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
  185. */
  186. public function testSetValueThrowsExceptionIfPropertyNotFound($objectOrArray, $path)
  187. {
  188. $this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
  189. }
  190. /**
  191. * @dataProvider getPathsWithMissingIndex
  192. */
  193. public function testSetValueThrowsNoExceptionIfIndexNotFound($objectOrArray, $path)
  194. {
  195. $this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
  196. $this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
  197. }
  198. /**
  199. * @dataProvider getPathsWithMissingIndex
  200. */
  201. public function testSetValueThrowsNoExceptionIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
  202. {
  203. $this->propertyAccessor = new PropertyAccessor(false, true);
  204. $this->propertyAccessor->setValue($objectOrArray, $path, 'Updated');
  205. $this->assertSame('Updated', $this->propertyAccessor->getValue($objectOrArray, $path));
  206. }
  207. /**
  208. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
  209. */
  210. public function testSetValueThrowsExceptionIfNotArrayAccess()
  211. {
  212. $object = new \stdClass();
  213. $this->propertyAccessor->setValue($object, '[index]', 'Updated');
  214. }
  215. public function testSetValueUpdatesMagicSet()
  216. {
  217. $author = new TestClassMagicGet('Bernhard');
  218. $this->propertyAccessor->setValue($author, 'magicProperty', 'Updated');
  219. $this->assertEquals('Updated', $author->__get('magicProperty'));
  220. }
  221. /**
  222. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
  223. */
  224. public function testSetValueThrowsExceptionIfThereAreMissingParameters()
  225. {
  226. $object = new TestClass('Bernhard');
  227. $this->propertyAccessor->setValue($object, 'publicAccessorWithMoreRequiredParameters', 'Updated');
  228. }
  229. /**
  230. * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
  231. */
  232. public function testSetValueDoesNotUpdateMagicCallByDefault()
  233. {
  234. $author = new TestClassMagicCall('Bernhard');
  235. $this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
  236. }
  237. public function testSetValueUpdatesMagicCallIfEnabled()
  238. {
  239. $this->propertyAccessor = new PropertyAccessor(true);
  240. $author = new TestClassMagicCall('Bernhard');
  241. $this->propertyAccessor->setValue($author, 'magicCallProperty', 'Updated');
  242. $this->assertEquals('Updated', $author->__call('getMagicCallProperty', array()));
  243. }
  244. /**
  245. * @dataProvider getPathsWithUnexpectedType
  246. * @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
  247. * @expectedExceptionMessage PropertyAccessor requires a graph of objects or arrays to operate on
  248. */
  249. public function testSetValueThrowsExceptionIfNotObjectOrArray($objectOrArray, $path)
  250. {
  251. $this->propertyAccessor->setValue($objectOrArray, $path, 'value');
  252. }
  253. public function testGetValueWhenArrayValueIsNull()
  254. {
  255. $this->propertyAccessor = new PropertyAccessor(false, true);
  256. $this->assertNull($this->propertyAccessor->getValue(array('index' => array('nullable' => null)), '[index][nullable]'));
  257. }
  258. /**
  259. * @dataProvider getValidPropertyPaths
  260. */
  261. public function testIsReadable($objectOrArray, $path)
  262. {
  263. $this->assertTrue($this->propertyAccessor->isReadable($objectOrArray, $path));
  264. }
  265. /**
  266. * @dataProvider getPathsWithMissingProperty
  267. */
  268. public function testIsReadableReturnsFalseIfPropertyNotFound($objectOrArray, $path)
  269. {
  270. $this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
  271. }
  272. /**
  273. * @dataProvider getPathsWithMissingIndex
  274. */
  275. public function testIsReadableReturnsTrueIfIndexNotFound($objectOrArray, $path)
  276. {
  277. // Non-existing indices can be read. In this case, null is returned
  278. $this->assertTrue($this->propertyAccessor->isReadable($objectOrArray, $path));
  279. }
  280. /**
  281. * @dataProvider getPathsWithMissingIndex
  282. */
  283. public function testIsReadableReturnsFalseIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
  284. {
  285. $this->propertyAccessor = new PropertyAccessor(false, true);
  286. // When exceptions are enabled, non-existing indices cannot be read
  287. $this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
  288. }
  289. public function testIsReadableRecognizesMagicGet()
  290. {
  291. $this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicGet('Bernhard'), 'magicProperty'));
  292. }
  293. public function testIsReadableDoesNotRecognizeMagicCallByDefault()
  294. {
  295. $this->assertFalse($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
  296. }
  297. public function testIsReadableRecognizesMagicCallIfEnabled()
  298. {
  299. $this->propertyAccessor = new PropertyAccessor(true);
  300. $this->assertTrue($this->propertyAccessor->isReadable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
  301. }
  302. /**
  303. * @dataProvider getPathsWithUnexpectedType
  304. */
  305. public function testIsReadableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
  306. {
  307. $this->assertFalse($this->propertyAccessor->isReadable($objectOrArray, $path));
  308. }
  309. /**
  310. * @dataProvider getValidPropertyPaths
  311. */
  312. public function testIsWritable($objectOrArray, $path)
  313. {
  314. $this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
  315. }
  316. /**
  317. * @dataProvider getPathsWithMissingProperty
  318. */
  319. public function testIsWritableReturnsFalseIfPropertyNotFound($objectOrArray, $path)
  320. {
  321. $this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
  322. }
  323. /**
  324. * @dataProvider getPathsWithMissingIndex
  325. */
  326. public function testIsWritableReturnsTrueIfIndexNotFound($objectOrArray, $path)
  327. {
  328. // Non-existing indices can be written. Arrays are created on-demand.
  329. $this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
  330. }
  331. /**
  332. * @dataProvider getPathsWithMissingIndex
  333. */
  334. public function testIsWritableReturnsTrueIfIndexNotFoundAndIndexExceptionsEnabled($objectOrArray, $path)
  335. {
  336. $this->propertyAccessor = new PropertyAccessor(false, true);
  337. // Non-existing indices can be written even if exceptions are enabled
  338. $this->assertTrue($this->propertyAccessor->isWritable($objectOrArray, $path));
  339. }
  340. public function testIsWritableRecognizesMagicSet()
  341. {
  342. $this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicGet('Bernhard'), 'magicProperty'));
  343. }
  344. public function testIsWritableDoesNotRecognizeMagicCallByDefault()
  345. {
  346. $this->assertFalse($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
  347. }
  348. public function testIsWritableRecognizesMagicCallIfEnabled()
  349. {
  350. $this->propertyAccessor = new PropertyAccessor(true);
  351. $this->assertTrue($this->propertyAccessor->isWritable(new TestClassMagicCall('Bernhard'), 'magicCallProperty'));
  352. }
  353. /**
  354. * @dataProvider getPathsWithUnexpectedType
  355. */
  356. public function testIsWritableReturnsFalseIfNotObjectOrArray($objectOrArray, $path)
  357. {
  358. $this->assertFalse($this->propertyAccessor->isWritable($objectOrArray, $path));
  359. }
  360. public function getValidPropertyPaths()
  361. {
  362. return array(
  363. array(array('Bernhard', 'Schussek'), '[0]', 'Bernhard'),
  364. array(array('Bernhard', 'Schussek'), '[1]', 'Schussek'),
  365. array(array('firstName' => 'Bernhard'), '[firstName]', 'Bernhard'),
  366. array(array('index' => array('firstName' => 'Bernhard')), '[index][firstName]', 'Bernhard'),
  367. array((object) array('firstName' => 'Bernhard'), 'firstName', 'Bernhard'),
  368. array((object) array('property' => array('firstName' => 'Bernhard')), 'property[firstName]', 'Bernhard'),
  369. array(array('index' => (object) array('firstName' => 'Bernhard')), '[index].firstName', 'Bernhard'),
  370. array((object) array('property' => (object) array('firstName' => 'Bernhard')), 'property.firstName', 'Bernhard'),
  371. // Accessor methods
  372. array(new TestClass('Bernhard'), 'publicProperty', 'Bernhard'),
  373. array(new TestClass('Bernhard'), 'publicAccessor', 'Bernhard'),
  374. array(new TestClass('Bernhard'), 'publicAccessorWithDefaultValue', 'Bernhard'),
  375. array(new TestClass('Bernhard'), 'publicAccessorWithRequiredAndDefaultValue', 'Bernhard'),
  376. array(new TestClass('Bernhard'), 'publicIsAccessor', 'Bernhard'),
  377. array(new TestClass('Bernhard'), 'publicHasAccessor', 'Bernhard'),
  378. array(new TestClass('Bernhard'), 'publicGetSetter', 'Bernhard'),
  379. // Methods are camelized
  380. array(new TestClass('Bernhard'), 'public_accessor', 'Bernhard'),
  381. array(new TestClass('Bernhard'), '_public_accessor', 'Bernhard'),
  382. // Missing indices
  383. array(array('index' => array()), '[index][firstName]', null),
  384. array(array('root' => array('index' => array())), '[root][index][firstName]', null),
  385. // Special chars
  386. array(array('%!@$§.' => 'Bernhard'), '[%!@$§.]', 'Bernhard'),
  387. array(array('index' => array('%!@$§.' => 'Bernhard')), '[index][%!@$§.]', 'Bernhard'),
  388. array((object) array('%!@$§' => 'Bernhard'), '%!@$§', 'Bernhard'),
  389. array((object) array('property' => (object) array('%!@$§' => 'Bernhard')), 'property.%!@$§', 'Bernhard'),
  390. // nested objects and arrays
  391. array(array('foo' => new TestClass('bar')), '[foo].publicGetSetter', 'bar'),
  392. array(new TestClass(array('foo' => 'bar')), 'publicGetSetter[foo]', 'bar'),
  393. array(new TestClass(new TestClass('bar')), 'publicGetter.publicGetSetter', 'bar'),
  394. array(new TestClass(array('foo' => new TestClass('bar'))), 'publicGetter[foo].publicGetSetter', 'bar'),
  395. array(new TestClass(new TestClass(new TestClass('bar'))), 'publicGetter.publicGetter.publicGetSetter', 'bar'),
  396. array(new TestClass(array('foo' => array('baz' => new TestClass('bar')))), 'publicGetter[foo][baz].publicGetSetter', 'bar'),
  397. );
  398. }
  399. public function testTicket5755()
  400. {
  401. $object = new Ticket5775Object();
  402. $this->propertyAccessor->setValue($object, 'property', 'foobar');
  403. $this->assertEquals('foobar', $object->getProperty());
  404. }
  405. public function testSetValueDeepWithMagicGetter()
  406. {
  407. $obj = new TestClassMagicGet('foo');
  408. $obj->publicProperty = array('foo' => array('bar' => 'some_value'));
  409. $this->propertyAccessor->setValue($obj, 'publicProperty[foo][bar]', 'Updated');
  410. $this->assertSame('Updated', $obj->publicProperty['foo']['bar']);
  411. }
  412. public function getReferenceChainObjectsForSetValue()
  413. {
  414. return array(
  415. array(array('a' => array('b' => array('c' => 'old-value'))), '[a][b][c]', 'new-value'),
  416. array(new TestClassSetValue(new TestClassSetValue('old-value')), 'value.value', 'new-value'),
  417. array(new TestClassSetValue(array('a' => array('b' => array('c' => new TestClassSetValue('old-value'))))), 'value[a][b][c].value', 'new-value'),
  418. array(new TestClassSetValue(array('a' => array('b' => 'old-value'))), 'value[a][b]', 'new-value'),
  419. array(new \ArrayIterator(array('a' => array('b' => array('c' => 'old-value')))), '[a][b][c]', 'new-value'),
  420. );
  421. }
  422. /**
  423. * @dataProvider getReferenceChainObjectsForSetValue
  424. */
  425. public function testSetValueForReferenceChainIssue($object, $path, $value)
  426. {
  427. $this->propertyAccessor->setValue($object, $path, $value);
  428. $this->assertEquals($value, $this->propertyAccessor->getValue($object, $path));
  429. }
  430. public function getReferenceChainObjectsForIsWritable()
  431. {
  432. return array(
  433. array(new TestClassIsWritable(array('a' => array('b' => 'old-value'))), 'value[a][b]', false),
  434. array(new TestClassIsWritable(new \ArrayIterator(array('a' => array('b' => 'old-value')))), 'value[a][b]', true),
  435. array(new TestClassIsWritable(array('a' => array('b' => array('c' => new TestClassSetValue('old-value'))))), 'value[a][b][c].value', true),
  436. );
  437. }
  438. /**
  439. * @dataProvider getReferenceChainObjectsForIsWritable
  440. */
  441. public function testIsWritableForReferenceChainIssue($object, $path, $value)
  442. {
  443. $this->assertEquals($value, $this->propertyAccessor->isWritable($object, $path));
  444. }
  445. /**
  446. * @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
  447. * @expectedExceptionMessage Expected argument of type "DateTime", "string" given
  448. */
  449. public function testThrowTypeError()
  450. {
  451. $object = new TypeHinted();
  452. $this->propertyAccessor->setValue($object, 'date', 'This is a string, \DateTime expected.');
  453. }
  454. /**
  455. * @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
  456. * @expectedExceptionMessage Expected argument of type "DateTime", "NULL" given
  457. */
  458. public function testThrowTypeErrorWithNullArgument()
  459. {
  460. $object = new TypeHinted();
  461. $this->propertyAccessor->setValue($object, 'date', null);
  462. }
  463. public function testSetTypeHint()
  464. {
  465. $date = new \DateTime();
  466. $object = new TypeHinted();
  467. $this->propertyAccessor->setValue($object, 'date', $date);
  468. $this->assertSame($date, $object->getDate());
  469. }
  470. public function testArrayNotBeeingOverwritten()
  471. {
  472. $value = array('value1' => 'foo', 'value2' => 'bar');
  473. $object = new TestClass($value);
  474. $this->propertyAccessor->setValue($object, 'publicAccessor[value2]', 'baz');
  475. $this->assertSame('baz', $this->propertyAccessor->getValue($object, 'publicAccessor[value2]'));
  476. $this->assertSame(array('value1' => 'foo', 'value2' => 'baz'), $object->getPublicAccessor());
  477. }
  478. public function testCacheReadAccess()
  479. {
  480. $obj = new TestClass('foo');
  481. $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
  482. $this->assertEquals('foo', $propertyAccessor->getValue($obj, 'publicGetSetter'));
  483. $propertyAccessor->setValue($obj, 'publicGetSetter', 'bar');
  484. $propertyAccessor->setValue($obj, 'publicGetSetter', 'baz');
  485. $this->assertEquals('baz', $propertyAccessor->getValue($obj, 'publicGetSetter'));
  486. }
  487. /**
  488. * @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
  489. * @expectedExceptionMessage Expected argument of type "Countable", "string" given
  490. */
  491. public function testThrowTypeErrorWithInterface()
  492. {
  493. $object = new TypeHinted();
  494. $this->propertyAccessor->setValue($object, 'countable', 'This is a string, \Countable expected.');
  495. }
  496. public function testAnonymousClassRead()
  497. {
  498. $value = 'bar';
  499. $obj = $this->generateAnonymousClass($value);
  500. $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
  501. $this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
  502. }
  503. public function testAnonymousClassWrite()
  504. {
  505. $value = 'bar';
  506. $obj = $this->generateAnonymousClass('');
  507. $propertyAccessor = new PropertyAccessor(false, false, new ArrayAdapter());
  508. $propertyAccessor->setValue($obj, 'foo', $value);
  509. $this->assertEquals($value, $propertyAccessor->getValue($obj, 'foo'));
  510. }
  511. private function generateAnonymousClass($value)
  512. {
  513. $obj = eval('return new class($value)
  514. {
  515. private $foo;
  516. public function __construct($foo)
  517. {
  518. $this->foo = $foo;
  519. }
  520. /**
  521. * @return mixed
  522. */
  523. public function getFoo()
  524. {
  525. return $this->foo;
  526. }
  527. /**
  528. * @param mixed $foo
  529. */
  530. public function setFoo($foo)
  531. {
  532. $this->foo = $foo;
  533. }
  534. };');
  535. return $obj;
  536. }
  537. /**
  538. * @expectedException \TypeError
  539. */
  540. public function testThrowTypeErrorInsideSetterCall()
  541. {
  542. $object = new TestClassTypeErrorInsideCall();
  543. $this->propertyAccessor->setValue($object, 'property', 'foo');
  544. }
  545. /**
  546. * @expectedException \TypeError
  547. */
  548. public function testDoNotDiscardReturnTypeError()
  549. {
  550. $object = new ReturnTyped();
  551. $this->propertyAccessor->setValue($object, 'foos', array(new \DateTime()));
  552. }
  553. /**
  554. * @expectedException \TypeError
  555. */
  556. public function testDoNotDiscardReturnTypeErrorWhenWriterMethodIsMisconfigured()
  557. {
  558. $object = new ReturnTyped();
  559. $this->propertyAccessor->setValue($object, 'name', 'foo');
  560. }
  561. }