PropertyNormalizerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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\Serializer\Tests\Normalizer;
  11. use Doctrine\Common\Annotations\AnnotationReader;
  12. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
  13. use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
  14. use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
  15. use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
  16. use Symfony\Component\Serializer\Serializer;
  17. use Symfony\Component\Serializer\SerializerInterface;
  18. use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
  19. use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
  20. use Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy;
  21. use Symfony\Component\Serializer\Tests\Fixtures\PropertySiblingHolder;
  22. class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
  23. {
  24. /**
  25. * @var PropertyNormalizer
  26. */
  27. private $normalizer;
  28. /**
  29. * @var SerializerInterface
  30. */
  31. private $serializer;
  32. protected function setUp()
  33. {
  34. $this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock();
  35. $this->normalizer = new PropertyNormalizer();
  36. $this->normalizer->setSerializer($this->serializer);
  37. }
  38. public function testNormalize()
  39. {
  40. $obj = new PropertyDummy();
  41. $obj->foo = 'foo';
  42. $obj->setBar('bar');
  43. $obj->setCamelCase('camelcase');
  44. $this->assertEquals(
  45. array('foo' => 'foo', 'bar' => 'bar', 'camelCase' => 'camelcase'),
  46. $this->normalizer->normalize($obj, 'any')
  47. );
  48. }
  49. public function testDenormalize()
  50. {
  51. $obj = $this->normalizer->denormalize(
  52. array('foo' => 'foo', 'bar' => 'bar'),
  53. __NAMESPACE__.'\PropertyDummy',
  54. 'any'
  55. );
  56. $this->assertEquals('foo', $obj->foo);
  57. $this->assertEquals('bar', $obj->getBar());
  58. }
  59. public function testConstructorDenormalize()
  60. {
  61. $obj = $this->normalizer->denormalize(
  62. array('foo' => 'foo', 'bar' => 'bar'),
  63. __NAMESPACE__.'\PropertyConstructorDummy',
  64. 'any'
  65. );
  66. $this->assertEquals('foo', $obj->getFoo());
  67. $this->assertEquals('bar', $obj->getBar());
  68. }
  69. public function testConstructorDenormalizeWithNullArgument()
  70. {
  71. $obj = $this->normalizer->denormalize(
  72. array('foo' => null, 'bar' => 'bar'),
  73. __NAMESPACE__.'\PropertyConstructorDummy', '
  74. any'
  75. );
  76. $this->assertNull($obj->getFoo());
  77. $this->assertEquals('bar', $obj->getBar());
  78. }
  79. /**
  80. * @dataProvider provideCallbacks
  81. */
  82. public function testCallbacks($callbacks, $value, $result, $message)
  83. {
  84. $this->normalizer->setCallbacks($callbacks);
  85. $obj = new PropertyConstructorDummy('', $value);
  86. $this->assertEquals(
  87. $result,
  88. $this->normalizer->normalize($obj, 'any'),
  89. $message
  90. );
  91. }
  92. /**
  93. * @expectedException \InvalidArgumentException
  94. */
  95. public function testUncallableCallbacks()
  96. {
  97. $this->normalizer->setCallbacks(array('bar' => null));
  98. $obj = new PropertyConstructorDummy('baz', 'quux');
  99. $this->normalizer->normalize($obj, 'any');
  100. }
  101. public function testIgnoredAttributes()
  102. {
  103. $this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'camelCase'));
  104. $obj = new PropertyDummy();
  105. $obj->foo = 'foo';
  106. $obj->setBar('bar');
  107. $this->assertEquals(
  108. array(),
  109. $this->normalizer->normalize($obj, 'any')
  110. );
  111. }
  112. public function testGroupsNormalize()
  113. {
  114. $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  115. $this->normalizer = new PropertyNormalizer($classMetadataFactory);
  116. $this->normalizer->setSerializer($this->serializer);
  117. $obj = new GroupDummy();
  118. $obj->setFoo('foo');
  119. $obj->setBar('bar');
  120. $obj->setFooBar('fooBar');
  121. $obj->setSymfony('symfony');
  122. $obj->setKevin('kevin');
  123. $obj->setCoopTilleuls('coopTilleuls');
  124. $this->assertEquals(array(
  125. 'bar' => 'bar',
  126. ), $this->normalizer->normalize($obj, null, array(PropertyNormalizer::GROUPS => array('c'))));
  127. // The PropertyNormalizer is not able to hydrate properties from parent classes
  128. $this->assertEquals(array(
  129. 'symfony' => 'symfony',
  130. 'foo' => 'foo',
  131. 'fooBar' => 'fooBar',
  132. 'bar' => 'bar',
  133. ), $this->normalizer->normalize($obj, null, array(PropertyNormalizer::GROUPS => array('a', 'c'))));
  134. }
  135. public function testGroupsDenormalize()
  136. {
  137. $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  138. $this->normalizer = new PropertyNormalizer($classMetadataFactory);
  139. $this->normalizer->setSerializer($this->serializer);
  140. $obj = new GroupDummy();
  141. $obj->setFoo('foo');
  142. $toNormalize = array('foo' => 'foo', 'bar' => 'bar');
  143. $normalized = $this->normalizer->denormalize(
  144. $toNormalize,
  145. 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
  146. null,
  147. array(PropertyNormalizer::GROUPS => array('a'))
  148. );
  149. $this->assertEquals($obj, $normalized);
  150. $obj->setBar('bar');
  151. $normalized = $this->normalizer->denormalize(
  152. $toNormalize,
  153. 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
  154. null,
  155. array(PropertyNormalizer::GROUPS => array('a', 'b'))
  156. );
  157. $this->assertEquals($obj, $normalized);
  158. }
  159. public function testGroupsNormalizeWithNameConverter()
  160. {
  161. $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  162. $this->normalizer = new PropertyNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
  163. $this->normalizer->setSerializer($this->serializer);
  164. $obj = new GroupDummy();
  165. $obj->setFooBar('@dunglas');
  166. $obj->setSymfony('@coopTilleuls');
  167. $obj->setCoopTilleuls('les-tilleuls.coop');
  168. $this->assertEquals(
  169. array(
  170. 'bar' => null,
  171. 'foo_bar' => '@dunglas',
  172. 'symfony' => '@coopTilleuls',
  173. ),
  174. $this->normalizer->normalize($obj, null, array(PropertyNormalizer::GROUPS => array('name_converter')))
  175. );
  176. }
  177. public function testGroupsDenormalizeWithNameConverter()
  178. {
  179. $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  180. $this->normalizer = new PropertyNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
  181. $this->normalizer->setSerializer($this->serializer);
  182. $obj = new GroupDummy();
  183. $obj->setFooBar('@dunglas');
  184. $obj->setSymfony('@coopTilleuls');
  185. $this->assertEquals(
  186. $obj,
  187. $this->normalizer->denormalize(array(
  188. 'bar' => null,
  189. 'foo_bar' => '@dunglas',
  190. 'symfony' => '@coopTilleuls',
  191. 'coop_tilleuls' => 'les-tilleuls.coop',
  192. ), 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', null, array(PropertyNormalizer::GROUPS => array('name_converter')))
  193. );
  194. }
  195. public function provideCallbacks()
  196. {
  197. return array(
  198. array(
  199. array(
  200. 'bar' => function ($bar) {
  201. return 'baz';
  202. },
  203. ),
  204. 'baz',
  205. array('foo' => '', 'bar' => 'baz'),
  206. 'Change a string',
  207. ),
  208. array(
  209. array(
  210. 'bar' => function ($bar) {
  211. return;
  212. },
  213. ),
  214. 'baz',
  215. array('foo' => '', 'bar' => null),
  216. 'Null an item',
  217. ),
  218. array(
  219. array(
  220. 'bar' => function ($bar) {
  221. return $bar->format('d-m-Y H:i:s');
  222. },
  223. ),
  224. new \DateTime('2011-09-10 06:30:00'),
  225. array('foo' => '', 'bar' => '10-09-2011 06:30:00'),
  226. 'Format a date',
  227. ),
  228. array(
  229. array(
  230. 'bar' => function ($bars) {
  231. $foos = '';
  232. foreach ($bars as $bar) {
  233. $foos .= $bar->getFoo();
  234. }
  235. return $foos;
  236. },
  237. ),
  238. array(new PropertyConstructorDummy('baz', ''), new PropertyConstructorDummy('quux', '')),
  239. array('foo' => '', 'bar' => 'bazquux'),
  240. 'Collect a property',
  241. ),
  242. array(
  243. array(
  244. 'bar' => function ($bars) {
  245. return count($bars);
  246. },
  247. ),
  248. array(new PropertyConstructorDummy('baz', ''), new PropertyConstructorDummy('quux', '')),
  249. array('foo' => '', 'bar' => 2),
  250. 'Count a property',
  251. ),
  252. );
  253. }
  254. /**
  255. * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
  256. */
  257. public function testUnableToNormalizeCircularReference()
  258. {
  259. $serializer = new Serializer(array($this->normalizer));
  260. $this->normalizer->setSerializer($serializer);
  261. $this->normalizer->setCircularReferenceLimit(2);
  262. $obj = new PropertyCircularReferenceDummy();
  263. $this->normalizer->normalize($obj);
  264. }
  265. public function testSiblingReference()
  266. {
  267. $serializer = new Serializer(array($this->normalizer));
  268. $this->normalizer->setSerializer($serializer);
  269. $siblingHolder = new PropertySiblingHolder();
  270. $expected = array(
  271. 'sibling0' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
  272. 'sibling1' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
  273. 'sibling2' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
  274. );
  275. $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder));
  276. }
  277. public function testCircularReferenceHandler()
  278. {
  279. $serializer = new Serializer(array($this->normalizer));
  280. $this->normalizer->setSerializer($serializer);
  281. $this->normalizer->setCircularReferenceHandler(function ($obj) {
  282. return get_class($obj);
  283. });
  284. $obj = new PropertyCircularReferenceDummy();
  285. $expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy');
  286. $this->assertEquals($expected, $this->normalizer->normalize($obj));
  287. }
  288. public function testDenormalizeNonExistingAttribute()
  289. {
  290. $this->assertEquals(
  291. new PropertyDummy(),
  292. $this->normalizer->denormalize(array('non_existing' => true), __NAMESPACE__.'\PropertyDummy')
  293. );
  294. }
  295. public function testDenormalizeShouldIgnoreStaticProperty()
  296. {
  297. $obj = $this->normalizer->denormalize(array('outOfScope' => true), __NAMESPACE__.'\PropertyDummy');
  298. $this->assertEquals(new PropertyDummy(), $obj);
  299. $this->assertEquals('out_of_scope', PropertyDummy::$outOfScope);
  300. }
  301. /**
  302. * @expectedException \Symfony\Component\Serializer\Exception\LogicException
  303. * @expectedExceptionMessage Cannot normalize attribute "bar" because the injected serializer is not a normalizer
  304. */
  305. public function testUnableToNormalizeObjectAttribute()
  306. {
  307. $serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock();
  308. $this->normalizer->setSerializer($serializer);
  309. $obj = new PropertyDummy();
  310. $object = new \stdClass();
  311. $obj->setBar($object);
  312. $this->normalizer->normalize($obj, 'any');
  313. }
  314. public function testNoTraversableSupport()
  315. {
  316. $this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
  317. }
  318. public function testNoStaticPropertySupport()
  319. {
  320. $this->assertFalse($this->normalizer->supportsNormalization(new StaticPropertyDummy()));
  321. }
  322. public function testMaxDepth()
  323. {
  324. $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  325. $this->normalizer = new PropertyNormalizer($classMetadataFactory);
  326. $serializer = new Serializer(array($this->normalizer));
  327. $this->normalizer->setSerializer($serializer);
  328. $level1 = new MaxDepthDummy();
  329. $level1->foo = 'level1';
  330. $level2 = new MaxDepthDummy();
  331. $level2->foo = 'level2';
  332. $level1->child = $level2;
  333. $level3 = new MaxDepthDummy();
  334. $level3->foo = 'level3';
  335. $level2->child = $level3;
  336. $result = $serializer->normalize($level1, null, array(PropertyNormalizer::ENABLE_MAX_DEPTH => true));
  337. $expected = array(
  338. 'foo' => 'level1',
  339. 'child' => array(
  340. 'foo' => 'level2',
  341. 'child' => array(
  342. 'child' => null,
  343. 'bar' => null,
  344. ),
  345. 'bar' => null,
  346. ),
  347. 'bar' => null,
  348. );
  349. $this->assertEquals($expected, $result);
  350. }
  351. }
  352. class PropertyDummy
  353. {
  354. public static $outOfScope = 'out_of_scope';
  355. public $foo;
  356. private $bar;
  357. protected $camelCase;
  358. public function getBar()
  359. {
  360. return $this->bar;
  361. }
  362. public function setBar($bar)
  363. {
  364. $this->bar = $bar;
  365. }
  366. public function getCamelCase()
  367. {
  368. return $this->camelCase;
  369. }
  370. public function setCamelCase($camelCase)
  371. {
  372. $this->camelCase = $camelCase;
  373. }
  374. }
  375. class PropertyConstructorDummy
  376. {
  377. protected $foo;
  378. private $bar;
  379. public function __construct($foo, $bar)
  380. {
  381. $this->foo = $foo;
  382. $this->bar = $bar;
  383. }
  384. public function getFoo()
  385. {
  386. return $this->foo;
  387. }
  388. public function getBar()
  389. {
  390. return $this->bar;
  391. }
  392. }
  393. class PropertyCamelizedDummy
  394. {
  395. private $kevinDunglas;
  396. public $fooBar;
  397. public $bar_foo;
  398. public function __construct($kevinDunglas = null)
  399. {
  400. $this->kevinDunglas = $kevinDunglas;
  401. }
  402. }
  403. class StaticPropertyDummy
  404. {
  405. private static $property = 'value';
  406. }