Completed
Push — master ( 81b9ba...d5ded5 )
by Asmir
04:29
created

ClassMetadata::setDiscriminator()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 15
c 0
b 0
f 0
ccs 8
cts 10
cp 0.8
rs 9.4285
cc 3
eloc 9
nc 3
nop 3
crap 3.072
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 Metadata\MergeableInterface;
23
use Metadata\MethodMetadata;
24
use Metadata\MergeableClassMetadata;
25
use Metadata\PropertyMetadata as BasePropertyMetadata;
26
27
/**
28
 * Class Metadata used to customize the serialization process.
29
 *
30
 * @author Johannes M. Schmitt <[email protected]>
31
 */
32
class ClassMetadata extends MergeableClassMetadata
33
{
34
    const ACCESSOR_ORDER_UNDEFINED = 'undefined';
35
    const ACCESSOR_ORDER_ALPHABETICAL = 'alphabetical';
36
    const ACCESSOR_ORDER_CUSTOM = 'custom';
37
38
    /** @var \ReflectionMethod[] */
39
    public $preSerializeMethods = array();
40
41
    /** @var \ReflectionMethod[] */
42
    public $postSerializeMethods = array();
43
44
    /** @var \ReflectionMethod[] */
45
    public $postDeserializeMethods = array();
46
47
    public $xmlRootName;
48
    public $xmlRootNamespace;
49
    public $xmlNamespaces = array();
50
    public $accessorOrder;
51
    public $customOrder;
52
    public $handlerCallbacks = array();
53
54
    public $discriminatorDisabled = false;
55
    public $discriminatorBaseClass;
56
    public $discriminatorFieldName;
57
    public $discriminatorValue;
58
    public $discriminatorMap = array();
59
    public $discriminatorGroups = array();
60
61 21
    public function setDiscriminator($fieldName, array $map, array $groups = array())
62
    {
63 21
        if (empty($fieldName)) {
64
            throw new \InvalidArgumentException('The $fieldName cannot be empty.');
65
        }
66
67 21
        if (empty($map)) {
68
            throw new \InvalidArgumentException('The discriminator map cannot be empty.');
69
        }
70
71 21
        $this->discriminatorBaseClass = $this->name;
72 21
        $this->discriminatorFieldName = $fieldName;
73 21
        $this->discriminatorMap = $map;
74 21
        $this->discriminatorGroups = $groups;
75 21
    }
76
77
    /**
78
     * Sets the order of properties in the class.
79
     *
80
     * @param string $order
81
     * @param array $customOrder
82
     *
83
     * @throws InvalidArgumentException When the accessor order is not valid
84
     * @throws InvalidArgumentException When the custom order is not valid
85
     */
86 20
    public function setAccessorOrder($order, array $customOrder = array())
87
    {
88 20
        if ( ! in_array($order, array(self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM), true)) {
89
            throw new InvalidArgumentException(sprintf('The accessor order "%s" is invalid.', $order));
90
        }
91
92 20
        foreach ($customOrder as $name) {
93 13
            if ( ! is_string($name)) {
94
                throw new InvalidArgumentException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
95
            }
96 20
        }
97
98 20
        $this->accessorOrder = $order;
99 20
        $this->customOrder = array_flip($customOrder);
100 20
        $this->sortProperties();
101 20
    }
102
103 234
    public function addPropertyMetadata(BasePropertyMetadata $metadata)
104
    {
105 234
        parent::addPropertyMetadata($metadata);
106 234
        $this->sortProperties();
107 234
    }
108
109 3
    public function addPreSerializeMethod(MethodMetadata $method)
110
    {
111 3
        $this->preSerializeMethods[] = $method;
112 3
    }
113
114 3
    public function addPostSerializeMethod(MethodMetadata $method)
115
    {
116 3
        $this->postSerializeMethods[] = $method;
117 3
    }
118
119 6
    public function addPostDeserializeMethod(MethodMetadata $method)
120
    {
121 6
        $this->postDeserializeMethods[] = $method;
122 6
    }
123
124
    /**
125
     * @param integer $direction
126
     * @param string|integer $format
127
     * @param string $methodName
128
     */
129 7
    public function addHandlerCallback($direction, $format, $methodName)
130
    {
131 7
        $this->handlerCallbacks[$direction][$format] = $methodName;
132 7
    }
133
134 26
    public function merge(MergeableInterface $object)
135
    {
136 26
        if ( ! $object instanceof ClassMetadata) {
137
            throw new InvalidArgumentException('$object must be an instance of ClassMetadata.');
138
        }
139 26
        parent::merge($object);
140
141 26
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
142 26
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
143 26
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
144 26
        $this->xmlRootName = $object->xmlRootName;
145 26
        $this->xmlRootNamespace = $object->xmlRootNamespace;
146 26
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
147
148
        // Handler methods are taken from the outer class completely.
149 26
        $this->handlerCallbacks = $object->handlerCallbacks;
150
151 26
        if ($object->accessorOrder) {
152 3
            $this->accessorOrder = $object->accessorOrder;
153 3
            $this->customOrder = $object->customOrder;
154 3
        }
155
156 26
        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 26
        } elseif ( ! $this->discriminatorFieldName && $object->discriminatorFieldName) {
164 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
165 2
            $this->discriminatorMap = $object->discriminatorMap;
166 2
        }
167
168 26
        if ($object->discriminatorDisabled !== null) {
169 26
            $this->discriminatorDisabled = $object->discriminatorDisabled;
170 26
        }
171
172 26
        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 2
        }
177
178 26
        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
                throw new \LogicException(sprintf(
192
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
193
                    $this->discriminatorFieldName,
194
                    $this->discriminatorBaseClass,
195
                    $this->name
196
                ));
197
            }
198
199 15
            $discriminatorProperty = new StaticPropertyMetadata(
200 15
                $this->name,
201 15
                $this->discriminatorFieldName,
202 15
                $typeValue,
203 15
                $this->discriminatorGroups
204 15
            );
205 15
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
206 15
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
207 15
        }
208
209 26
        $this->sortProperties();
210 26
    }
211
212 23
    public function registerNamespace($uri, $prefix = null)
213
    {
214 23
        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 23
        if ($prefix !== null) {
219 23
            if ( ! is_string($prefix)) {
220
                throw new InvalidArgumentException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
221
            }
222 23
        } else {
223 4
            $prefix = "";
224
        }
225
226 23
        $this->xmlNamespaces[$prefix] = $uri;
227
228 23
    }
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->handlerCallbacks,
244
            $this->discriminatorDisabled,
245
            $this->discriminatorBaseClass,
246
            $this->discriminatorFieldName,
247
            $this->discriminatorValue,
248
            $this->discriminatorMap,
249
            $this->discriminatorGroups,
250
            parent::serialize(),
251
            'discriminatorGroups' => $this->discriminatorGroups,
252
        ));
253
    }
254
255
    public function unserialize($str)
256
    {
257
        $deserializedData = unserialize($str);
258
259
        list(
260
            $this->preSerializeMethods,
261
            $this->postSerializeMethods,
262
            $this->postDeserializeMethods,
263
            $this->xmlRootName,
264
            $this->xmlRootNamespace,
265
            $this->xmlNamespaces,
266
            $this->accessorOrder,
267
            $this->customOrder,
268
            $this->handlerCallbacks,
269
            $this->discriminatorDisabled,
270
            $this->discriminatorBaseClass,
271
            $this->discriminatorFieldName,
272
            $this->discriminatorValue,
273
            $this->discriminatorMap,
274
            $this->discriminatorGroups,
275
            $parentStr
276
        ) = $deserializedData;
277
278
        if (isset($deserializedData['discriminatorGroups'])) {
279
            $this->discriminatorGroups = $deserializedData['discriminatorGroups'];
280
        }
281
282
        parent::unserialize($parentStr);
283
    }
284
285 234
    private function sortProperties()
286
    {
287 234
        switch ($this->accessorOrder) {
288 234
            case self::ACCESSOR_ORDER_ALPHABETICAL:
289 10
                ksort($this->propertyMetadata);
290 10
                break;
291
292 231
            case self::ACCESSOR_ORDER_CUSTOM:
293 13
                $order = $this->customOrder;
294 13
                $currentSorting = $this->propertyMetadata ? array_combine(array_keys($this->propertyMetadata), range(1, count($this->propertyMetadata))) : [];
295 13
                uksort($this->propertyMetadata, function($a, $b) use ($order, $currentSorting) {
296 13
                    $existsA = isset($order[$a]);
297 13
                    $existsB = isset($order[$b]);
298
299 13
                    if ( ! $existsA && ! $existsB) {
300 5
                        return $currentSorting[$a] - $currentSorting[$b];
301
                    }
302
303 12
                    if ( ! $existsA) {
304 5
                        return 1;
305
                    }
306
307 11
                    if ( ! $existsB) {
308 5
                        return -1;
309
                    }
310
311 6
                    return $order[$a] < $order[$b] ? -1 : 1;
312 13
                });
313 13
                break;
314 234
        }
315 234
    }
316
}
317