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
|
|||||||
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
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
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
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
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
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
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
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
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
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
|
|||||||
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 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.