Completed
Pull Request — master (#888)
by Robert
03:06
created

YamlDriver   D

Complexity

Total Complexity 96

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 78.03%

Importance

Changes 0
Metric Value
wmc 96
lcom 1
cbo 11
dl 0
loc 327
ccs 135
cts 173
cp 0.7803
rs 4.5142
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
F loadMetadataFromFile() 0 231 68
A getExtension() 0 4 1
F addClassProperties() 0 55 20
B getCallbackMetadata() 0 19 5

How to fix   Complexity   

Complex Class

Complex classes like YamlDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use YamlDriver, and based on these observations, apply Extract Interface, too.

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 JMS\Serializer\Accessor\Updater\ClassAccessorUpdater;
22
use JMS\Serializer\Annotation\ExclusionPolicy;
23
use JMS\Serializer\Exception\RuntimeException;
24
use JMS\Serializer\GraphNavigator;
25
use JMS\Serializer\Metadata\ClassMetadata;
26
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
27
use JMS\Serializer\Metadata\PropertyMetadata;
28
use JMS\Serializer\Metadata\ClassMetadataUpdaterInterface;
29
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
30
use Metadata\Driver\AbstractFileDriver;
31
use Metadata\Driver\FileLocatorInterface;
32
use Metadata\MethodMetadata;
33
use Symfony\Component\Yaml\Yaml;
34
35
class YamlDriver extends AbstractFileDriver
36
{
37
    /**
38
     * @var ClassMetadataUpdaterInterface
39
     */
40
    private $propertyUpdater;
41
42 29
    public function __construct(FileLocatorInterface $locator, ClassMetadataUpdaterInterface $classMetadataUpdater = null)
43
    {
44 29
        parent::__construct($locator);
45 29
        $this->propertyUpdater = $classMetadataUpdater ?: new ClassAccessorUpdater();
46 29
    }
47
48
49 29
    protected function loadMetadataFromFile(\ReflectionClass $class, $file)
50
    {
51 29
        $config = Yaml::parse(file_get_contents($file));
52
53 29
        if (!isset($config[$name = $class->name])) {
54
            throw new RuntimeException(sprintf('Expected metadata for class %s to be defined in %s.', $class->name, $file));
55
        }
56
57 29
        $config = $config[$name];
58 29
        $metadata = new ClassMetadata($name);
59 29
        $metadata->fileResources[] = $file;
60 29
        $metadata->fileResources[] = $class->getFileName();
61 29
        $exclusionPolicy = isset($config['exclusion_policy']) ? strtoupper($config['exclusion_policy']) : 'NONE';
62 29
        $excludeAll = isset($config['exclude']) ? (Boolean)$config['exclude'] : false;
63 29
        $metadata->accessType = isset($config['access_type']) ? $config['access_type'] : null;
64 29
        $metadata->accessTypeNaming = isset($config['access_type_naming']) ? $config['access_type_naming'] : null;
65 29
        $readOnlyClass = isset($config['read_only']) ? (Boolean)$config['read_only'] : false;
66 29
        $this->addClassProperties($metadata, $config);
67
68 29
        $propertiesMetadata = array();
69 29
        if (array_key_exists('virtual_properties', $config)) {
70 4
            foreach ($config['virtual_properties'] as $methodName => $propertySettings) {
71 4
                if (isset($propertySettings['exp'])) {
72 2
                    $virtualPropertyMetadata = new ExpressionPropertyMetadata($name, $methodName, $propertySettings['exp']);
73 2
                    unset($propertySettings['exp']);
74
75
                } else {
76
77 3
                    if (!$class->hasMethod($methodName)) {
78
                        throw new RuntimeException('The method ' . $methodName . ' not found in class ' . $class->name);
79
                    }
80 3
                    $virtualPropertyMetadata = new VirtualPropertyMetadata($name, $methodName);
81
                }
82 4
                $propertiesMetadata[$methodName] = $virtualPropertyMetadata;
83 4
                $config['properties'][$methodName] = $propertySettings;
84
            }
85
        }
86
87 29
        if (!$excludeAll) {
88 29
            foreach ($class->getProperties() as $property) {
89 25
                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...
90 2
                    continue;
91
                }
92
93 24
                $pName = $property->getName();
94 24
                $propertiesMetadata[$pName] = new PropertyMetadata($name, $pName);
95
            }
96
97
            /** @var PropertyMetadata $pMetadata */
98 29
            foreach ($propertiesMetadata as $pName => $pMetadata) {
99 26
                $isExclude = false;
100 26
                $isExpose = $pMetadata instanceof VirtualPropertyMetadata
101 25
                    || $pMetadata instanceof ExpressionPropertyMetadata
102 26
                    || (isset($config['properties']) && array_key_exists($pName, $config['properties']));
103
104 26
                if (isset($config['properties'][$pName])) {
105 23
                    $pConfig = $config['properties'][$pName];
106
107 23
                    if (isset($pConfig['exclude'])) {
108 3
                        $isExclude = (Boolean)$pConfig['exclude'];
109
                    }
110
111 23
                    if ($isExclude) {
112 3
                        continue;
113
                    }
114
115 22
                    if (isset($pConfig['expose'])) {
116 3
                        $isExpose = (Boolean)$pConfig['expose'];
117
                    }
118
119 22
                    if (isset($pConfig['skip_when_empty'])) {
120 1
                        $pMetadata->skipWhenEmpty = (Boolean)$pConfig['skip_when_empty'];
121
                    }
122
123 22
                    if (isset($pConfig['since_version'])) {
124
                        $pMetadata->sinceVersion = (string)$pConfig['since_version'];
125
                    }
126
127 22
                    if (isset($pConfig['until_version'])) {
128
                        $pMetadata->untilVersion = (string)$pConfig['until_version'];
129
                    }
130
131 22
                    if (isset($pConfig['exclude_if'])) {
132 2
                        $pMetadata->excludeIf = (string)$pConfig['exclude_if'];
133
                    }
134
135 22
                    if (isset($pConfig['expose_if'])) {
136 2
                        $pMetadata->excludeIf = "!(" . $pConfig['expose_if'] . ")";
137
                    }
138
139 22
                    if (isset($pConfig['serialized_name'])) {
140 3
                        $pMetadata->serializedName = (string)$pConfig['serialized_name'];
141
                    }
142
143 22
                    if (isset($pConfig['type'])) {
144 16
                        $pMetadata->setType((string)$pConfig['type']);
145
                    }
146
147 22
                    if (isset($pConfig['groups'])) {
148 1
                        $pMetadata->groups = $pConfig['groups'];
149
                    }
150
151 22
                    if (isset($pConfig['xml_list'])) {
152 2
                        $pMetadata->xmlCollection = true;
153
154 2
                        $colConfig = $pConfig['xml_list'];
155 2
                        if (isset($colConfig['inline'])) {
156 2
                            $pMetadata->xmlCollectionInline = (Boolean)$colConfig['inline'];
157
                        }
158
159 2
                        if (isset($colConfig['entry_name'])) {
160 1
                            $pMetadata->xmlEntryName = (string)$colConfig['entry_name'];
161
                        }
162
163 2
                        if (isset($colConfig['skip_when_empty'])) {
164 1
                            $pMetadata->xmlCollectionSkipWhenEmpty = (Boolean)$colConfig['skip_when_empty'];
165
                        } else {
166 2
                            $pMetadata->xmlCollectionSkipWhenEmpty = true;
167
                        }
168
169 2
                        if (isset($colConfig['namespace'])) {
170
                            $pMetadata->xmlEntryNamespace = (string)$colConfig['namespace'];
171
                        }
172
                    }
173
174 22
                    if (isset($pConfig['xml_map'])) {
175
                        $pMetadata->xmlCollection = true;
176
177
                        $colConfig = $pConfig['xml_map'];
178
                        if (isset($colConfig['inline'])) {
179
                            $pMetadata->xmlCollectionInline = (Boolean)$colConfig['inline'];
180
                        }
181
182
                        if (isset($colConfig['entry_name'])) {
183
                            $pMetadata->xmlEntryName = (string)$colConfig['entry_name'];
184
                        }
185
186
                        if (isset($colConfig['namespace'])) {
187
                            $pMetadata->xmlEntryNamespace = (string)$colConfig['namespace'];
188
                        }
189
190
                        if (isset($colConfig['key_attribute_name'])) {
191
                            $pMetadata->xmlKeyAttribute = $colConfig['key_attribute_name'];
192
                        }
193
194
                    }
195
196 22
                    if (isset($pConfig['xml_element'])) {
197 4
                        $colConfig = $pConfig['xml_element'];
198 4
                        if (isset($colConfig['cdata'])) {
199 2
                            $pMetadata->xmlElementCData = (Boolean)$colConfig['cdata'];
200
                        }
201
202 4
                        if (isset($colConfig['namespace'])) {
203 3
                            $pMetadata->xmlNamespace = (string)$colConfig['namespace'];
204
                        }
205
                    }
206
207 22
                    if (isset($pConfig['xml_attribute'])) {
208 4
                        $pMetadata->xmlAttribute = (Boolean)$pConfig['xml_attribute'];
209
                    }
210
211 22
                    if (isset($pConfig['xml_attribute_map'])) {
212
                        $pMetadata->xmlAttributeMap = (Boolean)$pConfig['xml_attribute_map'];
213
                    }
214
215 22
                    if (isset($pConfig['xml_value'])) {
216 2
                        $pMetadata->xmlValue = (Boolean)$pConfig['xml_value'];
217
                    }
218
219 22
                    if (isset($pConfig['xml_key_value_pairs'])) {
220 1
                        $pMetadata->xmlKeyValuePairs = (Boolean)$pConfig['xml_key_value_pairs'];
221
                    }
222
223
                    //we need read_only before setter and getter set, because that method depends on flag being set
224 22
                    if (isset($pConfig['read_only'])) {
225 3
                        $pMetadata->readOnly = (Boolean)$pConfig['read_only'];
226
                    } else {
227 20
                        $pMetadata->readOnly = $pMetadata->readOnly || $readOnlyClass;
228
                    }
229
230 22
                    $pMetadata->setAccessor(
231 22
                        isset($pConfig['access_type']) ? $pConfig['access_type'] : null,
232 22
                        isset($pConfig['accessor']['getter']) ? $pConfig['accessor']['getter'] : null,
233 22
                        isset($pConfig['accessor']['setter']) ? $pConfig['accessor']['setter'] : null,
234 22
                        isset($pConfig['access_type_naming']) ? $pConfig['access_type_naming'] : null
235
                    );
236
237 22
                    if (isset($pConfig['inline'])) {
238
                        $pMetadata->inline = (Boolean)$pConfig['inline'];
239
                    }
240
241 22
                    if (isset($pConfig['max_depth'])) {
242 1
                        $pMetadata->maxDepth = (int)$pConfig['max_depth'];
243
                    }
244
                }
245 26
                if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
246 26
                    || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)
247
                ) {
248 26
                    $metadata->addPropertyMetadata($pMetadata);
249
                }
250
            }
251
        }
252
253 29
        if (isset($config['handler_callbacks'])) {
254 1
            foreach ($config['handler_callbacks'] as $directionName => $formats) {
255 1
                $direction = GraphNavigator::parseDirection($directionName);
256 1
                foreach ($formats as $format => $methodName) {
257 1
                    $metadata->addHandlerCallback($direction, $format, $methodName);
258
                }
259
            }
260
        }
261
262 29
        if (isset($config['callback_methods'])) {
263
            $cConfig = $config['callback_methods'];
264
265
            if (isset($cConfig['pre_serialize'])) {
266
                $metadata->preSerializeMethods = $this->getCallbackMetadata($class, $cConfig['pre_serialize']);
267
            }
268
            if (isset($cConfig['post_serialize'])) {
269
                $metadata->postSerializeMethods = $this->getCallbackMetadata($class, $cConfig['post_serialize']);
270
            }
271
            if (isset($cConfig['post_deserialize'])) {
272
                $metadata->postDeserializeMethods = $this->getCallbackMetadata($class, $cConfig['post_deserialize']);
273
            }
274
        }
275
276 29
        $this->propertyUpdater->update($metadata);
277
278 28
        return $metadata;
279
    }
280
281 29
    protected function getExtension()
282
    {
283 29
        return 'yml';
284
    }
285
286 29
    private function addClassProperties(ClassMetadata $metadata, array $config)
287
    {
288 29
        if (isset($config['custom_accessor_order']) && !isset($config['accessor_order'])) {
289 1
            $config['accessor_order'] = 'custom';
290
        }
291
292 29
        if (isset($config['accessor_order'])) {
293 1
            $metadata->setAccessorOrder($config['accessor_order'], isset($config['custom_accessor_order']) ? $config['custom_accessor_order'] : array());
294
        }
295
296 29
        if (isset($config['xml_root_name'])) {
297 9
            $metadata->xmlRootName = (string)$config['xml_root_name'];
298
        }
299
300 29
        if (isset($config['xml_root_namespace'])) {
301 1
            $metadata->xmlRootNamespace = (string)$config['xml_root_namespace'];
302
        }
303
304 29
        if (array_key_exists('xml_namespaces', $config)) {
305
306 3
            foreach ($config['xml_namespaces'] as $prefix => $uri) {
307 3
                $metadata->registerNamespace($uri, $prefix);
308
            }
309
310
        }
311
312 29
        if (isset($config['discriminator'])) {
313 4
            if (isset($config['discriminator']['disabled']) && true === $config['discriminator']['disabled']) {
314
                $metadata->discriminatorDisabled = true;
315
            } else {
316 4
                if (!isset($config['discriminator']['field_name'])) {
317
                    throw new RuntimeException('The "field_name" attribute must be set for discriminators.');
318
                }
319
320 4
                if (!isset($config['discriminator']['map']) || !\is_array($config['discriminator']['map'])) {
321
                    throw new RuntimeException('The "map" attribute must be set, and be an array for discriminators.');
322
                }
323 4
                $groups = isset($config['discriminator']['groups']) ? $config['discriminator']['groups'] : array();
324 4
                $metadata->setDiscriminator($config['discriminator']['field_name'], $config['discriminator']['map'], $groups);
325
326 4
                if (isset($config['discriminator']['xml_attribute'])) {
327 1
                    $metadata->xmlDiscriminatorAttribute = (bool)$config['discriminator']['xml_attribute'];
328
                }
329 4
                if (isset($config['discriminator']['xml_element'])) {
330 2
                    if (isset($config['discriminator']['xml_element']['cdata'])) {
331 1
                        $metadata->xmlDiscriminatorCData = (bool)$config['discriminator']['xml_element']['cdata'];
332
                    }
333 2
                    if (isset($config['discriminator']['xml_element']['namespace'])) {
334 1
                        $metadata->xmlDiscriminatorNamespace = (string)$config['discriminator']['xml_element']['namespace'];
335
                    }
336
                }
337
338
            }
339
        }
340 29
    }
341
342
    private function getCallbackMetadata(\ReflectionClass $class, $config)
343
    {
344
        if (\is_string($config)) {
345
            $config = array($config);
346
        } elseif (!\is_array($config)) {
347
            throw new RuntimeException(sprintf('callback methods expects a string, or an array of strings that represent method names, but got %s.', json_encode($config['pre_serialize'])));
348
        }
349
350
        $methods = array();
351
        foreach ($config as $name) {
352
            if (!$class->hasMethod($name)) {
353
                throw new RuntimeException(sprintf('The method %s does not exist in class %s.', $name, $class->name));
354
            }
355
356
            $methods[] = new MethodMetadata($class->name, $name);
357
        }
358
359
        return $methods;
360
    }
361
}
362