Completed
Push — master ( 37cc39...3535a4 )
by Johannes
16s queued 12s
created

ClassMetadata::sortProperties()   D

Complexity

Conditions 9
Paths 4

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
c 2
b 2
f 0
dl 0
loc 31
rs 4.909
cc 9
eloc 19
nc 4
nop 0
1
<?php
2
3
/*
4
 * Copyright 2013 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
60
    public function setDiscriminator($fieldName, array $map)
61
    {
62
        if (empty($fieldName)) {
63
            throw new \InvalidArgumentException('The $fieldName cannot be empty.');
64
        }
65
66
        if (empty($map)) {
67
            throw new \InvalidArgumentException('The discriminator map cannot be empty.');
68
        }
69
70
        $this->discriminatorBaseClass = $this->name;
71
        $this->discriminatorFieldName = $fieldName;
72
        $this->discriminatorMap = $map;
73
    }
74
75
    /**
76
     * Sets the order of properties in the class.
77
     *
78
     * @param string $order
79
     * @param array $customOrder
80
     *
81
     * @throws InvalidArgumentException When the accessor order is not valid
82
     * @throws InvalidArgumentException When the custom order is not valid
83
     */
84
    public function setAccessorOrder($order, array $customOrder = array())
85
    {
86
        if ( ! in_array($order, array(self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM), true)) {
87
            throw new InvalidArgumentException(sprintf('The accessor order "%s" is invalid.', $order));
88
        }
89
90
        foreach ($customOrder as $name) {
91
            if ( ! is_string($name)) {
92
                throw new InvalidArgumentException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
93
            }
94
        }
95
96
        $this->accessorOrder = $order;
97
        $this->customOrder = array_flip($customOrder);
98
        $this->sortProperties();
99
    }
100
101
    public function addPropertyMetadata(BasePropertyMetadata $metadata)
102
    {
103
        parent::addPropertyMetadata($metadata);
104
        $this->sortProperties();
105
    }
106
107
    public function addPreSerializeMethod(MethodMetadata $method)
108
    {
109
        $this->preSerializeMethods[] = $method;
110
    }
111
112
    public function addPostSerializeMethod(MethodMetadata $method)
113
    {
114
        $this->postSerializeMethods[] = $method;
115
    }
116
117
    public function addPostDeserializeMethod(MethodMetadata $method)
118
    {
119
        $this->postDeserializeMethods[] = $method;
120
    }
121
122
    /**
123
     * @param integer $direction
124
     * @param string|integer $format
125
     * @param string $methodName
126
     */
127
    public function addHandlerCallback($direction, $format, $methodName)
128
    {
129
        $this->handlerCallbacks[$direction][$format] = $methodName;
130
    }
131
132
    public function merge(MergeableInterface $object)
133
    {
134
        if ( ! $object instanceof ClassMetadata) {
135
            throw new InvalidArgumentException('$object must be an instance of ClassMetadata.');
136
        }
137
        parent::merge($object);
138
139
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
140
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
141
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
142
        $this->xmlRootName = $object->xmlRootName;
143
        $this->xmlRootNamespace = $object->xmlRootNamespace;
144
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
145
146
        // Handler methods are taken from the outer class completely.
147
        $this->handlerCallbacks = $object->handlerCallbacks;
148
149
        if ($object->accessorOrder) {
150
            $this->accessorOrder = $object->accessorOrder;
151
            $this->customOrder = $object->customOrder;
152
        }
153
154
        if ($object->discriminatorFieldName && $this->discriminatorFieldName) {
155
            throw new \LogicException(sprintf(
156
                '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.',
157
                $object->name,
158
                $this->discriminatorBaseClass,
159
                $this->discriminatorBaseClass
160
            ));
161
        }
162
163
        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...
164
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
165
                throw new \LogicException(sprintf(
166
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
167
                    $this->name,
168
                    $this->discriminatorBaseClass
169
                ));
170
            }
171
172
            $this->discriminatorValue = $typeValue;
173
174
            if (isset($this->propertyMetadata[$this->discriminatorFieldName])
175
                    && ! $this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata) {
176
                throw new \LogicException(sprintf(
177
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
178
                    $this->discriminatorFieldName,
179
                    $this->discriminatorBaseClass,
180
                    $this->name
181
                ));
182
            }
183
184
            $discriminatorProperty = new StaticPropertyMetadata(
185
                $this->name,
186
                $this->discriminatorFieldName,
187
                $typeValue
188
            );
189
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
190
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
191
        }
192
193
        $this->sortProperties();
194
    }
195
196
    public function registerNamespace($uri, $prefix = null)
197
    {
198
        if ( ! is_string($uri)) {
199
            throw new InvalidArgumentException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
200
        }
201
202
        if ($prefix !== null) {
203
            if ( ! is_string($prefix)) {
204
                throw new InvalidArgumentException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
205
            }
206
        } else {
207
            $prefix = "";
208
        }
209
210
        $this->xmlNamespaces[$prefix] = $uri;
211
212
    }
213
214
    public function serialize()
215
    {
216
        $this->sortProperties();
217
218
        return serialize(array(
219
            $this->preSerializeMethods,
220
            $this->postSerializeMethods,
221
            $this->postDeserializeMethods,
222
            $this->xmlRootName,
223
            $this->xmlRootNamespace,
224
            $this->xmlNamespaces,
225
            $this->accessorOrder,
226
            $this->customOrder,
227
            $this->handlerCallbacks,
228
            $this->discriminatorDisabled,
229
            $this->discriminatorBaseClass,
230
            $this->discriminatorFieldName,
231
            $this->discriminatorValue,
232
            $this->discriminatorMap,
233
            parent::serialize(),
234
        ));
235
    }
236
237
    public function unserialize($str)
238
    {
239
        list(
240
            $this->preSerializeMethods,
241
            $this->postSerializeMethods,
242
            $this->postDeserializeMethods,
243
            $this->xmlRootName,
244
            $this->xmlRootNamespace,
245
            $this->xmlNamespaces,
246
            $this->accessorOrder,
247
            $this->customOrder,
248
            $this->handlerCallbacks,
249
            $this->discriminatorDisabled,
250
            $this->discriminatorBaseClass,
251
            $this->discriminatorFieldName,
252
            $this->discriminatorValue,
253
            $this->discriminatorMap,
254
            $parentStr
255
        ) = unserialize($str);
256
257
        parent::unserialize($parentStr);
258
    }
259
260
    private function sortProperties()
261
    {
262
        switch ($this->accessorOrder) {
263
            case self::ACCESSOR_ORDER_ALPHABETICAL:
264
                ksort($this->propertyMetadata);
265
                break;
266
267
            case self::ACCESSOR_ORDER_CUSTOM:
268
                $order = $this->customOrder;
269
                $currentSorting = $this->propertyMetadata ? array_combine(array_keys($this->propertyMetadata), range(1, count($this->propertyMetadata))) : [];
270
                uksort($this->propertyMetadata, function($a, $b) use ($order, $currentSorting) {
271
                    $existsA = isset($order[$a]);
272
                    $existsB = isset($order[$b]);
273
274
                    if ( ! $existsA && ! $existsB) {
275
                        return $currentSorting[$a] - $currentSorting[$b];
276
                    }
277
278
                    if ( ! $existsA) {
279
                        return 1;
280
                    }
281
282
                    if ( ! $existsB) {
283
                        return -1;
284
                    }
285
286
                    return $order[$a] < $order[$b] ? -1 : 1;
287
                });
288
                break;
289
        }
290
    }
291
}
292