1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer; 6 7use JMS\Serializer\ContextFactory\DefaultDeserializationContextFactory; 8use JMS\Serializer\ContextFactory\DefaultSerializationContextFactory; 9use JMS\Serializer\ContextFactory\DeserializationContextFactoryInterface; 10use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface; 11use JMS\Serializer\Exception\InvalidArgumentException; 12use JMS\Serializer\Exception\RuntimeException; 13use JMS\Serializer\Exception\UnsupportedFormatException; 14use JMS\Serializer\GraphNavigator\Factory\GraphNavigatorFactoryInterface; 15use JMS\Serializer\Type\Parser; 16use JMS\Serializer\Type\ParserInterface; 17use JMS\Serializer\Visitor\Factory\DeserializationVisitorFactory; 18use JMS\Serializer\Visitor\Factory\SerializationVisitorFactory; 19use Metadata\MetadataFactoryInterface; 20 21/** 22 * Serializer Implementation. 23 * 24 * @author Johannes M. Schmitt <schmittjoh@gmail.com> 25 */ 26final class Serializer implements SerializerInterface, ArrayTransformerInterface 27{ 28 /** 29 * @var MetadataFactoryInterface 30 */ 31 private $factory; 32 33 /** 34 * @var TypeParser 35 */ 36 private $typeParser; 37 38 /** 39 * @var SerializationVisitorFactory[] 40 */ 41 private $serializationVisitors = []; 42 43 /** 44 * @var DeserializationVisitorFactory[] 45 */ 46 private $deserializationVisitors = []; 47 48 /** 49 * @var SerializationContextFactoryInterface 50 */ 51 private $serializationContextFactory; 52 53 /** 54 * @var DeserializationContextFactoryInterface 55 */ 56 private $deserializationContextFactory; 57 58 /** 59 * @var GraphNavigatorFactoryInterface[] 60 */ 61 private $graphNavigators; 62 63 /** 64 * @param GraphNavigatorFactoryInterface[] $graphNavigators 65 * @param SerializationVisitorFactory[] $serializationVisitors 66 * @param DeserializationVisitorFactory[] $deserializationVisitors 67 */ 68 public function __construct( 69 MetadataFactoryInterface $factory, 70 array $graphNavigators, 71 array $serializationVisitors, 72 array $deserializationVisitors, 73 ?SerializationContextFactoryInterface $serializationContextFactory = null, 74 ?DeserializationContextFactoryInterface $deserializationContextFactory = null, 75 ?ParserInterface $typeParser = null 76 ) { 77 $this->factory = $factory; 78 $this->graphNavigators = $graphNavigators; 79 $this->serializationVisitors = $serializationVisitors; 80 $this->deserializationVisitors = $deserializationVisitors; 81 82 $this->typeParser = $typeParser ?? new Parser(); 83 84 $this->serializationContextFactory = $serializationContextFactory ?: new DefaultSerializationContextFactory(); 85 $this->deserializationContextFactory = $deserializationContextFactory ?: new DefaultDeserializationContextFactory(); 86 } 87 88 /** 89 * Parses a direction string to one of the direction constants. 90 */ 91 public static function parseDirection(string $dirStr): int 92 { 93 switch (strtolower($dirStr)) { 94 case 'serialization': 95 return GraphNavigatorInterface::DIRECTION_SERIALIZATION; 96 97 case 'deserialization': 98 return GraphNavigatorInterface::DIRECTION_DESERIALIZATION; 99 100 default: 101 throw new InvalidArgumentException(sprintf('The direction "%s" does not exist.', $dirStr)); 102 } 103 } 104 105 private function findInitialType(?string $type, SerializationContext $context): ?string 106 { 107 if (null !== $type) { 108 return $type; 109 } elseif ($context->hasAttribute('initial_type')) { 110 return $context->getAttribute('initial_type'); 111 } 112 return null; 113 } 114 115 private function getNavigator(int $direction): GraphNavigatorInterface 116 { 117 if (!isset($this->graphNavigators[$direction])) { 118 throw new RuntimeException( 119 sprintf( 120 'Can not find a graph navigator for the direction "%s".', 121 GraphNavigatorInterface::DIRECTION_SERIALIZATION === $direction ? 'serialization' : 'deserialization' 122 ) 123 ); 124 } 125 126 return $this->graphNavigators[$direction]->getGraphNavigator(); 127 } 128 129 private function getVisitor(int $direction, string $format): VisitorInterface 130 { 131 $factories = GraphNavigatorInterface::DIRECTION_SERIALIZATION === $direction 132 ? $this->serializationVisitors 133 : $this->deserializationVisitors; 134 135 if (!isset($factories[$format])) { 136 throw new UnsupportedFormatException( 137 sprintf( 138 'The format "%s" is not supported for %s.', 139 $format, 140 GraphNavigatorInterface::DIRECTION_SERIALIZATION === $direction ? 'serialization' : 'deserialization' 141 ) 142 ); 143 } 144 145 return $factories[$format]->getVisitor(); 146 } 147 148 /** 149 * {@InheritDoc} 150 */ 151 public function serialize($data, string $format, ?SerializationContext $context = null, ?string $type = null): string 152 { 153 if (null === $context) { 154 $context = $this->serializationContextFactory->createSerializationContext(); 155 } 156 157 $visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_SERIALIZATION, $format); 158 $navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_SERIALIZATION); 159 160 $type = $this->findInitialType($type, $context); 161 162 $result = $this->visit($navigator, $visitor, $context, $data, $format, $type); 163 return $visitor->getResult($result); 164 } 165 166 /** 167 * {@InheritDoc} 168 */ 169 public function deserialize(string $data, string $type, string $format, ?DeserializationContext $context = null) 170 { 171 if (null === $context) { 172 $context = $this->deserializationContextFactory->createDeserializationContext(); 173 } 174 175 $visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $format); 176 $navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_DESERIALIZATION); 177 178 $result = $this->visit($navigator, $visitor, $context, $data, $format, $type); 179 180 return $visitor->getResult($result); 181 } 182 183 /** 184 * {@InheritDoc} 185 */ 186 public function toArray($data, ?SerializationContext $context = null, ?string $type = null): array 187 { 188 if (null === $context) { 189 $context = $this->serializationContextFactory->createSerializationContext(); 190 } 191 192 $visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_SERIALIZATION, 'json'); 193 $navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_SERIALIZATION); 194 195 $type = $this->findInitialType($type, $context); 196 $result = $this->visit($navigator, $visitor, $context, $data, 'json', $type); 197 $result = $this->convertArrayObjects($result); 198 199 if (!\is_array($result)) { 200 throw new RuntimeException(sprintf( 201 'The input data of type "%s" did not convert to an array, but got a result of type "%s".', 202 \is_object($data) ? \get_class($data) : \gettype($data), 203 \is_object($result) ? \get_class($result) : \gettype($result) 204 )); 205 } 206 207 return $result; 208 } 209 210 /** 211 * {@InheritDoc} 212 */ 213 public function fromArray(array $data, string $type, ?DeserializationContext $context = null) 214 { 215 if (null === $context) { 216 $context = $this->deserializationContextFactory->createDeserializationContext(); 217 } 218 219 $visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'json'); 220 $navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_DESERIALIZATION); 221 222 return $this->visit($navigator, $visitor, $context, $data, 'json', $type, false); 223 } 224 225 /** 226 * @param mixed $data 227 * 228 * @return mixed 229 */ 230 private function visit(GraphNavigatorInterface $navigator, VisitorInterface $visitor, Context $context, $data, string $format, ?string $type = null, bool $prepare = true) 231 { 232 $context->initialize( 233 $format, 234 $visitor, 235 $navigator, 236 $this->factory 237 ); 238 239 $visitor->setNavigator($navigator); 240 $navigator->initialize($visitor, $context); 241 242 if ($prepare) { 243 $data = $visitor->prepare($data); 244 } 245 246 if (null !== $type) { 247 $type = $this->typeParser->parse($type); 248 } 249 return $navigator->accept($data, $type); 250 } 251 252 /** 253 * @param mixed $data 254 * 255 * @return mixed 256 */ 257 private function convertArrayObjects($data) 258 { 259 if ($data instanceof \ArrayObject || $data instanceof \stdClass) { 260 $data = (array) $data; 261 } 262 if (\is_array($data)) { 263 foreach ($data as $k => $v) { 264 $data[$k] = $this->convertArrayObjects($v); 265 } 266 } 267 268 return $data; 269 } 270} 271