GraphNavigator.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. /*
  3. * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace JMS\SerializerBundle\Serializer;
  18. use JMS\SerializerBundle\EventDispatcher\Event;
  19. use JMS\SerializerBundle\EventDispatcher\EventDispatcherInterface;
  20. use JMS\SerializerBundle\Metadata\ClassMetadata;
  21. use Metadata\MetadataFactoryInterface;
  22. use JMS\SerializerBundle\Exception\InvalidArgumentException;
  23. use JMS\SerializerBundle\Serializer\Exclusion\ExclusionStrategyInterface;
  24. final class GraphNavigator
  25. {
  26. const DIRECTION_SERIALIZATION = 1;
  27. const DIRECTION_DESERIALIZATION = 2;
  28. private $direction;
  29. private $dispatcher;
  30. private $metadataFactory;
  31. private $format;
  32. private $exclusionStrategy;
  33. private $visiting;
  34. public function __construct($direction, MetadataFactoryInterface $metadataFactory, $format, ExclusionStrategyInterface $exclusionStrategy = null, EventDispatcherInterface $dispatcher = null)
  35. {
  36. $this->direction = $direction;
  37. $this->dispatcher = $dispatcher;
  38. $this->metadataFactory = $metadataFactory;
  39. $this->format = $format;
  40. $this->exclusionStrategy = $exclusionStrategy;
  41. $this->visiting = new \SplObjectStorage();
  42. }
  43. public function accept($data, $type, VisitorInterface $visitor)
  44. {
  45. // determine type if not given
  46. if (null === $type) {
  47. if (null === $data) {
  48. return null;
  49. }
  50. $type = gettype($data);
  51. if ('object' === $type) {
  52. $type = get_class($data);
  53. }
  54. }
  55. if ('string' === $type) {
  56. return $visitor->visitString($data, $type);
  57. } else if ('integer' === $type) {
  58. return $visitor->visitInteger($data, $type);
  59. } else if ('boolean' === $type) {
  60. return $visitor->visitBoolean($data, $type);
  61. } else if ('double' === $type) {
  62. return $visitor->visitDouble($data, $type);
  63. } else if ('array' === $type || ('a' === $type[0] && 0 === strpos($type, 'array<'))) {
  64. return $visitor->visitArray($data, $type);
  65. } else if ('resource' === $type) {
  66. $msg = 'Resources are not supported in serialized data.';
  67. if (null !== $path = $this->getCurrentPath()) {
  68. $msg .= ' Path: '.implode(' -> ', $path);
  69. }
  70. throw new \RuntimeException($msg);
  71. } else {
  72. if (self::DIRECTION_SERIALIZATION === $this->direction && null !== $data) {
  73. if ($this->visiting->contains($data)) {
  74. return null;
  75. }
  76. $this->visiting->attach($data);
  77. }
  78. // try custom handler
  79. $handled = false;
  80. $rs = $visitor->visitUsingCustomHandler($data, $type, $handled);
  81. if ($handled) {
  82. if (self::DIRECTION_SERIALIZATION === $this->direction) {
  83. $this->visiting->detach($data);
  84. }
  85. return $rs;
  86. }
  87. $metadata = $this->metadataFactory->getMetadataForClass($type);
  88. if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, self::DIRECTION_SERIALIZATION === $this->direction ? $data : null)) {
  89. if (self::DIRECTION_SERIALIZATION === $this->direction) {
  90. $this->visiting->detach($data);
  91. }
  92. return null;
  93. }
  94. // pre-serialization callbacks
  95. if (self::DIRECTION_SERIALIZATION === $this->direction) {
  96. foreach ($metadata->preSerializeMethods as $method) {
  97. $method->invoke($data);
  98. }
  99. if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.pre_serialize', $type, $this->format)) {
  100. $this->dispatcher->dispatch('serializer.pre_serialize', $type, $this->format, new Event($visitor, $data, $metadata));
  101. }
  102. }
  103. // check if traversable
  104. if (self::DIRECTION_SERIALIZATION === $this->direction && $data instanceof \Traversable) {
  105. $rs = $visitor->visitTraversable($data, $type);
  106. $this->afterVisitingObject($visitor, $metadata, $data, self::DIRECTION_SERIALIZATION === $this->direction);
  107. return $rs;
  108. }
  109. $visitor->startVisitingObject($metadata, $data, $type);
  110. foreach ($metadata->propertyMetadata as $propertyMetadata) {
  111. if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, self::DIRECTION_SERIALIZATION === $this->direction ? $data : null)) {
  112. continue;
  113. }
  114. if (self::DIRECTION_DESERIALIZATION === $this->direction && $propertyMetadata->readOnly) {
  115. continue;
  116. }
  117. // try custom handler
  118. if (!$visitor->visitPropertyUsingCustomHandler($propertyMetadata, $data)) {
  119. $visitor->visitProperty($propertyMetadata, $data);
  120. }
  121. }
  122. if (self::DIRECTION_SERIALIZATION === $this->direction) {
  123. $this->afterVisitingObject($visitor, $metadata, $data);
  124. return $visitor->endVisitingObject($metadata, $data, $type);
  125. }
  126. $rs = $visitor->endVisitingObject($metadata, $data, $type);
  127. $this->afterVisitingObject($visitor, $metadata, $rs);
  128. return $rs;
  129. }
  130. }
  131. public function detachObject($object)
  132. {
  133. if (null === $object) {
  134. throw new InvalidArgumentException('$object cannot be null');
  135. } else if (!is_object($object)) {
  136. throw new InvalidArgumentException(sprintf('Expected an object to detach, given "%s".', gettype($object)));
  137. }
  138. $this->visiting->detach($object);
  139. }
  140. private function getCurrentPath()
  141. {
  142. $path = array();
  143. foreach ($this->visiting as $obj) {
  144. $path[] = get_class($obj);
  145. }
  146. if ( ! $path) {
  147. return null;
  148. }
  149. return implode(' -> ', $path);
  150. }
  151. private function afterVisitingObject(VisitorInterface $visitor, ClassMetadata $metadata, $object)
  152. {
  153. if (self::DIRECTION_SERIALIZATION === $this->direction) {
  154. $this->visiting->detach($object);
  155. foreach ($metadata->postSerializeMethods as $method) {
  156. $method->invoke($object);
  157. }
  158. if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.post_serialize', $metadata->name, $this->format)) {
  159. $this->dispatcher->dispatch('serializer.post_serialize', $metadata->name, $this->format, new Event($visitor, $object, $metadata));
  160. }
  161. return;
  162. }
  163. foreach ($metadata->postDeserializeMethods as $method) {
  164. $method->invoke($object);
  165. }
  166. if (null !== $this->dispatcher && $this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) {
  167. $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new Event($visitor, $object, $metadata));
  168. }
  169. }
  170. }