Completed
Push — master ( 327515...051294 )
by Indra
02:26
created

SpecificationParser::parseShapes()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

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