Completed
Pull Request — master (#944)
by Asmir
02:43
created

ClassMetadata::sortProperties()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.0961

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 0
dl 0
loc 14
ccs 9
cts 11
cp 0.8182
crap 4.0961
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
namespace JMS\Serializer\Metadata;
22
23
use JMS\Serializer\Exception\InvalidArgumentException;
24
use JMS\Serializer\Exception\LogicException;
25
use JMS\Serializer\Ordering\AlphabeticalPropertyOrderingStrategy;
26
use JMS\Serializer\Ordering\CustomPropertyOrderingStrategy;
27
use JMS\Serializer\Ordering\IdenticalPropertyOrderingStrategy;
28
use Metadata\MergeableClassMetadata;
29
use Metadata\MergeableInterface;
30
use Metadata\MethodMetadata;
31
use Metadata\PropertyMetadata as BasePropertyMetadata;
32
33
/**
34
 * Class Metadata used to customize the serialization process.
35
 *
36
 * @author Johannes M. Schmitt <[email protected]>
37
 */
38
class ClassMetadata extends MergeableClassMetadata
39
{
40
    const ACCESSOR_ORDER_UNDEFINED = 'undefined';
41
    const ACCESSOR_ORDER_ALPHABETICAL = 'alphabetical';
42
    const ACCESSOR_ORDER_CUSTOM = 'custom';
43
44
    /** @var \ReflectionMethod[] */
45
    public $preSerializeMethods = [];
46
47
    /** @var \ReflectionMethod[] */
48
    public $postSerializeMethods = [];
49
50
    /** @var \ReflectionMethod[] */
51
    public $postDeserializeMethods = [];
52
53
    public $xmlRootName;
54
    public $xmlRootNamespace;
55
    public $xmlNamespaces = [];
56
    public $accessorOrder;
57
    public $customOrder;
58
    public $usingExpression = false;
59
    public $isList = false;
60
61
    public $discriminatorDisabled = false;
62
    public $discriminatorBaseClass;
63
    public $discriminatorFieldName;
64
    public $discriminatorValue;
65
    public $discriminatorMap = [];
66
    public $discriminatorGroups = [];
67
68
    public $xmlDiscriminatorAttribute = false;
69
    public $xmlDiscriminatorCData = true;
70
    public $xmlDiscriminatorNamespace;
71
72 32
    public function setDiscriminator($fieldName, array $map, array $groups = [])
73
    {
74 32
        if (empty($fieldName)) {
75
            throw new InvalidArgumentException('The $fieldName cannot be empty.');
76
        }
77
78 32
        if (empty($map)) {
79
            throw new InvalidArgumentException('The discriminator map cannot be empty.');
80
        }
81
82 32
        $this->discriminatorBaseClass = $this->name;
83 32
        $this->discriminatorFieldName = $fieldName;
84 32
        $this->discriminatorMap = $map;
85 32
        $this->discriminatorGroups = $groups;
86 32
    }
87
88
    /**
89
     * Sets the order of properties in the class.
90
     *
91
     * @param string $order
92
     * @param array $customOrder
93
     *
94
     * @throws InvalidArgumentException When the accessor order is not valid
95
     * @throws InvalidArgumentException When the custom order is not valid
96
     */
97 39
    public function setAccessorOrder($order, array $customOrder = [])
98
    {
99 39
        if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) {
100
            throw new InvalidArgumentException(sprintf('The accessor order "%s" is invalid.', $order));
101
        }
102
103 39
        foreach ($customOrder as $name) {
104 34
            if (!\is_string($name)) {
105 34
                throw new InvalidArgumentException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
106
            }
107
        }
108
109 39
        $this->accessorOrder = $order;
110 39
        $this->customOrder = array_flip($customOrder);
111 39
        $this->sortProperties();
112 39
    }
113
114 270
    public function addPropertyMetadata(BasePropertyMetadata $metadata)
115
    {
116 270
        parent::addPropertyMetadata($metadata);
117 270
        $this->sortProperties();
118 270
        if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) {
119 26
            $this->usingExpression = true;
120
        }
121 270
    }
122
123 2
    public function addPreSerializeMethod(MethodMetadata $method)
124
    {
125 2
        $this->preSerializeMethods[] = $method;
126 2
    }
127
128 2
    public function addPostSerializeMethod(MethodMetadata $method)
129
    {
130 2
        $this->postSerializeMethods[] = $method;
131 2
    }
132
133 4
    public function addPostDeserializeMethod(MethodMetadata $method)
134
    {
135 4
        $this->postDeserializeMethods[] = $method;
136 4
    }
137
138 25
    public function merge(MergeableInterface $object)
139
    {
140 25
        if (!$object instanceof ClassMetadata) {
141
            throw new InvalidArgumentException('$object must be an instance of ClassMetadata.');
142
        }
143 25
        parent::merge($object);
144
145 25
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
146 25
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
147 25
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
148 25
        $this->xmlRootName = $object->xmlRootName;
149 25
        $this->xmlRootNamespace = $object->xmlRootNamespace;
150 25
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
151
152 25
        if ($object->accessorOrder) {
153 2
            $this->accessorOrder = $object->accessorOrder;
154 2
            $this->customOrder = $object->customOrder;
155
        }
156
157 25
        if ($object->discriminatorFieldName && $this->discriminatorFieldName) {
158
            throw new LogicException(sprintf(
159
                'The discriminator of class "%s" would overwrite the discriminator of the parent class "%s". Please define all possible sub-classes in the discriminator of %s.',
160
                $object->name,
161
                $this->discriminatorBaseClass,
162
                $this->discriminatorBaseClass
163
            ));
164 25
        } elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) {
165 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
166 2
            $this->discriminatorMap = $object->discriminatorMap;
167
        }
168
169 25
        if ($object->discriminatorDisabled !== null) {
170 25
            $this->discriminatorDisabled = $object->discriminatorDisabled;
171
        }
172
173 25
        if ($object->discriminatorMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object->discriminatorMap of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
174 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
175 2
            $this->discriminatorMap = $object->discriminatorMap;
176 2
            $this->discriminatorBaseClass = $object->discriminatorBaseClass;
177
        }
178
179 25
        if ($this->discriminatorMap && !$this->reflection->isAbstract()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->discriminatorMap of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
180 15
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
181
                throw new LogicException(sprintf(
182
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
183
                    $this->name,
184
                    $this->discriminatorBaseClass
185
                ));
186
            }
187
188 15
            $this->discriminatorValue = $typeValue;
189
190 15
            if (isset($this->propertyMetadata[$this->discriminatorFieldName])
191 15
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
192
            ) {
193
                throw new LogicException(sprintf(
194
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
195
                    $this->discriminatorFieldName,
196
                    $this->discriminatorBaseClass,
197
                    $this->name
198
                ));
199
            }
200
201 15
            $discriminatorProperty = new StaticPropertyMetadata(
202 15
                $this->name,
203 15
                $this->discriminatorFieldName,
204 15
                $typeValue,
205 15
                $this->discriminatorGroups
206
            );
207 15
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
208 15
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
209 15
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
210 15
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
211 15
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
212
        }
213
214 25
        $this->sortProperties();
215 25
    }
216
217 31
    public function registerNamespace($uri, $prefix = null)
218
    {
219 31
        if (!\is_string($uri)) {
220
            throw new InvalidArgumentException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
221
        }
222
223 31
        if ($prefix !== null) {
224 31
            if (!\is_string($prefix)) {
225 31
                throw new InvalidArgumentException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
226
            }
227
        } else {
228 2
            $prefix = "";
229
        }
230
231 31
        $this->xmlNamespaces[$prefix] = $uri;
232 31
    }
233
234
    public function serialize()
235
    {
236
        $this->sortProperties();
237
238
        return serialize([
239
            $this->preSerializeMethods,
240
            $this->postSerializeMethods,
241
            $this->postDeserializeMethods,
242
            $this->xmlRootName,
243
            $this->xmlRootNamespace,
244
            $this->xmlNamespaces,
245
            $this->accessorOrder,
246
            $this->customOrder,
247
            $this->discriminatorDisabled,
248
            $this->discriminatorBaseClass,
249
            $this->discriminatorFieldName,
250
            $this->discriminatorValue,
251
            $this->discriminatorMap,
252
            $this->discriminatorGroups,
253
            parent::serialize(),
254
            'discriminatorGroups' => $this->discriminatorGroups,
255
            'xmlDiscriminatorAttribute' => $this->xmlDiscriminatorAttribute,
256
            'xmlDiscriminatorCData' => $this->xmlDiscriminatorCData,
257
            'usingExpression' => $this->usingExpression,
258
            'xmlDiscriminatorNamespace' => $this->xmlDiscriminatorNamespace,
259
            'isList' => $this->isList,
260
        ]);
261
    }
262
263
    public function unserialize($str)
264
    {
265
        $unserialized = unserialize($str);
266
267
        list(
268
            $this->preSerializeMethods,
269
            $this->postSerializeMethods,
270
            $this->postDeserializeMethods,
271
            $this->xmlRootName,
272
            $this->xmlRootNamespace,
273
            $this->xmlNamespaces,
274
            $this->accessorOrder,
275
            $this->customOrder,
276
            $this->discriminatorDisabled,
277
            $this->discriminatorBaseClass,
278
            $this->discriminatorFieldName,
279
            $this->discriminatorValue,
280
            $this->discriminatorMap,
281
            $this->discriminatorGroups,
282
            $parentStr
283
            ) = $unserialized;
284
285
        if (isset($unserialized['discriminatorGroups'])) {
286
            $this->discriminatorGroups = $unserialized['discriminatorGroups'];
287
        }
288
        if (isset($unserialized['usingExpression'])) {
289
            $this->usingExpression = $unserialized['usingExpression'];
290
        }
291
292
        if (isset($unserialized['xmlDiscriminatorAttribute'])) {
293
            $this->xmlDiscriminatorAttribute = $unserialized['xmlDiscriminatorAttribute'];
294
        }
295
296
        if (isset($unserialized['xmlDiscriminatorNamespace'])) {
297
            $this->xmlDiscriminatorNamespace = $unserialized['xmlDiscriminatorNamespace'];
298
        }
299
300
        if (isset($unserialized['xmlDiscriminatorCData'])) {
301
            $this->xmlDiscriminatorCData = $unserialized['xmlDiscriminatorCData'];
302
        }
303
304
        if (isset($unserialized['isList'])) {
305
            $this->isList = $unserialized['isList'];
306
        }
307
        parent::unserialize($parentStr);
308
    }
309
310 274
    private function sortProperties()
311
    {
312 274
        switch ($this->accessorOrder) {
313 274
            case self::ACCESSOR_ORDER_UNDEFINED:
314
                $this->propertyMetadata = (new IdenticalPropertyOrderingStrategy())->order($this->propertyMetadata);
315
                break;
316
317 274
            case self::ACCESSOR_ORDER_ALPHABETICAL:
318 7
                $this->propertyMetadata = (new AlphabeticalPropertyOrderingStrategy())->order($this->propertyMetadata);
319 7
                break;
320
321 272
            case self::ACCESSOR_ORDER_CUSTOM:
322 34
                $this->propertyMetadata = (new CustomPropertyOrderingStrategy($this->customOrder))->order($this->propertyMetadata);
323 34
                break;
324
        }
325 274
    }
326
}
327