Completed
Pull Request — master (#1108)
by Vincent
19:47 queued 04:50
created

DateHandler::getDeserializationFormats()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 3
nop 1
dl 0
loc 7
ccs 0
cts 0
cp 0
crap 12
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 $defaultFormat;
20
21 329
    /**
22
     * @var \DateTimeZone
23 329
     */
24 329
    private $defaultTimezone;
25 329
26
    /**
27 329
     * @var bool
28
     */
29 329
    private $xmlCData;
30 329
31 329
    /**
32 329
     * {@inheritdoc}
33 329
     */
34
    public static function getSubscribingMethods()
35
    {
36
        $methods = [];
37 329
        $deserializationTypes = ['DateTime', 'DateTimeImmutable', 'DateInterval'];
38 329
        $serialisationTypes = ['DateTime', 'DateTimeImmutable', 'DateInterval'];
39 329
40 329
        foreach (['json', 'xml'] as $format) {
41 329
            foreach ($deserializationTypes as $type) {
42 329
                $methods[] = [
43
                    'type' => $type,
44
                    'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
45
                    'format' => $format,
46
                ];
47 329
            }
48
49
            foreach ($serialisationTypes as $type) {
50 334
                $methods[] = [
51
                    'type' => $type,
52 334
                    'format' => $format,
53 334
                    'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
54 334
                    'method' => 'serialize' . $type,
55 334
                ];
56
            }
57 19
        }
58
59
        return $methods;
60
    }
61
62
    public function __construct(string $defaultFormat = \DateTime::ATOM, string $defaultTimezone = 'UTC', bool $xmlCData = true)
63 19
    {
64 2
        $this->defaultFormat = $defaultFormat;
65
        $this->defaultTimezone = new \DateTimeZone($defaultTimezone);
66
        $this->xmlCData = $xmlCData;
67 17
    }
68 17
69 2
    /**
70
     * @return \DOMCdataSection|\DOMText|mixed
71
     */
72 15
    private function serializeDateTimeInterface(
73
        SerializationVisitorInterface $visitor,
74
        \DateTimeInterface $date,
75 15
        array $type,
76
        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

76
        /** @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...
77 15
    ) {
78
        if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) {
79
            return $visitor->visitSimpleString($date->format($this->getFormat($type)), $type);
80 4
        }
81
82
        $format = $this->getFormat($type);
83
        if ('U' === $format) {
84
            return $visitor->visitInteger((int) $date->format($format), $type);
85
        }
86 4
87
        return $visitor->visitString($date->format($this->getFormat($type)), $type);
88
    }
89 2
90
    /**
91 2
     * @param array $type
92
     *
93 2
     * @return \DOMCdataSection|\DOMText|mixed
94
     */
95
    public function serializeDateTime(SerializationVisitorInterface $visitor, \DateTime $date, array $type, SerializationContext $context)
96
    {
97 2
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
98
    }
99
100 7
    /**
101
     * @param array $type
102 7
     *
103 7
     * @return \DOMCdataSection|\DOMText|mixed
104
     */
105
    public function serializeDateTimeImmutable(
106 5
        SerializationVisitorInterface $visitor,
107
        \DateTimeImmutable $date,
108 5
        array $type,
109
        SerializationContext $context
110
    ) {
111
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
112 5
    }
113
114
    /**
115 1
     * @param array $type
116
     *
117 1
     * @return \DOMCdataSection|\DOMText|mixed
118
     */
119
    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

119
    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...
120
    {
121 1
        $iso8601DateIntervalString = $this->format($date);
122
123
        if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) {
124 1
            return $visitor->visitSimpleString($iso8601DateIntervalString, $type);
125
        }
126 1
127
        return $visitor->visitString($iso8601DateIntervalString, $type);
128
    }
129
130 1
    /**
131
     * @param mixed $data
132
     */
133 11
    private function isDataXmlNull($data): bool
134
    {
135 11
        $attributes = $data->attributes('xsi', true);
136 2
        return isset($attributes['nil'][0]) && 'true' === (string) $attributes['nil'][0];
137
    }
138
139 9
    /**
140
     * @param mixed $data
141
     * @param array $type
142 4
     */
143
    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

143
    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...
144 4
    {
145
        if ($this->isDataXmlNull($data)) {
146
            return null;
147
        }
148 4
149
        return $this->parseDateTime($data, $type);
150
    }
151 1
152
    /**
153 1
     * @param mixed $data
154
     * @param array $type
155
     */
156
    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

156
    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...
157 1
    {
158
        if ($this->isDataXmlNull($data)) {
159
            return null;
160 19
        }
161
162 19
        return $this->parseDateTime($data, $type, true);
163 19
    }
164
165 19
    /**
166 5
     * @param mixed $data
167
     * @param array $type
168 14
     */
169
    public function deserializeDateIntervalFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval
0 ignored issues
show
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

169
    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...
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

169
    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...
170
    {
171 19
        if ($this->isDataXmlNull($data)) {
172
            return null;
173
        }
174
175 19
        return $this->parseDateInterval((string) $data);
176 4
    }
177
178
    /**
179 19
     * @param mixed $data
180
     * @param array $type
181
     */
182 2
    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

182
    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...
183
    {
184 2
        if (null === $data) {
185
            return null;
186 2
        }
187
188
        return $this->parseDateTime($data, $type);
189
    }
190
191 2
    /**
192
     * @param mixed $data
193
     * @param array $type
194
     */
195
    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

195
    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...
196
    {
197
        if (null === $data) {
198 19
            return null;
199
        }
200 19
201 3
        return $this->parseDateTime($data, $type, true);
202
    }
203 17
204 9
    /**
205
     * @param mixed $data
206 10
     * @param array $type
207
     */
208
    public function deserializeDateIntervalFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval
0 ignored issues
show
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

208
    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...
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

208
    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...
209
    {
210
        if (null === $data) {
211
            return null;
212
        }
213 19
214
        return $this->parseDateInterval($data);
215 19
    }
216
217
    /**
218
     * @param mixed $data
219
     * @param array $type
220
     */
221
    private function parseDateTime($data, array $type, bool $immutable = false): \DateTimeInterface
222 3
    {
223
        $timezone = !empty($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone;
224 3
        $formats = $this->getDeserializationFormats($type);
225
226 3
        $formatTried = [];
227 1
        foreach ($formats as $format) {
228
            if ($immutable) {
229
                $datetime = \DateTimeImmutable::createFromFormat($format, (string) $data, $timezone);
230 3
            } else {
231
                $datetime = \DateTime::createFromFormat($format, (string) $data, $timezone);
232
            }
233
234 3
            if (false !== $datetime) {
235 1
                if ('U' === $format) {
236
                    $datetime = $datetime->setTimezone($timezone);
237
                }
238 3
239 3
                return $datetime;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $datetime could return the type false which is incompatible with the type-hinted return DateTimeInterface. Consider adding an additional type-check to rule them out.
Loading history...
240
            }
241
242 3
            $formatTried[] = $format;
243 1
        }
244
245
        throw new RuntimeException(sprintf(
246 3
            'Invalid datetime "%s", expected one of the format %s.',
247 3
            $data,
248
            '"' . implode('", "', $formatTried) . '"'
249
        ));
250 3
    }
251 1
252
    private function parseDateInterval(string $data): \DateInterval
253
    {
254 3
        $dateInterval = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $dateInterval is dead and can be removed.
Loading history...
255 1
        try {
256
            $dateInterval = new \DateInterval($data);
257
        } catch (\Throwable $e) {
258 3
            throw new RuntimeException(sprintf('Invalid dateinterval "%s", expected ISO 8601 format', $data), null, $e);
259
        }
260
261
        return $dateInterval;
262
    }
263
264
    /**
265
     * @param array $type
266
     */
267
    private function getDeserializationFormats(array $type): array
268
    {
269
        if (isset($type['params'][2])) {
270
            return is_array($type['params'][2]) ? $type['params'][2] : [$type['params'][2]];
271
        }
272
273
        return [$this->getFormat($type)];
274
    }
275
276
    /**
277
     * @param array $type
278
     */
279
    private function getFormat(array $type): string
280
    {
281
        return $type['params'][0] ?? $this->defaultFormat;
282
    }
283
284
    public function format(\DateInterval $dateInterval): string
285
    {
286
        $format = 'P';
287
288
        if (0 < $dateInterval->y) {
289
            $format .= $dateInterval->y . 'Y';
290
        }
291
292
        if (0 < $dateInterval->m) {
293
            $format .= $dateInterval->m . 'M';
294
        }
295
296
        if (0 < $dateInterval->d) {
297
            $format .= $dateInterval->d . 'D';
298
        }
299
300
        if (0 < $dateInterval->h || 0 < $dateInterval->i || 0 < $dateInterval->s) {
301
            $format .= 'T';
302
        }
303
304
        if (0 < $dateInterval->h) {
305
            $format .= $dateInterval->h . 'H';
306
        }
307
308
        if (0 < $dateInterval->i) {
309
            $format .= $dateInterval->i . 'M';
310
        }
311
312
        if (0 < $dateInterval->s) {
313
            $format .= $dateInterval->s . 'S';
314
        }
315
316
        if ('P' === $format) {
317
            $format = 'P0DT0S';
318
        }
319
320
        return $format;
321
    }
322
}
323