Passed
Pull Request — master (#938)
by Michael
03:10
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
60
    public $discriminatorDisabled = false;
61
    public $discriminatorBaseClass;
62
    public $discriminatorFieldName;
63
    public $discriminatorValue;
64
    public $discriminatorMap = [];
65
    public $discriminatorGroups = [];
66
67
    public $xmlDiscriminatorAttribute = false;
68
    public $xmlDiscriminatorCData = true;
69
    public $xmlDiscriminatorNamespace;
70
71 32
    public function setDiscriminator($fieldName, array $map, array $groups = [])
72
    {
73 32
        if (empty($fieldName)) {
74
            throw new InvalidArgumentException('The $fieldName cannot be empty.');
75
        }
76
77 32
        if (empty($map)) {
78
            throw new InvalidArgumentException('The discriminator map cannot be empty.');
79
        }
80
81 32
        $this->discriminatorBaseClass = $this->name;
82 32
        $this->discriminatorFieldName = $fieldName;
83 32
        $this->discriminatorMap = $map;
84 32
        $this->discriminatorGroups = $groups;
85 32
    }
86
87
    /**
88
     * Sets the order of properties in the class.
89
     *
90
     * @param string $order
91
     * @param array $customOrder
92
     *
93
     * @throws InvalidArgumentException When the accessor order is not valid
94
     * @throws InvalidArgumentException When the custom order is not valid
95
     */
96 39
    public function setAccessorOrder($order, array $customOrder = [])
97
    {
98 39
        if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) {
99
            throw new InvalidArgumentException(sprintf('The accessor order "%s" is invalid.', $order));
100
        }
101
102 39
        foreach ($customOrder as $name) {
103 34
            if (!\is_string($name)) {
104 34
                throw new InvalidArgumentException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
105
            }
106
        }
107
108 39
        $this->accessorOrder = $order;
109 39
        $this->customOrder = array_flip($customOrder);
110 39
        $this->sortProperties();
111 39
    }
112
113 263
    public function addPropertyMetadata(BasePropertyMetadata $metadata)
114
    {
115 263
        parent::addPropertyMetadata($metadata);
116 263
        $this->sortProperties();
117 263
        if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) {
118 26
            $this->usingExpression = true;
119
        }
120 263
    }
121
122 2
    public function addPreSerializeMethod(MethodMetadata $method)
123
    {
124 2
        $this->preSerializeMethods[] = $method;
125 2
    }
126
127 2
    public function addPostSerializeMethod(MethodMetadata $method)
128
    {
129 2
        $this->postSerializeMethods[] = $method;
130 2
    }
131
132 4
    public function addPostDeserializeMethod(MethodMetadata $method)
133
    {
134 4
        $this->postDeserializeMethods[] = $method;
135 4
    }
136
137 25
    public function merge(MergeableInterface $object)
138
    {
139 25
        if (!$object instanceof ClassMetadata) {
140
            throw new InvalidArgumentException('$object must be an instance of ClassMetadata.');
141
        }
142 25
        parent::merge($object);
143
144 25
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
145 25
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
146 25
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
147 25
        $this->xmlRootName = $object->xmlRootName;
148 25
        $this->xmlRootNamespace = $object->xmlRootNamespace;
149 25
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
150
151 25
        if ($object->accessorOrder) {
152 2
            $this->accessorOrder = $object->accessorOrder;
153 2
            $this->customOrder = $object->customOrder;
154
        }
155
156 25
        if ($object->discriminatorFieldName && $this->discriminatorFieldName) {
157
            throw new LogicException(sprintf(
158
                '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.',
159
                $object->name,
160
                $this->discriminatorBaseClass,
161
                $this->discriminatorBaseClass
162
            ));
163 25
        } elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) {
164 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
165 2
            $this->discriminatorMap = $object->discriminatorMap;
166
        }
167
168 25
        if ($object->discriminatorDisabled !== null) {
169 25
            $this->discriminatorDisabled = $object->discriminatorDisabled;
170
        }
171
172 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...
173 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
174 2
            $this->discriminatorMap = $object->discriminatorMap;
175 2
            $this->discriminatorBaseClass = $object->discriminatorBaseClass;
176
        }
177
178 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...
179 15
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
180
                throw new LogicException(sprintf(
181
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
182
                    $this->name,
183
                    $this->discriminatorBaseClass
184
                ));
185
            }
186
187 15
            $this->discriminatorValue = $typeValue;
188
189 15
            if (isset($this->propertyMetadata[$this->discriminatorFieldName])
190 15
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
191
            ) {
192
                throw new LogicException(sprintf(
193
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
194
                    $this->discriminatorFieldName,
195
                    $this->discriminatorBaseClass,
196
                    $this->name
197
                ));
198
            }
199
200 15
            $discriminatorProperty = new StaticPropertyMetadata(
201 15
                $this->name,
202 15
                $this->discriminatorFieldName,
203 15
                $typeValue,
204 15
                $this->discriminatorGroups
205
            );
206 15
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
207 15
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
208 15
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
209 15
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
210 15
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
211
        }
212
213 25
        $this->sortProperties();
214 25
    }
215
216 31
    public function registerNamespace($uri, $prefix = null)
217
    {
218 31
        if (!\is_string($uri)) {
219
            throw new InvalidArgumentException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
220
        }
221
222 31
        if ($prefix !== null) {
223 31
            if (!\is_string($prefix)) {
224 31
                throw new InvalidArgumentException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
225
            }
226
        } else {
227 2
            $prefix = "";
228
        }
229
230 31
        $this->xmlNamespaces[$prefix] = $uri;
231 31
    }
232
233
    public function serialize()
234
    {
235
        $this->sortProperties();
236
237
        return serialize([
238
            $this->preSerializeMethods,
239
            $this->postSerializeMethods,
240
            $this->postDeserializeMethods,
241
            $this->xmlRootName,
242
            $this->xmlRootNamespace,
243
            $this->xmlNamespaces,
244
            $this->accessorOrder,
245
            $this->customOrder,
246
            $this->discriminatorDisabled,
247
            $this->discriminatorBaseClass,
248
            $this->discriminatorFieldName,
249
            $this->discriminatorValue,
250
            $this->discriminatorMap,
251
            $this->discriminatorGroups,
252
            parent::serialize(),
253
            'discriminatorGroups' => $this->discriminatorGroups,
254
            'xmlDiscriminatorAttribute' => $this->xmlDiscriminatorAttribute,
255
            'xmlDiscriminatorCData' => $this->xmlDiscriminatorCData,
256
            'usingExpression' => $this->usingExpression,
257
            'xmlDiscriminatorNamespace' => $this->xmlDiscriminatorNamespace,
258
        ]);
259
    }
260
261
    public function unserialize($str)
262
    {
263
        $unserialized = unserialize($str);
264
265
        list(
266
            $this->preSerializeMethods,
267
            $this->postSerializeMethods,
268
            $this->postDeserializeMethods,
269
            $this->xmlRootName,
270
            $this->xmlRootNamespace,
271
            $this->xmlNamespaces,
272
            $this->accessorOrder,
273
            $this->customOrder,
274
            $this->discriminatorDisabled,
275
            $this->discriminatorBaseClass,
276
            $this->discriminatorFieldName,
277
            $this->discriminatorValue,
278
            $this->discriminatorMap,
279
            $this->discriminatorGroups,
280
            $parentStr
281
            ) = $unserialized;
282
283
        if (isset($unserialized['discriminatorGroups'])) {
284
            $this->discriminatorGroups = $unserialized['discriminatorGroups'];
285
        }
286
        if (isset($unserialized['usingExpression'])) {
287
            $this->usingExpression = $unserialized['usingExpression'];
288
        }
289
290
        if (isset($unserialized['xmlDiscriminatorAttribute'])) {
291
            $this->xmlDiscriminatorAttribute = $unserialized['xmlDiscriminatorAttribute'];
292
        }
293
294
        if (isset($unserialized['xmlDiscriminatorNamespace'])) {
295
            $this->xmlDiscriminatorNamespace = $unserialized['xmlDiscriminatorNamespace'];
296
        }
297
298
        if (isset($unserialized['xmlDiscriminatorCData'])) {
299
            $this->xmlDiscriminatorCData = $unserialized['xmlDiscriminatorCData'];
300
        }
301
302
        parent::unserialize($parentStr);
303
    }
304
305 267
    private function sortProperties()
306
    {
307 267
        switch ($this->accessorOrder) {
308 267
            case self::ACCESSOR_ORDER_UNDEFINED:
309
                $this->propertyMetadata = (new IdenticalPropertyOrderingStrategy())->order($this->propertyMetadata);
310
                break;
311
312 267
            case self::ACCESSOR_ORDER_ALPHABETICAL:
313 7
                $this->propertyMetadata = (new AlphabeticalPropertyOrderingStrategy())->order($this->propertyMetadata);
314 7
                break;
315
316 265
            case self::ACCESSOR_ORDER_CUSTOM:
317 34
                $this->propertyMetadata = (new CustomPropertyOrderingStrategy($this->customOrder))->order($this->propertyMetadata);
318 34
                break;
319
        }
320 267
    }
321
}
322