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