Serializer::normalize()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 13
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 26
ccs 15
cts 15
cp 1
crap 6
rs 9.2222
1
<?php
2
3
namespace Bdf\Serializer;
4
5
use Bdf\Serializer\Context\DenormalizationContext;
6
use Bdf\Serializer\Context\NormalizationContext;
7
use Bdf\Serializer\Normalizer\NormalizerInterface;
8
use Bdf\Serializer\Normalizer\NormalizerLoaderInterface;
9
use Bdf\Serializer\Type\Type;
10
use Bdf\Serializer\Type\TypeFactory;
11
12
/**
13
 * Serializer
14
 *
15
 * @author  Seb
16
 *
17
 * @implements NormalizerInterface<mixed>
18
 */
19
class Serializer implements SerializerInterface, NormalizerInterface, BinarySerializerInterface
20
{
21
    /**
22
     * The loader of normalizers
23
     *
24
     * @var NormalizerLoaderInterface
25
     */
26
    private $loader;
27
28
    /**
29
     * @var array<string, mixed>|null
30
     */
31
    private $defaultDenormalizationOptions;
32
33
    /**
34
     * @var array<string, mixed>|null
35
     */
36
    private $defaultNormalizationOptions;
37
38
    /**
39
     * @param NormalizerLoaderInterface $loader
40
     * @param array<string, mixed>|null $defaultDenormalizationOptions Default options to use when denormalizing (i.e. convert serialized data to PHP data).
41
     * @param array<string, mixed>|null $defaultNormalizationOptions Default options to use when normalizing (i.e. convert PHP data to serialized data).
42
     */
43 198
    public function __construct(NormalizerLoaderInterface $loader, ?array $defaultDenormalizationOptions = null, ?array $defaultNormalizationOptions = null)
44
    {
45 198
        $this->loader = $loader;
46 198
        $this->defaultDenormalizationOptions = $defaultDenormalizationOptions;
47 198
        $this->defaultNormalizationOptions = $defaultNormalizationOptions;
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 30
    public function serialize($data, $format, array $context = [])
54
    {
55
        switch ($format) {
56 30
            case 'json':
57 28
                return $this->toJson($data, $context);
58
59 2
            case 'binary':
60
                return $this->toBinary($data, $context);
61
62
            default:
63 2
                return $this->toArray($data, $context);
64
        }
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 34
    public function toJson($data, array $context = [])
71
    {
72 34
        $context = $this->normalizationContext($context);
73
74 34
        return json_encode($this->normalize($data, $context), $context->option('json_options', 0));
0 ignored issues
show
Bug introduced by
0 of type integer is incompatible with the type Bdf\Serializer\Context\T expected by parameter $default of Bdf\Serializer\Context\Context::option(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

74
        return json_encode($this->normalize($data, $context), $context->option('json_options', /** @scrutinizer ignore-type */ 0));
Loading history...
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public function toBinary($data, array $context = [])
81
    {
82
        return igbinary_serialize($this->normalize($data, $this->normalizationContext($context)));
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88 82
    public function toArray($data, array $context = [])
89
    {
90 82
        return $this->normalize($data, $this->normalizationContext($context));
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96 116
    public function normalize($data, NormalizationContext $context)
97
    {
98 116
        if (null === $data || is_scalar($data)) {
99 104
            return $data;
100
        }
101
102 114
        if (is_array($data)) {
103 14
            $normalized = [];
104
105 14
            foreach ($data as $key => $value) {
106 14
                $normalized[$key] = $this->normalize($value, $context);
107
            }
108
109 14
            return $normalized;
110
        }
111
112 112
        $normalized = $this->loader->getNormalizer($data)->normalize($data, $context);
113
114 106
        if ($context->includeMetaType()) {
115 12
            return [
116 12
                '@type' => get_class($data),
117 12
                'data'  => $normalized,
118 12
            ];
119
        }
120
121 94
        return $normalized;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 8
    public function deserialize($data, $type, $format, array $context = [])
128
    {
129
        switch ($format) {
130 8
            case 'json':
131 6
                return $this->fromJson($data, $type, $context);
132
133 2
            case 'binary':
134
                return $this->fromBinary($data, $type, $context);
135
136
            default:
137 2
                return $this->fromArray($data, $type, $context);
138
        }
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 74
    public function fromArray(array $data, $type, array $context = [])
145
    {
146 74
        return $this->denormalize($data, TypeFactory::createType($type), $this->denormalizationContext($context));
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 12
    public function fromJson(string $json, $type, array $context = [])
153
    {
154 12
        $context = $this->denormalizationContext($context);
155
156 12
        return $this->denormalize(json_decode($json, true, 512, $context->option('json_options', 0)), TypeFactory::createType($type), $context);
0 ignored issues
show
Bug introduced by
0 of type integer is incompatible with the type Bdf\Serializer\Context\T expected by parameter $default of Bdf\Serializer\Context\Context::option(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
        return $this->denormalize(json_decode($json, true, 512, $context->option('json_options', /** @scrutinizer ignore-type */ 0)), TypeFactory::createType($type), $context);
Loading history...
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function fromBinary(string $raw, $type, array $context = [])
163
    {
164
        return $this->denormalize(igbinary_unserialize($raw), TypeFactory::createType($type), $this->denormalizationContext($context));
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     *
170
     * @template T
171
     * @param mixed $data
172
     * @param Type<T> $type
173
     * @return T|T[]
174
     */
175 86
    public function denormalize($data, Type $type, DenormalizationContext $context)
176
    {
177 86
        if (!is_scalar($data) && !is_array($data)) {
178 10
            return $data;
179
        }
180
181 86
        $type = $type->hint($data);
182
183 86
        if ($type->isArray()) {
184 26
            $denormalized = [];
185
186 26
            foreach ((array)$data as $key => $value) {
187 26
                $denormalized[$key] = $this->denormalize($value, $type->subType() ?? TypeFactory::mixedType(), $context);
188
            }
189
190 26
            return $denormalized;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $denormalized returns the type array which is incompatible with the return type mandated by Bdf\Serializer\Normalize...nterface::denormalize() of Bdf\Serializer\Normalizer\T.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
191
        }
192
193 86
        if ($type->isBuildin()) {
194 64
            return $type->convert($data);
195
        }
196
197
        /**
198
         * @var NormalizerInterface<T> $normalizer
199
         * @psalm-suppress ArgumentTypeCoercion
200
         */
201 82
        $normalizer = $this->loader->getNormalizer($type->name());
202
203 80
        return $normalizer->denormalize($data, $type, $context);
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209 2
    public function supports(string $className): bool
210
    {
211 2
        return true;
212
    }
213
214
    /**
215
     * Get the normalizer loader
216
     *
217
     * @return NormalizerLoaderInterface
218
     */
219 14
    public function getLoader(): NormalizerLoaderInterface
220
    {
221 14
        return $this->loader;
222
    }
223
224
    /**
225
     * Create a new denormalization context, overriding the default context
226
     *
227
     * @param array<string, mixed> $context
228
     * @return DenormalizationContext
229
     */
230 86
    private function denormalizationContext(array $context): DenormalizationContext
231
    {
232 86
        if ($this->defaultDenormalizationOptions === null) {
233 82
            return new DenormalizationContext($this, $context);
234
        }
235
236 4
        $contextObj = new DenormalizationContext($this, $this->defaultDenormalizationOptions);
237
238 4
        return $context ? $contextObj->duplicate($context) : $contextObj;
239
    }
240
241
    /**
242
     * Create a new normalization context, overriding the default context
243
     *
244
     * @param array<string, mixed> $context
245
     * @return NormalizationContext
246
     */
247 116
    private function normalizationContext(array $context): NormalizationContext
248
    {
249 116
        if ($this->defaultNormalizationOptions === null) {
250 112
            return new NormalizationContext($this, $context);
251
        }
252
253 4
        $contextObj = new NormalizationContext($this, $this->defaultNormalizationOptions);
254
255
        // Use duplicate instead of merge to handle options aliases
256 4
        return $context ? $contextObj->duplicate($context) : $contextObj;
257
    }
258
}
259