SpecificationParser   B
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 39.26%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 7
dl 0
loc 284
ccs 53
cts 135
cp 0.3926
rs 8.295
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
B parse() 0 35 5
C validateDefaults() 0 58 14
C parseOperations() 0 73 13
A parseShapes() 0 12 3
B mergeMemberReferenceShape() 0 26 4
A mergeExtendsShape() 0 23 3

How to fix   Complexity   

Complex Class

Complex classes like SpecificationParser 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 SpecificationParser, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of indragunawan/rest-service package.
5
 *
6
 * (c) Indra Gunawan <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace IndraGunawan\RestService\Parser;
13
14
use IndraGunawan\RestService\Exception\InvalidSpecificationException;
15
use IndraGunawan\RestService\Validator\SpecificationConfiguration;
16
use IndraGunawan\RestService\Validator\Validator;
17
use Symfony\Component\Config\ConfigCache;
18
use Symfony\Component\Config\Definition\Processor;
19
use Symfony\Component\Config\Resource\FileResource;
20
21
class SpecificationParser
22
{
23
    /**
24
     * Parse from specificationFile to specificationArray.
25
     *
26
     * @param string $specificationFile
27
     * @param array  $defaults
28
     * @param string $cacheDir
29
     * @param bool   $debug
30
     *
31
     * @throws \IndraGunawan\RestService\Exception\InvalidSpecificationException
32
     *
33
     * @return array
34
     */
35 5
    public function parse($specificationFile, array $defaults = [], $cacheDir = null, $debug = true)
36
    {
37 5
        $cachePath = null === $cacheDir ? '' : $cacheDir.'/restService_'.md5(serialize($defaults).$specificationFile);
38
39 5
        $restServiceCache = new ConfigCache($cachePath, $debug);
40 5
        if (false === $restServiceCache->isFresh()) {
41
            try {
42 5
                $specificationArray = require $specificationFile;
43 5
                $specificationArray = ['rest_service' => $specificationArray];
44 5
                $processor = new Processor();
45 5
                $specification = $processor->processConfiguration(
46 5
                    new SpecificationConfiguration(),
47 5
                    $specificationArray
48
                );
49
50 4
                $this->validateDefaults($specification, $defaults);
51 2
                $this->parseShapes($specification['shapes']);
52 2
                $this->parseOperations(
53 2
                    $specification['operations'],
54 2
                    $specification['shapes'],
55 2
                    $specification['errorShapes']
56
                );
57
58 2
                if ($restServiceCache->getPath()) {
59
                    $restServiceCache->write(sprintf('<?php return %s;', var_export($specification, true)), [new FileResource($specificationFile)]);
60
                }
61
62 2
                return $specification;
63 3
            } catch (\Exception $e) {
64 3
                throw new InvalidSpecificationException($e->getMessage(), $e->getCode(), $e);
65
            }
66
        }
67
68
        return require $cachePath;
69
    }
70
71
    /**
72
     * Validate and Merge defaults from specification with user input.
73
     *
74
     * @param array &$specification
75
     * @param array $defaults
76
     *
77
     * @throws \IndraGunawan\RestService\Exception\InvalidSpecificationException
78
     * @throws \IndraGunawan\RestService\Exception\ValidatorException
79
     */
80 4
    private function validateDefaults(array &$specification, array $defaults)
81
    {
82 4
        $validator = new Validator();
83
84 4
        foreach ($specification['defaults'] as $key => $default) {
85 2
            if (array_key_exists($key, $defaults)) {
86
                $validator->add($key, $default, $defaults[$key]);
87
                unset($defaults[$key]);
88
            } else {
89 2
                $validator->add($key, $default);
90
            }
91
        }
92
93 4
        if (count($defaults) > 0) {
94 1
            throw new InvalidSpecificationException(sprintf(
95 1
                'Undefined defaults "%s".',
96 1
                implode('", "', array_keys($defaults))
97
            ));
98
        }
99
100 3
        if (!$validator->isValid()) {
101 1
            throw $validator->createValidatorException();
102
        }
103
104 2
        $patterns = [];
105 2
        $replacements = [];
106 2
        foreach ($validator->getDatas() as $key => $value) {
107
            $specification['defaults'][$key]['value'] = $value;
108
109
            $patterns[] = '/{'.$key.'}/';
110
            $replacements[] = $value;
111
        }
112
113
        // assign default value to all posibility
114 2
        $specification['endpoint'] = rtrim(preg_replace($patterns, $replacements, $specification['endpoint']), '/').'/';
115
116 2
        foreach ($specification['shapes'] as $name => $shape) {
117
            foreach ($shape['members'] as $memberName => $member) {
118
                if ($member['defaultValue']) {
119
                    $defaultValue = preg_replace($patterns, $replacements, $member['defaultValue']);
120
                    $specification['shapes'][$name]['members'][$memberName]['defaultValue'] = $defaultValue;
121
                }
122
            }
123
        }
124
125 2
        foreach ($specification['operations'] as $name => $operation) {
126 2
            foreach (['request', 'response'] as $placement) {
127 2
                if (isset($operation[$placement])) {
128
                    foreach ($operation[$placement]['members'] as $memberName => $member) {
129
                        if ($member['defaultValue']) {
130
                            $defaultValue = preg_replace($patterns, $replacements, $member['defaultValue']);
131 2
                            $specification['operations'][$name][$placement]['members'][$memberName]['defaultValue'] = $defaultValue;
132
                        }
133
                    }
134
                }
135
            }
136
        }
137 2
    }
138
139
    /**
140
     * Parse Operations section of specification.
141
     *
142
     * @param array &$operations
143
     * @param array $shapes
144
     * @param array $errorShapes
145
     */
146 2
    private function parseOperations(array &$operations, array $shapes, array $errorShapes)
147
    {
148 2
        foreach ($operations as $name => $operation) {
149 2
            foreach (['request', 'response'] as $placement) {
150 2
                if (isset($operation[$placement])) {
151
                    // merge member shape if exist
152
                    foreach ($operation[$placement]['members'] as $memberName => $member) {
153
                        $shapeName = $member['shape'];
154
                        if ($shapeName) {
155
                            if (!array_key_exists($shapeName, $shapes)) {
156
                                throw new InvalidSpecificationException(sprintf(
157
                                    'Shape "%s" not found, used by "%s"',
158
                                    $shapeName,
159
                                    $name
160
                                ));
161
                            }
162
163
                            $newMemberShape = array_replace_recursive(
164
                                $shapes[$shapeName],
165
                                $operation[$placement]['members'][$memberName]
166
                            );
167
                            $newMemberShape['shape'] = null;
168
169
                            $operations[$name][$placement]['members'][$memberName] = $newMemberShape;
170
                        }
171
                    }
172
173
                    foreach (['shape', 'extends'] as $property) {
174
                        $shapeName = $operation[$placement][$property];
175
                        if ($shapeName) {
176
                            if (!array_key_exists($shapeName, $shapes)) {
177
                                throw new InvalidSpecificationException(sprintf(
178
                                    'Shape "%s" not found, used by "%s"',
179
                                    $shapeName,
180
                                    $name
181
                                ));
182
                            }
183
184
                            $newShape = array_replace_recursive(
185
                                $shapes[$shapeName],
186
                                $operations[$name][$placement]
187
                            );
188
                            $newShape[$property] = null;
189
190 2
                            $operations[$name][$placement] = $newShape;
191
                        }
192
                    }
193
                }
194
            }
195
196
            // errors
197 2
            foreach ($operation['errors'] as $errorName => $error) {
198
                $errorShape = $error['errorShape'];
199
                if ($errorShape) {
200
                    if (!array_key_exists($errorShape, $errorShapes)) {
201
                        throw new InvalidSpecificationException(sprintf(
202
                            'Error Shape "%s" not found, used by "%s"',
203
                            $errorShape,
204
                            $name
205
                        ));
206
                    }
207
208
                    $newErrorShape = array_replace_recursive(
209
                        $error,
210
                        $errorShapes[$errorShape]
211
                    );
212
                    $newErrorShape['errorShape'] = null;
213
214 2
                    $operations[$name]['errors'][$errorName] = $newErrorShape;
215
                }
216
            }
217
        }
218 2
    }
219
220
    /**
221
     * Parse shapes section of specification.
222
     *
223
     * @param array &$shapes
224
     */
225 2
    private function parseShapes(array &$shapes)
226
    {
227
        // merge extends
228 2
        foreach (array_keys($shapes) as $name) {
229
            $this->mergeExtendsShape($shapes, $name);
230
        }
231
232
        // merge member reference
233 2
        foreach (array_keys($shapes) as $name) {
234
            $this->mergeMemberReferenceShape($shapes, $name);
235
        }
236 2
    }
237
238
    /**
239
     * Merge reference member of a shape.
240
     *
241
     * @param array  &$shapes
242
     * @param string $shapeName
243
     *
244
     * @return array
245
     */
246
    private function mergeMemberReferenceShape(array &$shapes, $shapeName)
247
    {
248
        $members = $shapes[$shapeName]['members'];
249
        foreach ($members as $memberName => $member) {
250
            $referenceShape = $members[$memberName]['shape'];
251
            if ($referenceShape) {
252
                if (!array_key_exists($referenceShape, $shapes)) {
253
                    throw new InvalidSpecificationException(sprintf(
254
                        '"%s" not found, used by "%s"',
255
                        $referenceShape,
256
                        $memberName
257
                    ));
258
                }
259
260
                $newMemberShape = array_replace_recursive(
261
                    $this->mergeMemberReferenceShape($shapes, $referenceShape),
262
                    $members[$memberName]
263
                );
264
                $newMemberShape['shape'] = null;
265
266
                $shapes[$shapeName]['members'][$memberName] = $newMemberShape;
267
            }
268
        }
269
270
        return $shapes[$shapeName];
271
    }
272
273
    /**
274
     * Merge reference extends of shape.
275
     *
276
     * @param array  &$shapes
277
     * @param string $shapeName
278
     *
279
     * @return array
280
     */
281
    private function mergeExtendsShape(array &$shapes, $shapeName)
282
    {
283
        $shape = $shapes[$shapeName]['extends'];
284
        if ($shape) {
285
            if (!array_key_exists($shape, $shapes)) {
286
                throw new InvalidSpecificationException(sprintf(
287
                    '"%s" not found, extends by "%s"',
288
                    $shape,
289
                    $shapeName
290
                ));
291
            }
292
293
            $newShape = array_replace_recursive(
294
                $this->mergeExtendsShape($shapes, $shape),
295
                $shapes[$shapeName]
296
            );
297
            $newShape['extends'] = null;
298
299
            $shapes[$shapeName] = $newShape;
300
        }
301
302
        return $shapes[$shapeName];
303
    }
304
}
305