Completed
Pull Request — master (#884)
by Robert
13:50
created

AnnotationDriver::findAccessors()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 19
c 0
b 0
f 0
rs 9.4285
ccs 0
cts 0
cp 0
cc 3
eloc 11
nc 4
nop 4
crap 12
1
<?php
2
3
/*
4
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer\Metadata\Driver;
20
21
use Doctrine\Common\Annotations\Reader;
22
use JMS\Serializer\Accessor\Guess\AccessorFinderInterface;
23
use JMS\Serializer\Annotation\Accessor;
24
use JMS\Serializer\Annotation\AccessorOrder;
25
use JMS\Serializer\Annotation\AccessType;
26
use JMS\Serializer\Annotation\Discriminator;
27
use JMS\Serializer\Annotation\Exclude;
28
use JMS\Serializer\Annotation\ExcludeIf;
29
use JMS\Serializer\Annotation\ExclusionPolicy;
30
use JMS\Serializer\Annotation\Expose;
31
use JMS\Serializer\Annotation\Groups;
32
use JMS\Serializer\Annotation\AccessorFinder;
33
use JMS\Serializer\Annotation\HandlerCallback;
34
use JMS\Serializer\Annotation\Inline;
35
use JMS\Serializer\Annotation\MaxDepth;
36
use JMS\Serializer\Annotation\PostDeserialize;
37
use JMS\Serializer\Annotation\PostSerialize;
38
use JMS\Serializer\Annotation\PreSerialize;
39
use JMS\Serializer\Annotation\ReadOnly;
40
use JMS\Serializer\Annotation\SerializedName;
41
use JMS\Serializer\Annotation\Since;
42
use JMS\Serializer\Annotation\SkipWhenEmpty;
43
use JMS\Serializer\Annotation\Type;
44
use JMS\Serializer\Annotation\Until;
45
use JMS\Serializer\Annotation\VirtualProperty;
46
use JMS\Serializer\Annotation\XmlAttribute;
47
use JMS\Serializer\Annotation\XmlAttributeMap;
48
use JMS\Serializer\Annotation\XmlDiscriminator;
49
use JMS\Serializer\Annotation\XmlElement;
50
use JMS\Serializer\Annotation\XmlKeyValuePairs;
51
use JMS\Serializer\Annotation\XmlList;
52
use JMS\Serializer\Annotation\XmlMap;
53
use JMS\Serializer\Annotation\XmlNamespace;
54
use JMS\Serializer\Annotation\XmlRoot;
55
use JMS\Serializer\Annotation\XmlValue;
56
use JMS\Serializer\Exception\InvalidArgumentException;
57
use JMS\Serializer\Exception\LogicException;
58
use JMS\Serializer\GraphNavigator;
59
use JMS\Serializer\Metadata\ClassMetadata;
60
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
61
use JMS\Serializer\Metadata\PropertyMetadata;
62
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
63
use Metadata\Driver\DriverInterface;
64
use Metadata\MethodMetadata;
65
66
class AnnotationDriver implements DriverInterface
67 440
{
68
    private $reader;
69 440
70 440
    public function __construct(Reader $reader)
71
    {
72 273
        $this->reader = $reader;
73
    }
74 273
75 273
    public function loadMetadataForClass(\ReflectionClass $class)
76
    {
77 273
        $classMetadata = new ClassMetadata($name = $class->name);
78 273
        $classMetadata->fileResources[] = $class->getFilename();
79
80 273
        $propertiesMetadata = array();
81 273
        $propertiesAnnotations = array();
82 273
83 273
        $exclusionPolicy = 'NONE';
84 273
        $excludeAll = false;
85 155
        $classAccessType = PropertyMetadata::ACCESS_TYPE_PROPERTY;
86 35
        $readOnlyClass = false;
87 154
        $accessorFinder = null;
88 68
        foreach ($this->reader->getClassAnnotations($class) as $annot) {
89 68
            if ($annot instanceof ExclusionPolicy) {
90 119
                $exclusionPolicy = $annot->policy;
91 24
            } elseif ($annot instanceof AccessorFinder) {
92 97
                $accessorFinder = $annot->class;
93
                $accessorFinder = new $accessorFinder();
94 97
                if (!$accessorFinder instanceof AccessorFinderInterface) {
95 5
                    throw new LogicException('Wrong accessor finder type for class: ' . $class->name);
96 94
                }
97 5
            } elseif ($annot instanceof XmlRoot) {
98 89
                $classMetadata->xmlRootName = $annot->name;
99 46
                $classMetadata->xmlRootNamespace = $annot->namespace;
100 43
            } elseif ($annot instanceof XmlNamespace) {
101 21
                $classMetadata->registerNamespace($annot->uri, $annot->prefix);
102
            } elseif ($annot instanceof Exclude) {
103
                $excludeAll = true;
104 21
            } elseif ($annot instanceof AccessType) {
105
                $classAccessType = $annot->type;
106 27
            } elseif ($annot instanceof ReadOnly) {
107 5
                $readOnlyClass = true;
108 5
            } elseif ($annot instanceof AccessorOrder) {
109 5
                $classMetadata->setAccessorOrder($annot->order, $annot->custom);
110 22
            } elseif ($annot instanceof Discriminator) {
111 8
                if ($annot->disabled) {
112 8
                    $classMetadata->discriminatorDisabled = true;
113 155
                } else {
114
                    $classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups);
115
                }
116
            } elseif ($annot instanceof XmlDiscriminator) {
117 273
                $classMetadata->xmlDiscriminatorAttribute = (bool)$annot->attribute;
118 214
                $classMetadata->xmlDiscriminatorCData = (bool)$annot->cdata;
119 18
                $classMetadata->xmlDiscriminatorNamespace = $annot->namespace ? (string)$annot->namespace : null;
120
            } elseif ($annot instanceof VirtualProperty) {
121
                $virtualPropertyMetadata = new ExpressionPropertyMetadata($name, $annot->name, $annot->exp);
122 213
                $propertiesMetadata[] = $virtualPropertyMetadata;
123
                $propertiesAnnotations[] = $annot->options;
124 213
            }
125 51
        }
126 3
127 3
        foreach ($class->getMethods() as $method) {
128 51
            if ($method->class !== $name) {
129 6
                continue;
130 6
            }
131 48
132 3
            $methodAnnotations = $this->reader->getMethodAnnotations($method);
133 3
134 45
            foreach ($methodAnnotations as $annot) {
135 41
                if ($annot instanceof PreSerialize) {
136 41
                    $classMetadata->addPreSerializeMethod(new MethodMetadata($name, $method->name));
137 41
                    continue 2;
138 41
                } elseif ($annot instanceof PostDeserialize) {
139 7
                    $classMetadata->addPostDeserializeMethod(new MethodMetadata($name, $method->name));
140 4
                    continue 2;
141 189
                } elseif ($annot instanceof PostSerialize) {
142
                    $classMetadata->addPostSerializeMethod(new MethodMetadata($name, $method->name));
143
                    continue 2;
144
                } elseif ($annot instanceof VirtualProperty) {
145
                    $virtualPropertyMetadata = new VirtualPropertyMetadata($name, $method->name);
146 273
                    $propertiesMetadata[] = $virtualPropertyMetadata;
147 273
                    $propertiesAnnotations[] = $methodAnnotations;
148 252
                    continue 2;
149 20
                } elseif ($annot instanceof HandlerCallback) {
150
                    $classMetadata->addHandlerCallback(GraphNavigator::parseDirection($annot->direction), $annot->format, $method->name);
151 251
                    continue 2;
152 251
                }
153
            }
154
        }
155 273
156 260
        if (!$excludeAll) {
157 260
            foreach ($class->getProperties() as $property) {
158 260
                if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) {
0 ignored issues
show
Bug introduced by
The property info does not seem to exist in ReflectionProperty.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
159 260
                    continue;
160 260
                }
161 260
                $propertiesMetadata[] = new PropertyMetadata($name, $property->getName());
162
                $propertiesAnnotations[] = $this->reader->getPropertyAnnotations($property);
163 260
            }
164
165 260
            foreach ($propertiesMetadata as $propertyKey => $propertyMetadata) {
166 252
                $isExclude = false;
167 7
                $isExpose = $propertyMetadata instanceof VirtualPropertyMetadata
168 252
                    || $propertyMetadata instanceof ExpressionPropertyMetadata;
169 7
                $propertyMetadata->readOnly = $propertyMetadata->readOnly || $readOnlyClass;
170 252
                $accessType = $classAccessType;
171 86
                $accessor = array(null, null);
172 252
173 7
                $propertyAnnotations = $propertiesAnnotations[$propertyKey];
174 251
175 37
                foreach ($propertyAnnotations as $annot) {
176 37
                    if ($annot instanceof Since) {
177 37
                        $propertyMetadata->sinceVersion = $annot->version;
178
                    } elseif ($annot instanceof Until) {
179 251
                        $propertyMetadata->untilVersion = $annot->version;
180 43
                    } elseif ($annot instanceof SerializedName) {
181 21
                        $propertyMetadata->serializedName = $annot->name;
182
                    } elseif ($annot instanceof SkipWhenEmpty) {
183 43
                        $propertyMetadata->skipWhenEmpty = true;
184
                    } elseif ($annot instanceof Expose) {
185 249
                        $isExpose = true;
186 207
                        if (null !== $annot->if) {
187 166
                            $propertyMetadata->excludeIf = "!(" . $annot->if . ")";
188 21
                        }
189 21
                    } elseif ($annot instanceof Exclude) {
190 21
                        if (null !== $annot->if) {
191 160
                            $propertyMetadata->excludeIf = $annot->if;
192 45
                        } else {
193 45
                            $isExclude = true;
194 45
                        }
195 45
                    } elseif ($annot instanceof Type) {
196 45
                        $propertyMetadata->setType($annot->name);
197 152
                    } elseif ($annot instanceof XmlElement) {
198 23
                        $propertyMetadata->xmlAttribute = false;
199 23
                        $propertyMetadata->xmlElementCData = $annot->cdata;
200 23
                        $propertyMetadata->xmlNamespace = $annot->namespace;
201 23
                    } elseif ($annot instanceof XmlList) {
202 23
                        $propertyMetadata->xmlCollection = true;
203 144
                        $propertyMetadata->xmlCollectionInline = $annot->inline;
204 9
                        $propertyMetadata->xmlEntryName = $annot->entry;
205 135
                        $propertyMetadata->xmlEntryNamespace = $annot->namespace;
206 44
                        $propertyMetadata->xmlCollectionSkipWhenEmpty = $annot->skipWhenEmpty;
207 44
                    } elseif ($annot instanceof XmlMap) {
208 129
                        $propertyMetadata->xmlCollection = true;
209 28
                        $propertyMetadata->xmlCollectionInline = $annot->inline;
210 28
                        $propertyMetadata->xmlEntryName = $annot->entry;
211 107
                        $propertyMetadata->xmlEntryNamespace = $annot->namespace;
212
                        $propertyMetadata->xmlKeyAttribute = $annot->keyAttribute;
213 107
                    } elseif ($annot instanceof XmlKeyValuePairs) {
214 3
                        $propertyMetadata->xmlKeyValuePairs = true;
215 107
                    } elseif ($annot instanceof XmlAttribute) {
216 10
                        $propertyMetadata->xmlAttribute = true;
217 104
                        $propertyMetadata->xmlNamespace = $annot->namespace;
218 11
                    } elseif ($annot instanceof XmlValue) {
219 93
                        $propertyMetadata->xmlValue = true;
220 43
                        $propertyMetadata->xmlElementCData = $annot->cdata;
221 43
                    } elseif ($annot instanceof XmlElement) {
222 43
                        $propertyMetadata->xmlElementCData = $annot->cdata;
223 3
                    } elseif ($annot instanceof AccessType) {
224 3
                        $accessType = $annot->type;
225 3
                    } elseif ($annot instanceof ReadOnly) {
226 43
                        $propertyMetadata->readOnly = $annot->readOnly;
227
                    } elseif ($annot instanceof Accessor) {
228
                        $accessor = array($annot->getter, $annot->setter);
229
                    } elseif ($annot instanceof Groups) {
230 68
                        $propertyMetadata->groups = $annot->groups;
231 7
                        foreach ((array)$propertyMetadata->groups as $groupName) {
232 61
                            if (false !== strpos($groupName, ',')) {
233 4
                                throw new InvalidArgumentException(sprintf(
234 57
                                    'Invalid group name "%s" on "%s", did you mean to create multiple groups?',
235 249
                                    implode(', ', $propertyMetadata->groups),
236
                                    $propertyMetadata->class . '->' . $propertyMetadata->name
237
                                ));
238
                            }
239
                        }
240 257
                    } elseif ($annot instanceof Inline) {
241 257
                        $propertyMetadata->inline = true;
242
                    } elseif ($annot instanceof XmlAttributeMap) {
243 257
                        $propertyMetadata->xmlAttributeMap = true;
244 257
                    } elseif ($annot instanceof MaxDepth) {
245
                        $propertyMetadata->maxDepth = $annot->depth;
246
                    }
247
                }
248
249 270
250
                if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
251
                    || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)
252
                ) {
253
                    if ($accessorFinder) {
254
                        $accessor = $this->findAccessors(
255
                            $accessorFinder,
256
                            $accessor,
257
                            $class,
258
                            $propertyMetadata
259
                        );
260
                    }
261
                    $propertyMetadata->setAccessor($accessType, $accessor[0], $accessor[1]);
262
                    $classMetadata->addPropertyMetadata($propertyMetadata);
263
                }
264
            }
265
        }
266
267
        return $classMetadata;
268
    }
269
270
    /**
271
     * @param AccessorFinderInterface $guesser
272
     * @param array $accessor Getter and setter.
273
     * @param \ReflectionClass $class
274
     * @param PropertyMetadata $metadata
275
     *
276
     * @return array
277
     */
278
    private function findAccessors(
279
        AccessorFinderInterface $guesser,
280
        array $accessor,
281
        \ReflectionClass $class,
282
        PropertyMetadata $metadata
283
    )
284
    {
285
        list($getter, $setter) = $accessor;
286
287
        if ($getter === null) {
288
            $getter = $guesser->findGetter($class, $metadata->name);
289
        }
290
291
        if ($setter === null) {
292
            $setter = $guesser->findSetter($class, $metadata->name);
293
        }
294
295
        return [$getter, $setter];
296
    }
297
}
298