Passed
Branch master (bf85d9)
by Johannes
05:40
created

ClassMetadata::addPostSerializeMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer\Metadata;
20
21
use JMS\Serializer\Exception\InvalidArgumentException;
22
use JMS\Serializer\Exception\LogicException;
23
use Metadata\MergeableClassMetadata;
24
use Metadata\MergeableInterface;
25
use Metadata\MethodMetadata;
26
use Metadata\PropertyMetadata as BasePropertyMetadata;
27
28
/**
29
 * Class Metadata used to customize the serialization process.
30
 *
31
 * @author Johannes M. Schmitt <[email protected]>
32
 */
33
class ClassMetadata extends MergeableClassMetadata
34
{
35
    const ACCESSOR_ORDER_UNDEFINED = 'undefined';
36
    const ACCESSOR_ORDER_ALPHABETICAL = 'alphabetical';
37
    const ACCESSOR_ORDER_CUSTOM = 'custom';
38
39
    /** @var \ReflectionMethod[] */
40
    public $preSerializeMethods = array();
41
42
    /** @var \ReflectionMethod[] */
43
    public $postSerializeMethods = array();
44
45
    /** @var \ReflectionMethod[] */
46
    public $postDeserializeMethods = array();
47
48
    public $xmlRootName;
49
    public $xmlRootNamespace;
50
    public $xmlNamespaces = array();
51
    public $accessorOrder;
52
    public $customOrder;
53
    public $usingExpression = false;
54
55
56
    public $discriminatorDisabled = false;
57
    public $discriminatorBaseClass;
58
    public $discriminatorFieldName;
59
    public $discriminatorValue;
60
    public $discriminatorMap = array();
61
    public $discriminatorGroups = array();
62
63
    public $xmlDiscriminatorAttribute = false;
64
    public $xmlDiscriminatorCData = true;
65
    public $xmlDiscriminatorNamespace;
66
67 28
    public function setDiscriminator($fieldName, array $map, array $groups = array())
68
    {
69 28
        if (empty($fieldName)) {
70
            throw new InvalidArgumentException('The $fieldName cannot be empty.');
71
        }
72
73 28
        if (empty($map)) {
74
            throw new InvalidArgumentException('The discriminator map cannot be empty.');
75
        }
76
77 28
        $this->discriminatorBaseClass = $this->name;
78 28
        $this->discriminatorFieldName = $fieldName;
79 28
        $this->discriminatorMap = $map;
80 28
        $this->discriminatorGroups = $groups;
81 28
    }
82
83
    /**
84
     * Sets the order of properties in the class.
85
     *
86
     * @param string $order
87
     * @param array $customOrder
88
     *
89
     * @throws InvalidArgumentException When the accessor order is not valid
90
     * @throws InvalidArgumentException When the custom order is not valid
91
     */
92 39
    public function setAccessorOrder($order, array $customOrder = array())
93
    {
94 39
        if (!in_array($order, array(self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM), true)) {
95
            throw new InvalidArgumentException(sprintf('The accessor order "%s" is invalid.', $order));
96
        }
97
98 39
        foreach ($customOrder as $name) {
99 34
            if (!\is_string($name)) {
100 34
                throw new InvalidArgumentException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
101
            }
102
        }
103
104 39
        $this->accessorOrder = $order;
105 39
        $this->customOrder = array_flip($customOrder);
106 39
        $this->sortProperties();
107 39
    }
108
109 258
    public function addPropertyMetadata(BasePropertyMetadata $metadata)
110
    {
111 258
        parent::addPropertyMetadata($metadata);
112 258
        $this->sortProperties();
113 258
        if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) {
114 26
            $this->usingExpression = true;
115
        }
116 258
    }
117
118 2
    public function addPreSerializeMethod(MethodMetadata $method)
119
    {
120 2
        $this->preSerializeMethods[] = $method;
121 2
    }
122
123 2
    public function addPostSerializeMethod(MethodMetadata $method)
124
    {
125 2
        $this->postSerializeMethods[] = $method;
126 2
    }
127
128 4
    public function addPostDeserializeMethod(MethodMetadata $method)
129
    {
130 4
        $this->postDeserializeMethods[] = $method;
131 4
    }
132
133 24
    public function merge(MergeableInterface $object)
134
    {
135 24
        if (!$object instanceof ClassMetadata) {
136
            throw new InvalidArgumentException('$object must be an instance of ClassMetadata.');
137
        }
138 24
        parent::merge($object);
139
140 24
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
141 24
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
142 24
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
143 24
        $this->xmlRootName = $object->xmlRootName;
144 24
        $this->xmlRootNamespace = $object->xmlRootNamespace;
145 24
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
146
147 24
        if ($object->accessorOrder) {
148 2
            $this->accessorOrder = $object->accessorOrder;
149 2
            $this->customOrder = $object->customOrder;
150
        }
151
152 24
        if ($object->discriminatorFieldName && $this->discriminatorFieldName) {
153
            throw new LogicException(sprintf(
154
                '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.',
155
                $object->name,
156
                $this->discriminatorBaseClass,
157
                $this->discriminatorBaseClass
158
            ));
159 24
        } elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) {
160 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
161 2
            $this->discriminatorMap = $object->discriminatorMap;
162
        }
163
164 24
        if ($object->discriminatorDisabled !== null) {
165 24
            $this->discriminatorDisabled = $object->discriminatorDisabled;
166
        }
167
168 24
        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...
169 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
170 2
            $this->discriminatorMap = $object->discriminatorMap;
171 2
            $this->discriminatorBaseClass = $object->discriminatorBaseClass;
172
        }
173
174 24
        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...
175 14
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
176
                throw new LogicException(sprintf(
177
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
178
                    $this->name,
179
                    $this->discriminatorBaseClass
180
                ));
181
            }
182
183 14
            $this->discriminatorValue = $typeValue;
184
185 14
            if (isset($this->propertyMetadata[$this->discriminatorFieldName])
186 14
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
187
            ) {
188
                throw new LogicException(sprintf(
189
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
190
                    $this->discriminatorFieldName,
191
                    $this->discriminatorBaseClass,
192
                    $this->name
193
                ));
194
            }
195
196 14
            $discriminatorProperty = new StaticPropertyMetadata(
197 14
                $this->name,
198 14
                $this->discriminatorFieldName,
199 14
                $typeValue,
200 14
                $this->discriminatorGroups
201
            );
202 14
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
203 14
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
204 14
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
205 14
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
206 14
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
207
        }
208
209 24
        $this->sortProperties();
210 24
    }
211
212 28
    public function registerNamespace($uri, $prefix = null)
213
    {
214 28
        if (!\is_string($uri)) {
215
            throw new InvalidArgumentException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
216
        }
217
218 28
        if ($prefix !== null) {
219 28
            if (!\is_string($prefix)) {
220 28
                throw new InvalidArgumentException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
221
            }
222
        } else {
223 2
            $prefix = "";
224
        }
225
226 28
        $this->xmlNamespaces[$prefix] = $uri;
227
228 28
    }
229
230
    public function serialize()
231
    {
232
        $this->sortProperties();
233
234
        return serialize(array(
235
            $this->preSerializeMethods,
236
            $this->postSerializeMethods,
237
            $this->postDeserializeMethods,
238
            $this->xmlRootName,
239
            $this->xmlRootNamespace,
240
            $this->xmlNamespaces,
241
            $this->accessorOrder,
242
            $this->customOrder,
243
            $this->discriminatorDisabled,
244
            $this->discriminatorBaseClass,
245
            $this->discriminatorFieldName,
246
            $this->discriminatorValue,
247
            $this->discriminatorMap,
248
            $this->discriminatorGroups,
249
            parent::serialize(),
250
            'discriminatorGroups' => $this->discriminatorGroups,
251
            'xmlDiscriminatorAttribute' => $this->xmlDiscriminatorAttribute,
252
            'xmlDiscriminatorCData' => $this->xmlDiscriminatorCData,
253
            'usingExpression' => $this->usingExpression,
254
            'xmlDiscriminatorNamespace' => $this->xmlDiscriminatorNamespace,
255
        ));
256
    }
257
258
    public function unserialize($str)
259
    {
260
        $unserialized = unserialize($str);
261
262
        list(
263
            $this->preSerializeMethods,
264
            $this->postSerializeMethods,
265
            $this->postDeserializeMethods,
266
            $this->xmlRootName,
267
            $this->xmlRootNamespace,
268
            $this->xmlNamespaces,
269
            $this->accessorOrder,
270
            $this->customOrder,
271
            $this->discriminatorDisabled,
272
            $this->discriminatorBaseClass,
273
            $this->discriminatorFieldName,
274
            $this->discriminatorValue,
275
            $this->discriminatorMap,
276
            $this->discriminatorGroups,
277
            $parentStr
278
            ) = $unserialized;
279
280
        if (isset($unserialized['discriminatorGroups'])) {
281
            $this->discriminatorGroups = $unserialized['discriminatorGroups'];
282
        }
283
        if (isset($unserialized['usingExpression'])) {
284
            $this->usingExpression = $unserialized['usingExpression'];
285
        }
286
287
        if (isset($unserialized['xmlDiscriminatorAttribute'])) {
288
            $this->xmlDiscriminatorAttribute = $unserialized['xmlDiscriminatorAttribute'];
289
        }
290
291
        if (isset($unserialized['xmlDiscriminatorNamespace'])) {
292
            $this->xmlDiscriminatorNamespace = $unserialized['xmlDiscriminatorNamespace'];
293
        }
294
295
        if (isset($unserialized['xmlDiscriminatorCData'])) {
296
            $this->xmlDiscriminatorCData = $unserialized['xmlDiscriminatorCData'];
297
        }
298
299
        parent::unserialize($parentStr);
300
    }
301
302 261
    private function sortProperties()
303
    {
304 261
        switch ($this->accessorOrder) {
305 261
            case self::ACCESSOR_ORDER_ALPHABETICAL:
306 7
                ksort($this->propertyMetadata);
307 7
                break;
308
309 259
            case self::ACCESSOR_ORDER_CUSTOM:
310 34
                $order = $this->customOrder;
311 34
                $currentSorting = $this->propertyMetadata ? array_combine(array_keys($this->propertyMetadata), range(1, \count($this->propertyMetadata))) : [];
312 34
                uksort($this->propertyMetadata, function ($a, $b) use ($order, $currentSorting) {
313 34
                    $existsA = isset($order[$a]);
314 34
                    $existsB = isset($order[$b]);
315
316 34
                    if (!$existsA && !$existsB) {
317 4
                        return $currentSorting[$a] - $currentSorting[$b];
318
                    }
319
320 33
                    if (!$existsA) {
321 4
                        return 1;
322
                    }
323
324 29
                    if (!$existsB) {
325 1
                        return -1;
326
                    }
327
328 28
                    return $order[$a] < $order[$b] ? -1 : 1;
329 34
                });
330 34
                break;
331
        }
332 261
    }
333
}
334