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