Issues (163)

src/Handler/DateHandler.php (11 issues)

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
        $types = [\DateTime::class, \DateTimeImmutable::class, \DateInterval::class];
38 329
39 329
        foreach (['json', 'xml'] as $format) {
40 329
            foreach ($types as $type) {
41 329
                $methods[] = [
42 329
                    'type' => $type,
43
                    'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
44
                    'format' => $format,
45
                ];
46
                $methods[] = [
47 329
                    'type' => $type,
48
                    'format' => $format,
49
                    'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
50 334
                    'method' => 'serialize' . $type,
51
                ];
52 334
            }
53 334
54 334
            $methods[] = [
55 334
                'type' => \DateTimeInterface::class,
56
                'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
57 19
                'format' => $format,
58
                'method' => 'deserializeDateTimeFrom' . ucfirst($format),
59
            ];
60
61
            $methods[] = [
62
                'type' => \DateTimeInterface::class,
63 19
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
64 2
                'format' => $format,
65
                'method' => 'serializeDateTimeInterface',
66
            ];
67 17
        }
68 17
69 2
        return $methods;
70
    }
71
72 15
    public function __construct(string $defaultFormat = \DateTime::ATOM, string $defaultTimezone = 'UTC', bool $xmlCData = true)
73
    {
74
        $this->defaultFormat = $defaultFormat;
75 15
        $this->defaultTimezone = new \DateTimeZone($defaultTimezone);
76
        $this->xmlCData = $xmlCData;
77 15
    }
78
79
    /**
80 4
     * @return \DOMCdataSection|\DOMText|mixed
81
     */
82
    public function serializeDateTimeInterface(
83
        SerializationVisitorInterface $visitor,
84
        \DateTimeInterface $date,
85
        array $type,
86 4
        SerializationContext $context
0 ignored issues
show
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

86
        /** @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...
87
    ) {
88
        if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) {
89 2
            return $visitor->visitSimpleString($date->format($this->getFormat($type)), $type);
90
        }
91 2
92
        $format = $this->getFormat($type);
93 2
        if ('U' === $format) {
94
            return $visitor->visitInteger((int) $date->format($format), $type);
95
        }
96
97 2
        return $visitor->visitString($date->format($this->getFormat($type)), $type);
98
    }
99
100 7
    /**
101
     * @param array $type
102 7
     *
103 7
     * @return \DOMCdataSection|\DOMText|mixed
104
     */
105
    public function serializeDateTime(SerializationVisitorInterface $visitor, \DateTime $date, array $type, SerializationContext $context)
106 5
    {
107
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
108 5
    }
109
110
    /**
111
     * @param array $type
112 5
     *
113
     * @return \DOMCdataSection|\DOMText|mixed
114
     */
115 1
    public function serializeDateTimeImmutable(
116
        SerializationVisitorInterface $visitor,
117 1
        \DateTimeImmutable $date,
118
        array $type,
119
        SerializationContext $context
120
    ) {
121 1
        return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
122
    }
123
124 1
    /**
125
     * @param array $type
126 1
     *
127
     * @return \DOMCdataSection|\DOMText|mixed
128
     */
129
    public function serializeDateInterval(SerializationVisitorInterface $visitor, \DateInterval $date, array $type, SerializationContext $context)
0 ignored issues
show
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

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

154
    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...
155
    {
156
        if ($this->isDataXmlNull($data)) {
157 1
            return null;
158
        }
159
160 19
        return $this->parseDateTime($data, $type);
161
    }
162 19
163 19
    /**
164
     * @param mixed $data
165 19
     * @param array $type
166 5
     */
167
    public function deserializeDateTimeImmutableFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
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 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...
168 14
    {
169
        if ($this->isDataXmlNull($data)) {
170
            return null;
171 19
        }
172
173
        return $this->parseDateTime($data, $type, true);
174
    }
175 19
176 4
    /**
177
     * @param mixed $data
178
     * @param array $type
179 19
     */
180
    public function deserializeDateIntervalFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval
0 ignored issues
show
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 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...
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

180
    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...
181
    {
182 2
        if ($this->isDataXmlNull($data)) {
183
            return null;
184 2
        }
185
186 2
        return $this->parseDateInterval((string) $data);
187
    }
188
189
    /**
190
     * @param mixed $data
191 2
     * @param array $type
192
     */
193
    public function deserializeDateTimeFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
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 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...
194
    {
195
        if (null === $data) {
196
            return null;
197
        }
198 19
199
        return $this->parseDateTime($data, $type);
200 19
    }
201 3
202
    /**
203 17
     * @param mixed $data
204 9
     * @param array $type
205
     */
206 10
    public function deserializeDateTimeImmutableFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
0 ignored issues
show
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 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...
207
    {
208
        if (null === $data) {
209
            return null;
210
        }
211
212
        return $this->parseDateTime($data, $type, true);
213 19
    }
214
215 19
    /**
216
     * @param mixed $data
217
     * @param array $type
218
     */
219
    public function deserializeDateIntervalFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateInterval
0 ignored issues
show
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 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...
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

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