2121use function array_is_list ;
2222use function count ;
2323use function current ;
24- use function error_log ;
24+ use function end ;
2525use function filter_var ;
2626use function is_array ;
2727use function is_bool ;
3030use function is_iterable ;
3131use function is_numeric ;
3232use function is_object ;
33+ use function Koded \Stdlib \error_log ;
3334use function Koded \Stdlib \json_serialize ;
3435use function Koded \Stdlib \json_unserialize ;
3536use function key ;
3637use function preg_match ;
3738use function str_contains ;
39+ use function str_replace ;
3840use function str_starts_with ;
3941use function substr ;
4042use function trim ;
43+ use function xml_parse_into_struct ;
44+ use function xml_parser_create ;
4145
4246/**
4347 * Class XmlSerializer is heavily modified Symfony encoder (XmlEncoder).
@@ -71,21 +75,20 @@ final public function val(): string
7175 }
7276
7377 /**
74- * @param iterable $data
75- *
78+ * @param iterable $value
7679 * @return string|null XML
7780 */
78- public function serialize (mixed $ data ): string |null
81+ public function serialize (mixed $ value ): string |null
7982 {
8083 $ document = new DOMDocument ('1.0 ' , 'UTF-8 ' );
8184 $ document ->formatOutput = false ;
82- if (is_iterable ($ data )) {
85+ if (is_iterable ($ value )) {
8386 $ root = $ document ->createElement ($ this ->root );
8487 $ document ->appendChild ($ root );
8588 $ document ->createAttributeNS ('http://www.w3.org/2001/XMLSchema-instance ' , 'xsi: ' . $ this ->root );
86- $ this ->buildXml ($ document , $ root , $ data );
89+ $ this ->buildXml ($ document , $ root , $ value );
8790 } else {
88- $ this ->appendNode ($ document , $ document , $ data , $ this ->root , null );
91+ $ this ->appendNode ($ document , $ document , $ value , $ this ->root , null );
8992 }
9093 return trim ($ document ->saveXML ());
9194 }
@@ -94,11 +97,13 @@ public function serialize(mixed $data): string|null
9497 * Unserialize a proper XML document into array, scalar value or NULL.
9598 *
9699 * @param string $xml XML
97- *
98100 * @return mixed scalar|array|null
99101 */
100102 public function unserialize (string $ xml ): mixed
101103 {
104+ if (empty ($ xml = trim ($ xml ))) {
105+ return null ;
106+ }
102107 try {
103108 $ document = new DOMDocument ('1.0 ' , 'UTF-8 ' );
104109 $ document ->preserveWhiteSpace = false ;
@@ -111,15 +116,14 @@ public function unserialize(string $xml): mixed
111116 : [];
112117
113118 } catch (Throwable $ e ) {
114- error_log ( PHP_EOL . " [ { $ e -> getLine ()} ]: " . $ e ->getMessage ());
119+ $ this -> logUnserializeError ( __METHOD__ , $ e ->getMessage (), $ xml );
115120 return null ;
116121 }
117122 }
118123
119- private function buildXml (
120- DOMDocument $ document ,
121- DOMNode $ parent ,
122- iterable $ data ): void
124+ private function buildXml (DOMDocument $ document ,
125+ DOMNode $ parent ,
126+ iterable $ data ): void
123127 {
124128 foreach ($ data as $ key => $ val ) {
125129 $ isKeyNumeric = is_numeric ($ key );
@@ -192,7 +196,6 @@ private function parseXmlAttributes(DOMNode $node): array
192196
193197 /**
194198 * @param DOMNode $node
195- *
196199 * @return array|string|null
197200 * @throws \Exception
198201 */
@@ -228,12 +231,11 @@ private function parseXmlValue(DOMNode $node): mixed
228231 * @param string $name
229232 * @param string|null $key
230233 */
231- private function appendNode (
232- DOMDocument $ document ,
233- DOMNode $ parent ,
234- mixed $ data ,
235- string $ name ,
236- ?string $ key ): void
234+ private function appendNode (DOMDocument $ document ,
235+ DOMNode $ parent ,
236+ mixed $ data ,
237+ string $ name ,
238+ ?string $ key ): void
237239 {
238240 $ element = $ document ->createElement ($ name );
239241 if (null !== $ key ) {
@@ -254,7 +256,7 @@ private function appendNode(
254256 $ element ->setAttribute ('xsi:nil ' , 'true ' );
255257 } elseif ($ data instanceof DateTimeInterface) {
256258 $ element ->setAttribute ('type ' , 'xsd:dateTime ' );
257- $ element ->appendChild ($ document ->createTextNode ($ data ->format (DateTimeInterface::ISO8601 )));
259+ $ element ->appendChild ($ document ->createTextNode ($ data ->format (DateTimeInterface::RFC3339 )));
258260 } elseif (is_object ($ data )) {
259261 $ element ->setAttribute ('type ' , 'xsd:object ' );
260262 $ element ->appendChild ($ document ->createCDATASection (json_serialize ($ data )));
@@ -331,4 +333,18 @@ private function extractValuesFromChildNodes(DOMNode $node, array &$value): void
331333 }
332334 }
333335 }
336+
337+ public function logUnserializeError (string $ method ,
338+ string $ message ,
339+ string $ xml ): void
340+ {
341+ $ parser = xml_parser_create ();
342+ xml_parse_into_struct ($ parser , $ xml , $ values );
343+ $ last = end ($ values );
344+ unset($ last ['type ' ], $ last ['level ' ]);
345+ error_log ($ method ,
346+ str_replace ('DOMDocument::loadXML(): ' , '' , $ message ),
347+ 'hint: ' . json_serialize ($ last ?: ['<XML> ' => $ xml ])
348+ );
349+ }
334350}
0 commit comments