1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer\Metadata\Driver; 6 7use Doctrine\Common\Annotations\Reader; 8use JMS\Serializer\Annotation\Accessor; 9use JMS\Serializer\Annotation\AccessorOrder; 10use JMS\Serializer\Annotation\AccessType; 11use JMS\Serializer\Annotation\Discriminator; 12use JMS\Serializer\Annotation\Exclude; 13use JMS\Serializer\Annotation\ExclusionPolicy; 14use JMS\Serializer\Annotation\Expose; 15use JMS\Serializer\Annotation\Groups; 16use JMS\Serializer\Annotation\Inline; 17use JMS\Serializer\Annotation\MaxDepth; 18use JMS\Serializer\Annotation\PostDeserialize; 19use JMS\Serializer\Annotation\PostSerialize; 20use JMS\Serializer\Annotation\PreSerialize; 21use JMS\Serializer\Annotation\ReadOnly; 22use JMS\Serializer\Annotation\SerializedName; 23use JMS\Serializer\Annotation\Since; 24use JMS\Serializer\Annotation\SkipWhenEmpty; 25use JMS\Serializer\Annotation\Type; 26use JMS\Serializer\Annotation\Until; 27use JMS\Serializer\Annotation\VirtualProperty; 28use JMS\Serializer\Annotation\XmlAttribute; 29use JMS\Serializer\Annotation\XmlAttributeMap; 30use JMS\Serializer\Annotation\XmlDiscriminator; 31use JMS\Serializer\Annotation\XmlElement; 32use JMS\Serializer\Annotation\XmlKeyValuePairs; 33use JMS\Serializer\Annotation\XmlList; 34use JMS\Serializer\Annotation\XmlMap; 35use JMS\Serializer\Annotation\XmlNamespace; 36use JMS\Serializer\Annotation\XmlRoot; 37use JMS\Serializer\Annotation\XmlValue; 38use JMS\Serializer\Exception\InvalidMetadataException; 39use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface; 40use JMS\Serializer\Metadata\ClassMetadata; 41use JMS\Serializer\Metadata\ExpressionPropertyMetadata; 42use JMS\Serializer\Metadata\PropertyMetadata; 43use JMS\Serializer\Metadata\VirtualPropertyMetadata; 44use JMS\Serializer\Naming\PropertyNamingStrategyInterface; 45use JMS\Serializer\Type\Parser; 46use JMS\Serializer\Type\ParserInterface; 47use Metadata\ClassMetadata as BaseClassMetadata; 48use Metadata\Driver\DriverInterface; 49use Metadata\MethodMetadata; 50 51class AnnotationDriver implements DriverInterface 52{ 53 use ExpressionMetadataTrait; 54 55 /** 56 * @var Reader 57 */ 58 private $reader; 59 60 /** 61 * @var ParserInterface 62 */ 63 private $typeParser; 64 /** 65 * @var PropertyNamingStrategyInterface 66 */ 67 private $namingStrategy; 68 69 public function __construct(Reader $reader, PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null) 70 { 71 $this->reader = $reader; 72 $this->typeParser = $typeParser ?: new Parser(); 73 $this->namingStrategy = $namingStrategy; 74 $this->expressionEvaluator = $expressionEvaluator; 75 } 76 77 public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata 78 { 79 $classMetadata = new ClassMetadata($name = $class->name); 80 $fileResource = $class->getFilename(); 81 if (false !== $fileResource) { 82 $classMetadata->fileResources[] = $fileResource; 83 } 84 85 $propertiesMetadata = []; 86 $propertiesAnnotations = []; 87 88 $exclusionPolicy = 'NONE'; 89 $excludeAll = false; 90 $classAccessType = PropertyMetadata::ACCESS_TYPE_PROPERTY; 91 $readOnlyClass = false; 92 foreach ($this->reader->getClassAnnotations($class) as $annot) { 93 if ($annot instanceof ExclusionPolicy) { 94 $exclusionPolicy = $annot->policy; 95 } elseif ($annot instanceof XmlRoot) { 96 $classMetadata->xmlRootName = $annot->name; 97 $classMetadata->xmlRootNamespace = $annot->namespace; 98 $classMetadata->xmlRootPrefix = $annot->prefix; 99 } elseif ($annot instanceof XmlNamespace) { 100 $classMetadata->registerNamespace($annot->uri, $annot->prefix); 101 } elseif ($annot instanceof Exclude) { 102 $excludeAll = true; 103 } elseif ($annot instanceof AccessType) { 104 $classAccessType = $annot->type; 105 } elseif ($annot instanceof ReadOnly) { 106 $readOnlyClass = true; 107 } elseif ($annot instanceof AccessorOrder) { 108 $classMetadata->setAccessorOrder($annot->order, $annot->custom); 109 } elseif ($annot instanceof Discriminator) { 110 if ($annot->disabled) { 111 $classMetadata->discriminatorDisabled = true; 112 } else { 113 $classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups); 114 } 115 } elseif ($annot instanceof XmlDiscriminator) { 116 $classMetadata->xmlDiscriminatorAttribute = (bool) $annot->attribute; 117 $classMetadata->xmlDiscriminatorCData = (bool) $annot->cdata; 118 $classMetadata->xmlDiscriminatorNamespace = $annot->namespace ? (string) $annot->namespace : null; 119 } elseif ($annot instanceof VirtualProperty) { 120 $virtualPropertyMetadata = new ExpressionPropertyMetadata( 121 $name, 122 $annot->name, 123 $this->parseExpression($annot->exp) 124 ); 125 $propertiesMetadata[] = $virtualPropertyMetadata; 126 $propertiesAnnotations[] = $annot->options; 127 } 128 } 129 130 foreach ($class->getMethods() as $method) { 131 if ($method->class !== $name) { 132 continue; 133 } 134 135 $methodAnnotations = $this->reader->getMethodAnnotations($method); 136 137 foreach ($methodAnnotations as $annot) { 138 if ($annot instanceof PreSerialize) { 139 $classMetadata->addPreSerializeMethod(new MethodMetadata($name, $method->name)); 140 continue 2; 141 } elseif ($annot instanceof PostDeserialize) { 142 $classMetadata->addPostDeserializeMethod(new MethodMetadata($name, $method->name)); 143 continue 2; 144 } elseif ($annot instanceof PostSerialize) { 145 $classMetadata->addPostSerializeMethod(new MethodMetadata($name, $method->name)); 146 continue 2; 147 } elseif ($annot instanceof VirtualProperty) { 148 $virtualPropertyMetadata = new VirtualPropertyMetadata($name, $method->name); 149 $propertiesMetadata[] = $virtualPropertyMetadata; 150 $propertiesAnnotations[] = $methodAnnotations; 151 continue 2; 152 } 153 } 154 } 155 156 if (!$excludeAll) { 157 foreach ($class->getProperties() as $property) { 158 if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) { 159 continue; 160 } 161 $propertiesMetadata[] = new PropertyMetadata($name, $property->getName()); 162 $propertiesAnnotations[] = $this->reader->getPropertyAnnotations($property); 163 } 164 165 foreach ($propertiesMetadata as $propertyKey => $propertyMetadata) { 166 $isExclude = false; 167 $isExpose = $propertyMetadata instanceof VirtualPropertyMetadata 168 || $propertyMetadata instanceof ExpressionPropertyMetadata; 169 $propertyMetadata->readOnly = $propertyMetadata->readOnly || $readOnlyClass; 170 $accessType = $classAccessType; 171 $accessor = [null, null]; 172 173 $propertyAnnotations = $propertiesAnnotations[$propertyKey]; 174 175 foreach ($propertyAnnotations as $annot) { 176 if ($annot instanceof Since) { 177 $propertyMetadata->sinceVersion = $annot->version; 178 } elseif ($annot instanceof Until) { 179 $propertyMetadata->untilVersion = $annot->version; 180 } elseif ($annot instanceof SerializedName) { 181 $propertyMetadata->serializedName = $annot->name; 182 } elseif ($annot instanceof SkipWhenEmpty) { 183 $propertyMetadata->skipWhenEmpty = true; 184 } elseif ($annot instanceof Expose) { 185 $isExpose = true; 186 if (null !== $annot->if) { 187 $propertyMetadata->excludeIf = $this->parseExpression('!(' . $annot->if . ')'); 188 } 189 } elseif ($annot instanceof Exclude) { 190 if (null !== $annot->if) { 191 $propertyMetadata->excludeIf = $this->parseExpression($annot->if); 192 } else { 193 $isExclude = true; 194 } 195 } elseif ($annot instanceof Type) { 196 $propertyMetadata->setType($this->typeParser->parse($annot->name)); 197 } elseif ($annot instanceof XmlElement) { 198 $propertyMetadata->xmlAttribute = false; 199 $propertyMetadata->xmlElementCData = $annot->cdata; 200 $propertyMetadata->xmlNamespace = $annot->namespace; 201 } elseif ($annot instanceof XmlList) { 202 $propertyMetadata->xmlCollection = true; 203 $propertyMetadata->xmlCollectionInline = $annot->inline; 204 $propertyMetadata->xmlEntryName = $annot->entry; 205 $propertyMetadata->xmlEntryNamespace = $annot->namespace; 206 $propertyMetadata->xmlCollectionSkipWhenEmpty = $annot->skipWhenEmpty; 207 } elseif ($annot instanceof XmlMap) { 208 $propertyMetadata->xmlCollection = true; 209 $propertyMetadata->xmlCollectionInline = $annot->inline; 210 $propertyMetadata->xmlEntryName = $annot->entry; 211 $propertyMetadata->xmlEntryNamespace = $annot->namespace; 212 $propertyMetadata->xmlKeyAttribute = $annot->keyAttribute; 213 } elseif ($annot instanceof XmlKeyValuePairs) { 214 $propertyMetadata->xmlKeyValuePairs = true; 215 } elseif ($annot instanceof XmlAttribute) { 216 $propertyMetadata->xmlAttribute = true; 217 $propertyMetadata->xmlNamespace = $annot->namespace; 218 } elseif ($annot instanceof XmlValue) { 219 $propertyMetadata->xmlValue = true; 220 $propertyMetadata->xmlElementCData = $annot->cdata; 221 } elseif ($annot instanceof XmlElement) { 222 $propertyMetadata->xmlElementCData = $annot->cdata; 223 } elseif ($annot instanceof AccessType) { 224 $accessType = $annot->type; 225 } elseif ($annot instanceof ReadOnly) { 226 $propertyMetadata->readOnly = $annot->readOnly; 227 } elseif ($annot instanceof Accessor) { 228 $accessor = [$annot->getter, $annot->setter]; 229 } elseif ($annot instanceof Groups) { 230 $propertyMetadata->groups = $annot->groups; 231 foreach ((array) $propertyMetadata->groups as $groupName) { 232 if (false !== strpos($groupName, ',')) { 233 throw new InvalidMetadataException(sprintf( 234 'Invalid group name "%s" on "%s", did you mean to create multiple groups?', 235 implode(', ', $propertyMetadata->groups), 236 $propertyMetadata->class . '->' . $propertyMetadata->name 237 )); 238 } 239 } 240 } elseif ($annot instanceof Inline) { 241 $propertyMetadata->inline = true; 242 } elseif ($annot instanceof XmlAttributeMap) { 243 $propertyMetadata->xmlAttributeMap = true; 244 } elseif ($annot instanceof MaxDepth) { 245 $propertyMetadata->maxDepth = $annot->depth; 246 } 247 } 248 249 if ($propertyMetadata->inline) { 250 $classMetadata->isList = $classMetadata->isList || PropertyMetadata::isCollectionList($propertyMetadata->type); 251 $classMetadata->isMap = $classMetadata->isMap || PropertyMetadata::isCollectionMap($propertyMetadata->type); 252 253 if ($classMetadata->isMap && $classMetadata->isList) { 254 throw new InvalidMetadataException('Can not have an inline map and and inline map on the same class'); 255 } 256 } 257 258 if (!$propertyMetadata->serializedName) { 259 $propertyMetadata->serializedName = $this->namingStrategy->translateName($propertyMetadata); 260 } 261 262 foreach ($propertyAnnotations as $annot) { 263 if ($annot instanceof VirtualProperty && null !== $annot->name) { 264 $propertyMetadata->name = $annot->name; 265 } 266 } 267 268 if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude) 269 || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose) 270 ) { 271 $propertyMetadata->setAccessor($accessType, $accessor[0], $accessor[1]); 272 $classMetadata->addPropertyMetadata($propertyMetadata); 273 } 274 } 275 } 276 277 return $classMetadata; 278 } 279} 280