Completed
Pull Request — master (#1108)
by Vincent
13:10
created

DateHandler::getFormats()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 6
ccs 0
cts 0
cp 0
crap 12
rs 10
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
        $formats = $this->getFormats($type);
79
80 4
        foreach ($formats as $format) {
81
            $dateFormatted = $date->format($format);
82
83
            if (false !== $dateFormatted) {
84
                if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) {
85
                    return $visitor->visitSimpleString($dateFormatted, $type);
86 4
                }
87
88
                if ('U' === $format) {
89 2
                    return $visitor->visitInteger((int) $dateFormatted, $type);
90
                }
91 2
92
                return $visitor->visitString($dateFormatted, $type);
93 2
            }
94
        }
95
96
        throw new RuntimeException(sprintf('The date "%s" could not be formatted', $date));
0 ignored issues
show
Bug introduced by
$date of type DateTimeInterface is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

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

96
        throw new RuntimeException(sprintf('The date "%s" could not be formatted', /** @scrutinizer ignore-type */ $date));
Loading history...
97 2
    }
98
99
    /**
100 7
     * @param array $type
101
     *
102 7
     * @return \DOMCdataSection|\DOMText|mixed
103 7
     */
104
    public function serializeDateTime(SerializationVisitorInterface $visitor, \DateTime $date, array $type, SerializationContext $context)
105
    {
106 5
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
107
    }
108 5
109
    /**
110
     * @param array $type
111
     *
112 5
     * @return \DOMCdataSection|\DOMText|mixed
113
     */
114
    public function serializeDateTimeImmutable(
115 1
        SerializationVisitorInterface $visitor,
116
        \DateTimeImmutable $date,
117 1
        array $type,
118
        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 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

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

152
    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...
153 1
    {
154
        if ($this->isDataXmlNull($data)) {
155
            return null;
156
        }
157 1
158
        return $this->parseDateTime($data, $type);
159
    }
160 19
161
    /**
162 19
     * @param mixed $data
163 19
     * @param array $type
164
     */
165 19
    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

165
    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...
166 5
    {
167
        if ($this->isDataXmlNull($data)) {
168 14
            return null;
169
        }
170
171 19
        return $this->parseDateTime($data, $type, true);
172
    }
173
174
    /**
175 19
     * @param mixed $data
176 4
     * @param array $type
177
     */
178
    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

178
    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

178
    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...
179 19
    {
180
        if ($this->isDataXmlNull($data)) {
181
            return null;
182 2
        }
183
184 2
        return $this->parseDateInterval((string) $data);
185
    }
186 2
187
    /**
188
     * @param mixed $data
189
     * @param array $type
190
     */
191 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

191
    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...
192
    {
193
        if (null === $data) {
194
            return null;
195
        }
196
197
        return $this->parseDateTime($data, $type);
198 19
    }
199
200 19
    /**
201 3
     * @param mixed $data
202
     * @param array $type
203 17
     */
204 9
    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

204
    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...
205
    {
206 10
        if (null === $data) {
207
            return null;
208
        }
209
210
        return $this->parseDateTime($data, $type, true);
211
    }
212
213 19
    /**
214
     * @param mixed $data
215 19
     * @param array $type
216
     */
217
    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

217
    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

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