Completed
Push — master ( 41c5cb...fa48f7 )
by Eric
02:49
created

validatePropertyMetadataExpose()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
dl 9
loc 9
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 5
nc 2
nop 1
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->isPropertyMetadataExposed($value, $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('exclude', $data)) {
109
            $this->validatePropertyMetadataExclude($data['exclude']);
110
        }
111
112
        if (array_key_exists('expose', $data)) {
113
            $this->validatePropertyMetadataExpose($data['expose']);
114
        }
115
116
        if (array_key_exists('alias', $data)) {
117
            $this->loadPropertyMetadataAlias($propertyMetadata, $data['alias']);
118
        }
119
120
        if (array_key_exists('type', $data)) {
121
            $this->loadPropertyMetadataType($propertyMetadata, $data['type']);
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
    private function loadPropertyMetadataType(PropertyMetadataInterface $propertyMetadata, $type)
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                    $version
182
     */
183 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...
184
    {
185
        if (!is_string($version)) {
186
            throw new \InvalidArgumentException(sprintf(
187
                'The mapping property since version must be a non empty string, got "%s".',
188
                is_object($version) ? get_class($version) : gettype($version)
189
            ));
190
        }
191
192
        $version = trim($version);
193
194
        if (empty($version)) {
195
            throw new \InvalidArgumentException('The mapping property since version must be a non empty string.');
196
        }
197
198
        $propertyMetadata->setSinceVersion($version);
199
    }
200
201
    /**
202
     * @param PropertyMetadataInterface $propertyMetadata
203
     * @param string                    $version
204
     */
205 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...
206
    {
207
        if (!is_string($version)) {
208
            throw new \InvalidArgumentException(sprintf(
209
                'The mapping property until version must be a non empty string, got "%s".',
210
                is_object($version) ? get_class($version) : gettype($version)
211
            ));
212
        }
213
214
        $version = trim($version);
215
216
        if (empty($version)) {
217
            throw new \InvalidArgumentException('The mapping property until version must be a non empty string.');
218
        }
219
220
        $propertyMetadata->setUntilVersion($version);
221
    }
222
223
    /**
224
     * @param PropertyMetadataInterface $propertyMetadata
225
     * @param string|int                $maxDepth
226
     */
227
    private function loadPropertyMetadataMaxDepth(PropertyMetadataInterface $propertyMetadata, $maxDepth)
228
    {
229
        if (!is_int($maxDepth) && !is_string($maxDepth) && !ctype_digit($maxDepth)) {
230
            throw new \InvalidArgumentException(sprintf(
231
                'The mapping property max depth must be a positive integer, got "%s".',
232
                is_object($maxDepth) ? get_class($maxDepth) : gettype($maxDepth)
233
            ));
234
        }
235
236
        $maxDepth = (int) $maxDepth;
237
238
        if ($maxDepth <= 0) {
239
            throw new \InvalidArgumentException(sprintf(
240
                'The mapping property max depth must be a positive integer, got "%d".',
241
                $maxDepth
242
            ));
243
        }
244
245
        $propertyMetadata->setMaxDepth($maxDepth);
246
    }
247
248
    /**
249
     * @param PropertyMetadataInterface $propertyMetadata
250
     * @param string[]                  $groups
251
     */
252
    private function loadPropertyMetadataGroups(PropertyMetadataInterface $propertyMetadata, $groups)
253
    {
254
        if (!is_array($groups)) {
255
            throw new \InvalidArgumentException(sprintf(
256
                'The mapping property groups must be an array of non empty strings, got "%s".',
257
                is_object($groups) ? get_class($groups) : gettype($groups)
258
            ));
259
        }
260
261
        foreach ($groups as $group) {
262
            if (!is_string($group)) {
263
                throw new \InvalidArgumentException(sprintf(
264
                    'The mapping property groups must be an array of non empty strings, got "%s".',
265
                    is_object($group) ? get_class($group) : gettype($group)
266
                ));
267
            }
268
269
            $group = trim($group);
270
271
            if (empty($group)) {
272
                throw new \InvalidArgumentException(
273
                    'The mapping property groups must be an array of non empty strings.'
274
                );
275
            }
276
277
            $propertyMetadata->addGroup($group);
278
        }
279
    }
280
281
    /**
282
     * @param mixed[] $data
283
     *
284
     * @return string|null
285
     */
286
    private function getExclusionPolicy($data)
287
    {
288
        if (!isset($data['exclusion_policy'])) {
289
            return ExclusionPolicy::NONE;
290
        }
291
292
        $policy = $data['exclusion_policy'];
293
294
        if (!is_string($policy)) {
295
            throw new \InvalidArgumentException(sprintf(
296
                'The mapping exclusion policy must be "%s" or "%s", got "%s".',
297
                ExclusionPolicy::ALL,
298
                ExclusionPolicy::NONE,
299
                is_object($policy) ? get_class($policy) : gettype($policy)
300
            ));
301
        }
302
303
        $policy = strtolower(trim($policy));
304
305
        if ($policy !== ExclusionPolicy::ALL && $policy !== ExclusionPolicy::NONE) {
306
            throw new \InvalidArgumentException(sprintf(
307
                'The mapping exclusion policy must be "%s" or "%s", got "%s".',
308
                ExclusionPolicy::ALL,
309
                ExclusionPolicy::NONE,
310
                $policy
311
            ));
312
        }
313
314
        return $policy;
315
    }
316
317
    /**
318
     * @param bool $exclude
319
     */
320 View Code Duplication
    private function validatePropertyMetadataExclude($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...
321
    {
322
        if (!is_bool($exclude)) {
323
            throw new \InvalidArgumentException(sprintf(
324
                'The mapping property exclude must be a boolean, got "%s".',
325
                is_object($exclude) ? get_class($exclude) : gettype($exclude)
326
            ));
327
        }
328
    }
329
330
    /**
331
     * @param bool $expose
332
     */
333 View Code Duplication
    private function validatePropertyMetadataExpose($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...
334
    {
335
        if (!is_bool($expose)) {
336
            throw new \InvalidArgumentException(sprintf(
337
                'The mapping property expose must be a boolean, got "%s".',
338
                is_object($expose) ? get_class($expose) : gettype($expose)
339
            ));
340
        }
341
    }
342
343
    /**
344
     * @param mixed[] $property
345
     * @param string  $policy
346
     *
347
     * @return bool
348
     */
349
    private function isPropertyMetadataExposed($property, $policy)
350
    {
351
        $expose = isset($property['expose']) && $property['expose'];
352
        $exclude = isset($property['exclude']) && $property['exclude'];
353
354
        return ($policy === ExclusionPolicy::ALL && $expose) || ($policy === ExclusionPolicy::NONE && !$exclude);
355
    }
356
}
357