Passed
Push — master ( 362503...624768 )
by Asmir
10:47 queued 05:21
created

DateHandler::deserializeDateTimeImmutableFromXml()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 3
dl 0
loc 7
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Handler;
6
7
use JMS\Serializer\Exception\RuntimeException;
8
use JMS\Serializer\GraphNavigatorInterface;
9
use JMS\Serializer\SerializationContext;
10
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
11
use JMS\Serializer\Visitor\SerializationVisitorInterface;
12
use JMS\Serializer\XmlSerializationVisitor;
13
14
final class DateHandler implements SubscribingHandlerInterface
15
{
16
    /**
17
     * @var string
18
     */
19
    private $defaultSerializationFormat;
20
21 329
    /**
22
     * @var array<string>
23 329
     */
24 329
    private $defaultDeserializationFormats;
25 329
26
    /**
27 329
     * @var \DateTimeZone
28
     */
29 329
    private $defaultTimezone;
30 329
31 329
    /**
32 329
     * @var bool
33 329
     */
34
    private $xmlCData;
35
36
    /**
37 329
     * {@inheritdoc}
38 329
     */
39 329
    public static function getSubscribingMethods()
40 329
    {
41 329
        $methods = [];
42 329
        $types = [\DateTime::class, \DateTimeImmutable::class, \DateInterval::class];
43
44
        foreach (['json', 'xml'] as $format) {
45
            foreach ($types as $type) {
46
                $methods[] = [
47 329
                    'type' => $type,
48
                    'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
49
                    'format' => $format,
50 334
                ];
51
                $methods[] = [
52 334
                    'type' => $type,
53 334
                    'format' => $format,
54 334
                    'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
55 334
                    'method' => 'serialize' . $type,
56
                ];
57 19
            }
58
59
            $methods[] = [
60
                'type' => \DateTimeInterface::class,
61
                'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
62
                'format' => $format,
63 19
                'method' => 'deserializeDateTimeFrom' . ucfirst($format),
64 2
            ];
65
66
            $methods[] = [
67 17
                'type' => \DateTimeInterface::class,
68 17
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
69 2
                'format' => $format,
70
                'method' => 'serializeDateTimeInterface',
71
            ];
72 15
        }
73
74
        return $methods;
75 15
    }
76
77 15
    /**
78
     * @param array<string> $defaultDeserializationFormats
79
     */
80 4
    public function __construct(
81
        string $defaultFormat = \DateTime::ATOM,
82
        string $defaultTimezone = 'UTC',
83
        bool $xmlCData = true,
84
        array $defaultDeserializationFormats = []
85
    ) {
86 4
        $this->defaultSerializationFormat = $defaultFormat;
87
        $this->defaultDeserializationFormats = [] === $defaultDeserializationFormats ? [$defaultFormat] : $defaultDeserializationFormats;
88
        $this->defaultTimezone = new \DateTimeZone($defaultTimezone);
89 2
        $this->xmlCData = $xmlCData;
90
    }
91 2
92
    /**
93 2
     * @return \DOMCdataSection|\DOMText|mixed
94
     */
95
    public function serializeDateTimeInterface(
96
        SerializationVisitorInterface $visitor,
97 2
        \DateTimeInterface $date,
98
        array $type,
99
        SerializationContext $context
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

99
        /** @scrutinizer ignore-unused */ SerializationContext $context

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100 7
    ) {
101
        if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) {
102 7
            return $visitor->visitSimpleString($date->format($this->getSerializationFormat($type)), $type);
103 7
        }
104
105
        $format = $this->getSerializationFormat($type);
106 5
        if ('U' === $format) {
107
            return $visitor->visitInteger((int) $date->format($format), $type);
108 5
        }
109
110
        return $visitor->visitString($date->format($this->getSerializationFormat($type)), $type);
111
    }
112 5
113
    /**
114
     * @param array $type
115 1
     *
116
     * @return \DOMCdataSection|\DOMText|mixed
117 1
     */
118
    public function serializeDateTime(SerializationVisitorInterface $visitor, \DateTime $date, array $type, SerializationContext $context)
119
    {
120
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
121 1
    }
122
123
    /**
124 1
     * @param array $type
125
     *
126 1
     * @return \DOMCdataSection|\DOMText|mixed
127
     */
128
    public function serializeDateTimeImmutable(
129
        SerializationVisitorInterface $visitor,
130 1
        \DateTimeImmutable $date,
131
        array $type,
132
        SerializationContext $context
133 11
    ) {
134
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
135 11
    }
136 2
137
    /**
138
     * @param array $type
139 9
     *
140
     * @return \DOMCdataSection|\DOMText|mixed
141
     */
142 4
    public function serializeDateInterval(SerializationVisitorInterface $visitor, \DateInterval $date, array $type, SerializationContext $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

142
    public function serializeDateInterval(SerializationVisitorInterface $visitor, \DateInterval $date, array $type, /** @scrutinizer ignore-unused */ SerializationContext $context)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143
    {
144 4
        $iso8601DateIntervalString = $this->format($date);
145
146
        if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) {
147
            return $visitor->visitSimpleString($iso8601DateIntervalString, $type);
148 4
        }
149
150
        return $visitor->visitString($iso8601DateIntervalString, $type);
151 1
    }
152
153 1
    /**
154
     * @param mixed $data
155
     */
156
    private function isDataXmlNull($data): bool
157 1
    {
158
        $attributes = $data->attributes('xsi', true);
159
160 19
        return isset($attributes['nil'][0]) && 'true' === (string) $attributes['nil'][0];
161
    }
162 19
163 19
    /**
164
     * @param mixed $data
165 19
     * @param array $type
166 5
     */
167
    public function deserializeDateTimeFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

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

167
    public function deserializeDateTimeFromXml(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
168 14
    {
169
        if ($this->isDataXmlNull($data)) {
170
            return null;
171 19
        }
172
173
        return $this->parseDateTime($data, $type);
174
    }
175 19
176 4
    /**
177
     * @param mixed $data
178
     * @param array $type
179 19
     */
180
    public function deserializeDateTimeImmutableFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

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

180
    public function deserializeDateTimeImmutableFromXml(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
181
    {
182 2
        if ($this->isDataXmlNull($data)) {
183
            return null;
184 2
        }
185
186 2
        return $this->parseDateTime($data, $type, true);
187
    }
188
189
    /**
190
     * @param mixed $data
191 2
     * @param array $type
192
     */
193
    public function deserializeDateIntervalFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

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

193
    public function deserializeDateIntervalFromXml(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

193
    public function deserializeDateIntervalFromXml(DeserializationVisitorInterface $visitor, $data, /** @scrutinizer ignore-unused */ array $type): ?\DateInterval

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
194
    {
195
        if ($this->isDataXmlNull($data)) {
196
            return null;
197
        }
198 19
199
        return $this->parseDateInterval((string) $data);
200 19
    }
201 3
202
    /**
203 17
     * @param mixed $data
204 9
     * @param array $type
205
     */
206 10
    public function deserializeDateTimeFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

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

206
    public function deserializeDateTimeFromJson(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
207
    {
208
        if (null === $data) {
209
            return null;
210
        }
211
212
        return $this->parseDateTime($data, $type);
213 19
    }
214
215 19
    /**
216
     * @param mixed $data
217
     * @param array $type
218
     */
219
    public function deserializeDateTimeImmutableFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

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

219
    public function deserializeDateTimeImmutableFromJson(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
220
    {
221
        if (null === $data) {
222 3
            return null;
223
        }
224 3
225
        return $this->parseDateTime($data, $type, true);
226 3
    }
227 1
228
    /**
229
     * @param mixed $data
230 3
     * @param array $type
231
     */
232
    public function deserializeDateIntervalFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

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

232
    public function deserializeDateIntervalFromJson(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

232
    public function deserializeDateIntervalFromJson(DeserializationVisitorInterface $visitor, $data, /** @scrutinizer ignore-unused */ array $type): ?\DateInterval

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
233
    {
234 3
        if (null === $data) {
235 1
            return null;
236
        }
237
238 3
        return $this->parseDateInterval($data);
239 3
    }
240
241
    /**
242 3
     * @param mixed $data
243 1
     * @param array $type
244
     */
245
    private function parseDateTime($data, array $type, bool $immutable = false): \DateTimeInterface
246 3
    {
247 3
        $timezone = !empty($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone;
248
        $formats = $this->getDeserializationFormats($type);
249
250 3
        $formatTried = [];
251 1
        foreach ($formats as $format) {
252
            if ($immutable) {
253
                $datetime = \DateTimeImmutable::createFromFormat($format, (string) $data, $timezone);
254 3
            } else {
255 1
                $datetime = \DateTime::createFromFormat($format, (string) $data, $timezone);
256
            }
257
258 3
            if (false !== $datetime) {
259
                if ('U' === $format) {
260
                    $datetime = $datetime->setTimezone($timezone);
261
                }
262
263
                return $datetime;
264
            }
265
266
            $formatTried[] = $format;
267
        }
268
269
        throw new RuntimeException(sprintf(
270
            'Invalid datetime "%s", expected one of the format %s.',
271
            $data,
272
            '"' . implode('", "', $formatTried) . '"',
273
        ));
274
    }
275
276
    private function parseDateInterval(string $data): \DateInterval
277
    {
278
        $dateInterval = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $dateInterval is dead and can be removed.
Loading history...
279
        try {
280
            $f = 0.0;
281
            if (preg_match('~\.\d+~', $data, $match)) {
282
                $data = str_replace($match[0], '', $data);
283
                $f = (float) $match[0];
284
            }
285
286
            $dateInterval = new \DateInterval($data);
287
            $dateInterval->f = $f;
288
        } catch (\Throwable $e) {
289
            throw new RuntimeException(sprintf('Invalid dateinterval "%s", expected ISO 8601 format', $data), 0, $e);
290
        }
291
292
        return $dateInterval;
293
    }
294
295
    private function getDeserializationFormats(array $type): array
296
    {
297
        if (isset($type['params'][2])) {
298
            return is_array($type['params'][2]) ? $type['params'][2] : [$type['params'][2]];
299
        }
300
301
        if (isset($type['params'][0])) {
302
            return [$type['params'][0]];
303
        }
304
305
        return $this->defaultDeserializationFormats;
306
    }
307
308
    private function getSerializationFormat(array $type): string
309
    {
310
        return $type['params'][0] ?? $this->defaultSerializationFormat;
311
    }
312
313
    public function format(\DateInterval $dateInterval): string
314
    {
315
        $format = 'P';
316
317
        if (0 < $dateInterval->y) {
318
            $format .= $dateInterval->y . 'Y';
319
        }
320
321
        if (0 < $dateInterval->m) {
322
            $format .= $dateInterval->m . 'M';
323
        }
324
325
        if (0 < $dateInterval->d) {
326
            $format .= $dateInterval->d . 'D';
327
        }
328
329
        if (0 < $dateInterval->h || 0 < $dateInterval->i || 0 < $dateInterval->s) {
330
            $format .= 'T';
331
        }
332
333
        if (0 < $dateInterval->h) {
334
            $format .= $dateInterval->h . 'H';
335
        }
336
337
        if (0 < $dateInterval->i) {
338
            $format .= $dateInterval->i . 'M';
339
        }
340
341
        if (0 < $dateInterval->s) {
342
            $format .= $dateInterval->s . 'S';
343
        }
344
345
        if ('P' === $format) {
346
            $format = 'P0DT0S';
347
        }
348
349
        return $format;
350
    }
351
}
352