1111
1212namespace Symfony \Component \Serializer \Normalizer ;
1313
14+ use Symfony \Component \Serializer \Exception \CircularReferenceException ;
1415use Symfony \Component \Serializer \Exception \InvalidArgumentException ;
1516use Symfony \Component \Serializer \Exception \RuntimeException ;
1617
3334 * takes place.
3435 *
3536 * @author Nils Adermann <naderman@naderman.de>
37+ * @author Kévin Dunglas <dunglas@gmail.com>
3638 */
3739class GetSetMethodNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
3840{
41+ protected $ circularReferenceLimit = 1 ;
42+ protected $ circularReferenceHandler ;
3943 protected $ callbacks = array ();
4044 protected $ ignoredAttributes = array ();
4145 protected $ camelizedAttributes = array ();
4246
47+ /**
48+ * Set circular reference limit.
49+ *
50+ * @param $circularReferenceLimit limit of iterations for the same object
51+ *
52+ * @return self
53+ */
54+ public function setCircularReferenceLimit ($ circularReferenceLimit )
55+ {
56+ $ this ->circularReferenceLimit = $ circularReferenceLimit ;
57+
58+ return $ this ;
59+ }
60+
61+ /**
62+ * Set circular reference handler.
63+ *
64+ * @param callable $circularReferenceHandler
65+ *
66+ * @return self
67+ *
68+ * @throws InvalidArgumentException
69+ */
70+ public function setCircularReferenceHandler ($ circularReferenceHandler )
71+ {
72+ if (!is_callable ($ circularReferenceHandler )) {
73+ throw new InvalidArgumentException ('The given circular reference handler is not callable. ' );
74+ }
75+
76+ $ this ->circularReferenceHandler = $ circularReferenceHandler ;
77+
78+ return $ this ;
79+ }
80+
4381 /**
4482 * Set normalization callbacks.
4583 *
@@ -94,6 +132,24 @@ public function setCamelizedAttributes(array $camelizedAttributes)
94132 */
95133 public function normalize ($ object , $ format = null , array $ context = array ())
96134 {
135+ $ objectHash = spl_object_hash ($ object );
136+
137+ if (isset ($ context ['circular_reference_limit ' ][$ objectHash ])) {
138+ if ($ context ['circular_reference_limit ' ][$ objectHash ] >= $ this ->circularReferenceLimit ) {
139+ unset($ context ['circular_reference_limit ' ][$ objectHash ]);
140+
141+ if ($ this ->circularReferenceHandler ) {
142+ return call_user_func ($ this ->circularReferenceHandler , $ object );
143+ }
144+
145+ throw new CircularReferenceException (sprintf ('A circular reference has been detected (configured limit: %d). ' , $ this ->circularReferenceLimit ));
146+ }
147+
148+ $ context ['circular_reference_limit ' ][$ objectHash ]++;
149+ } else {
150+ $ context ['circular_reference_limit ' ][$ objectHash ] = 1 ;
151+ }
152+
97153 $ reflectionObject = new \ReflectionObject ($ object );
98154 $ reflectionMethods = $ reflectionObject ->getMethods (\ReflectionMethod::IS_PUBLIC );
99155
@@ -114,7 +170,8 @@ public function normalize($object, $format = null, array $context = array())
114170 if (!$ this ->serializer instanceof NormalizerInterface) {
115171 throw new \LogicException (sprintf ('Cannot normalize attribute "%s" because injected serializer is not a normalizer ' , $ attributeName ));
116172 }
117- $ attributeValue = $ this ->serializer ->normalize ($ attributeValue , $ format );
173+
174+ $ attributeValue = $ this ->serializer ->normalize ($ attributeValue , $ format , $ context );
118175 }
119176
120177 $ attributes [$ attributeName ] = $ attributeValue ;
0 commit comments