Completed
Push — master ( fc2d7f...dda5b7 )
by Eric
03:12
created

loadPropertyMetadataSinceVersion()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 17
Ratio 100 %

Importance

Changes 0
Metric Value
dl 17
loc 17
c 0
b 0
f 0
rs 9.2
cc 4
eloc 9
nc 3
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Ivory Serializer package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\Serializer\Mapping\Loader;
13
14
use Ivory\Serializer\Exclusion\ExclusionPolicy;
15
use Ivory\Serializer\Mapping\ClassMetadataInterface;
16
use Ivory\Serializer\Mapping\PropertyMetadata;
17
use Ivory\Serializer\Mapping\PropertyMetadataInterface;
18
use Ivory\Serializer\Type\Parser\TypeParser;
19
use Ivory\Serializer\Type\Parser\TypeParserInterface;
20
21
/**
22
 * @author GeLo <[email protected]>
23
 */
24
abstract class AbstractClassMetadataLoader implements ClassMetadataLoaderInterface
25
{
26
    /**
27
     * @var TypeParserInterface
28
     */
29
    private $typeParser;
30
31
    /**
32
     * @var mixed[][]
33
     */
34
    private $data = [];
35
36
    /**
37
     * @param TypeParserInterface|null $typeParser
38
     */
39
    public function __construct(TypeParserInterface $typeParser = null)
40
    {
41
        $this->typeParser = $typeParser ?: new TypeParser();
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function loadClassMetadata(ClassMetadataInterface $classMetadata)
48
    {
49
        $class = $classMetadata->getName();
50
51
        if (!array_key_exists($class, $this->data)) {
52
            $this->data[$class] = $this->loadData($class);
53
        }
54
55
        if (!is_array($data = $this->data[$class])) {
56
            return false;
57
        }
58
59
        $this->doLoadClassMetadata($classMetadata, $data);
60
61
        return true;
62
    }
63
64
    /**
65
     * @param string $class
66
     *
67
     * @return mixed[]|null
68
     */
69
    abstract protected function loadData($class);
70
71
    /**
72
     * @param ClassMetadataInterface $classMetadata
73
     * @param mixed[]                $data
74
     *
75
     * @return bool
76
     */
77
    private function doLoadClassMetadata(ClassMetadataInterface $classMetadata, array $data)
78
    {
79
        if (!isset($data['properties']) || empty($data['properties'])) {
80
            throw new \InvalidArgumentException(sprintf(
81
                'No mapping properties found for "%s".',
82
                $classMetadata->getName()
83
            ));
84
        }
85
86
        $policy = $this->getExclusionPolicy($data);
87
88
        foreach ($data['properties'] as $property => $value) {
89
            $propertyMetadata = $classMetadata->getProperty($property) ?: new PropertyMetadata($property);
90
            $this->loadPropertyMetadata($propertyMetadata, $value);
91
92
            if ($this->isPropertyExposed($propertyMetadata, $policy)) {
93
                $classMetadata->addProperty($propertyMetadata);
94
            }
95
        }
96
    }
97
98
    /**
99
     * @param PropertyMetadataInterface $propertyMetadata
100
     * @param mixed                     $data
101
     */
102
    private function loadPropertyMetadata(PropertyMetadataInterface $propertyMetadata, $data)
103
    {
104
        if (!is_array($data)) {
105
            return;
106
        }
107
108
        if (array_key_exists('alias', $data)) {
109
            $this->loadPropertyMetadataAlias($propertyMetadata, $data['alias']);
110
        }
111
112
        if (array_key_exists('type', $data)) {
113
            $this->loadPropertyMetadataType($propertyMetadata, $data['type']);
114
        }
115
116
        if (array_key_exists('exclude', $data)) {
117
            $this->loadPropertyMetadataExclude($propertyMetadata, $data['exclude']);
118
        }
119
120
        if (array_key_exists('expose', $data)) {
121
            $this->loadPropertyMetadataExpose($propertyMetadata, $data['expose']);
122
        }
123
124
        if (array_key_exists('since', $data)) {
125
            $this->loadPropertyMetadataSinceVersion($propertyMetadata, $data['since']);
126
        }
127
128
        if (array_key_exists('until', $data)) {
129
            $this->loadPropertyMetadataUntilVersion($propertyMetadata, $data['until']);
130
        }
131
132
        if (array_key_exists('max_depth', $data)) {
133
            $this->loadPropertyMetadataMaxDepth($propertyMetadata, $data['max_depth']);
134
        }
135
136
        if (array_key_exists('groups', $data)) {
137
            $this->loadPropertyMetadataGroups($propertyMetadata, $data['groups']);
138
        }
139
    }
140
141
    /**
142
     * @param PropertyMetadataInterface $propertyMetadata
143
     * @param string                    $alias
144
     */
145 View Code Duplication
    private function loadPropertyMetadataAlias(PropertyMetadataInterface $propertyMetadata, $alias)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
    {
147
        if (!is_string($alias)) {
148
            throw new \InvalidArgumentException(sprintf(
149
                'The mapping property alias must be a non empty string, got "%s".',
150
                is_object($alias) ? get_class($alias) : gettype($alias)
151
            ));
152
        }
153
154
        $alias = trim($alias);
155
156
        if (empty($alias)) {
157
            throw new \InvalidArgumentException('The mapping property alias must be a non empty string.');
158
        }
159
160
        $propertyMetadata->setAlias($alias);
161
    }
162
163
    /**
164
     * @param PropertyMetadataInterface $propertyMetadata
165
     * @param string                    $type
166
     */
167 View Code Duplication
    private function loadPropertyMetadataType(PropertyMetadataInterface $propertyMetadata, $type)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
168
    {
169
        if (!is_string($type)) {
170
            throw new \InvalidArgumentException(sprintf(
171
                'The mapping property type must be a non empty string, got "%s".',
172
                is_object($type) ? get_class($type) : gettype($type)
173
            ));
174
        }
175
176
        $propertyMetadata->setType($this->typeParser->parse($type));
177
    }
178
179
    /**
180
     * @param PropertyMetadataInterface $propertyMetadata
181
     * @param string                    $exclude
182
     */
183 View Code Duplication
    private function loadPropertyMetadataExclude(PropertyMetadataInterface $propertyMetadata, $exclude)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
    {
185
        if (!is_bool($exclude)) {
186
            throw new \InvalidArgumentException(sprintf(
187
                'The mapping property exclude must be a boolean, got "%s".',
188
                is_object($exclude) ? get_class($exclude) : gettype($exclude)
189
            ));
190
        }
191
192
        $propertyMetadata->setExcluded($exclude);
193
    }
194
195
    /**
196
     * @param PropertyMetadataInterface $propertyMetadata
197
     * @param string                    $expose
198
     */
199 View Code Duplication
    private function loadPropertyMetadataExpose(PropertyMetadataInterface $propertyMetadata, $expose)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
    {
201
        if (!is_bool($expose)) {
202
            throw new \InvalidArgumentException(sprintf(
203
                'The mapping property expose must be a boolean, got "%s".',
204
                is_object($expose) ? get_class($expose) : gettype($expose)
205
            ));
206
        }
207
208
        $propertyMetadata->setExposed($expose);
209
    }
210
211
    /**
212
     * @param PropertyMetadataInterface $propertyMetadata
213
     * @param string                    $version
214
     */
215 View Code Duplication
    private function loadPropertyMetadataSinceVersion(PropertyMetadataInterface $propertyMetadata, $version)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
    {
217
        if (!is_string($version)) {
218
            throw new \InvalidArgumentException(sprintf(
219
                'The mapping property since version must be a non empty string, got "%s".',
220
                is_object($version) ? get_class($version) : gettype($version)
221
            ));
222
        }
223
224
        $version = trim($version);
225
226
        if (empty($version)) {
227
            throw new \InvalidArgumentException('The mapping property since version must be a non empty string.');
228
        }
229
230
        $propertyMetadata->setSinceVersion($version);
231
    }
232
233
    /**
234
     * @param PropertyMetadataInterface $propertyMetadata
235
     * @param string                    $version
236
     */
237 View Code Duplication
    private function loadPropertyMetadataUntilVersion(PropertyMetadataInterface $propertyMetadata, $version)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
238
    {
239
        if (!is_string($version)) {
240
            throw new \InvalidArgumentException(sprintf(
241
                'The mapping property until version must be a non empty string, got "%s".',
242
                is_object($version) ? get_class($version) : gettype($version)
243
            ));
244
        }
245
246
        $version = trim($version);
247
248
        if (empty($version)) {
249
            throw new \InvalidArgumentException('The mapping property until version must be a non empty string.');
250
        }
251
252
        $propertyMetadata->setUntilVersion($version);
253
    }
254
255
    /**
256
     * @param PropertyMetadataInterface $propertyMetadata
257
     * @param string|int                $maxDepth
258
     */
259
    private function loadPropertyMetadataMaxDepth(PropertyMetadataInterface $propertyMetadata, $maxDepth)
260
    {
261
        if (!is_int($maxDepth) && !is_string($maxDepth) && !ctype_digit($maxDepth)) {
262
            throw new \InvalidArgumentException(sprintf(
263
                'The mapping property max depth must be a positive integer, got "%s".',
264
                is_object($maxDepth) ? get_class($maxDepth) : gettype($maxDepth)
265
            ));
266
        }
267
268
        $maxDepth = (int) $maxDepth;
269
270
        if ($maxDepth <= 0) {
271
            throw new \InvalidArgumentException(sprintf(
272
                'The mapping property max depth must be a positive integer, got "%d".',
273
                $maxDepth
274
            ));
275
        }
276
277
        $propertyMetadata->setMaxDepth($maxDepth);
278
    }
279
280
    /**
281
     * @param PropertyMetadataInterface $propertyMetadata
282
     * @param string[]                  $groups
283
     */
284
    private function loadPropertyMetadataGroups(PropertyMetadataInterface $propertyMetadata, $groups)
285
    {
286
        if (!is_array($groups)) {
287
            throw new \InvalidArgumentException(sprintf(
288
                'The mapping property groups must be an array of non empty strings, got "%s".',
289
                is_object($groups) ? get_class($groups) : gettype($groups)
290
            ));
291
        }
292
293
        foreach ($groups as $group) {
294
            if (!is_string($group)) {
295
                throw new \InvalidArgumentException(sprintf(
296
                    'The mapping property groups must be an array of non empty strings, got "%s".',
297
                    is_object($group) ? get_class($group) : gettype($group)
298
                ));
299
            }
300
301
            $group = trim($group);
302
303
            if (empty($group)) {
304
                throw new \InvalidArgumentException(
305
                    'The mapping property groups must be an array of non empty strings.'
306
                );
307
            }
308
309
            $propertyMetadata->addGroup($group);
310
        }
311
    }
312
313
    /**
314
     * @param mixed[] $data
315
     *
316
     * @return string|null
317
     */
318
    private function getExclusionPolicy($data)
319
    {
320
        if (!isset($data['exclusion_policy'])) {
321
            return ExclusionPolicy::NONE;
322
        }
323
324
        $policy = $data['exclusion_policy'];
325
326
        if (!is_string($policy)) {
327
            throw new \InvalidArgumentException(sprintf(
328
                'The mapping exclusion policy must be "%s" or "%s", got "%s".',
329
                ExclusionPolicy::ALL,
330
                ExclusionPolicy::NONE,
331
                is_object($policy) ? get_class($policy) : gettype($policy)
332
            ));
333
        }
334
335
        $policy = strtolower(trim($policy));
336
337
        if ($policy !== ExclusionPolicy::ALL && $policy !== ExclusionPolicy::NONE) {
338
            throw new \InvalidArgumentException(sprintf(
339
                'The mapping exclusion policy must be "%s" or "%s", got "%s".',
340
                ExclusionPolicy::ALL,
341
                ExclusionPolicy::NONE,
342
                $policy
343
            ));
344
        }
345
346
        return $policy;
347
    }
348
349
    /**
350
     * @param PropertyMetadataInterface $property
351
     * @param string                    $policy
352
     *
353
     * @return bool
354
     */
355
    private function isPropertyExposed(PropertyMetadataInterface $property, $policy)
356
    {
357
        return ($policy === ExclusionPolicy::ALL && $property->isExposed())
358
            || ($policy === ExclusionPolicy::NONE && !$property->isExcluded());
0 ignored issues
show
Bug Best Practice introduced by
The expression $property->isExcluded() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
359
    }
360
}
361