1<?php
2
3namespace Sabre\Xml\Serializer;
4
5use InvalidArgumentException;
6use Sabre\Xml\Writer;
7use Sabre\Xml\XmlSerializable;
8
9/**
10 * This file provides a number of 'serializer' helper functions.
11 *
12 * These helper functions can be used to easily xml-encode common PHP
13 * data structures, or can be placed in the $classMap.
14 */
15
16/**
17 * The 'enum' serializer writes simple list of elements.
18 *
19 * For example, calling:
20 *
21 * enum($writer, [
22 *   "{http://sabredav.org/ns}elem1",
23 *   "{http://sabredav.org/ns}elem2",
24 *   "{http://sabredav.org/ns}elem3",
25 *   "{http://sabredav.org/ns}elem4",
26 *   "{http://sabredav.org/ns}elem5",
27 * ]);
28 *
29 * Will generate something like this (if the correct namespace is declared):
30 *
31 * <s:elem1 />
32 * <s:elem2 />
33 * <s:elem3 />
34 * <s:elem4>content</s:elem4>
35 * <s:elem5 attr="val" />
36 *
37 * @param Writer $writer
38 * @param string[] $values
39 * @return void
40 */
41function enum(Writer $writer, array $values) {
42
43    foreach ($values as $value) {
44        $writer->writeElement($value);
45    }
46}
47
48/**
49 * The valueObject serializer turns a simple PHP object into a classname.
50 *
51 * Every public property will be encoded as an xml element with the same
52 * name, in the XML namespace as specified.
53 *
54 * Values that are set to null or an empty array are not serialized. To
55 * serialize empty properties, you must specify them as an empty string.
56 *
57 * @param Writer $writer
58 * @param object $valueObject
59 * @param string $namespace
60 */
61function valueObject(Writer $writer, $valueObject, $namespace) {
62    foreach (get_object_vars($valueObject) as $key => $val) {
63        if (is_array($val)) {
64            // If $val is an array, it has a special meaning. We need to
65            // generate one child element for each item in $val
66            foreach ($val as $child) {
67                $writer->writeElement('{' . $namespace . '}' . $key, $child);
68            }
69
70        } elseif ($val !== null) {
71            $writer->writeElement('{' . $namespace . '}' . $key, $val);
72        }
73    }
74}
75
76
77/**
78 * This serializer helps you serialize xml structures that look like
79 * this:
80 *
81 * <collection>
82 *    <item>...</item>
83 *    <item>...</item>
84 *    <item>...</item>
85 * </collection>
86 *
87 * In that previous example, this serializer just serializes the item element,
88 * and this could be called like this:
89 *
90 * repeatingElements($writer, $items, '{}item');
91 *
92 * @param Writer $writer
93 * @param array $items A list of items sabre/xml can serialize.
94 * @param string $childElementName Element name in clark-notation
95 * @return void
96 */
97function repeatingElements(Writer $writer, array $items, $childElementName) {
98
99    foreach ($items as $item) {
100        $writer->writeElement($childElementName, $item);
101    }
102
103}
104
105/**
106 * This function is the 'default' serializer that is able to serialize most
107 * things, and delegates to other serializers if needed.
108 *
109 * The standardSerializer supports a wide-array of values.
110 *
111 * $value may be a string or integer, it will just write out the string as text.
112 * $value may be an instance of XmlSerializable or Element, in which case it
113 *    calls it's xmlSerialize() method.
114 * $value may be a PHP callback/function/closure, in case we call the callback
115 *    and give it the Writer as an argument.
116 * $value may be a an object, and if it's in the classMap we automatically call
117 *    the correct serializer for it.
118 * $value may be null, in which case we do nothing.
119 *
120 * If $value is an array, the array must look like this:
121 *
122 * [
123 *    [
124 *       'name' => '{namespaceUri}element-name',
125 *       'value' => '...',
126 *       'attributes' => [ 'attName' => 'attValue' ]
127 *    ]
128 *    [,
129 *       'name' => '{namespaceUri}element-name2',
130 *       'value' => '...',
131 *    ]
132 * ]
133 *
134 * This would result in xml like:
135 *
136 * <element-name xmlns="namespaceUri" attName="attValue">
137 *   ...
138 * </element-name>
139 * <element-name2>
140 *   ...
141 * </element-name2>
142 *
143 * The value property may be any value standardSerializer supports, so you can
144 * nest data-structures this way. Both value and attributes are optional.
145 *
146 * Alternatively, you can also specify the array using this syntax:
147 *
148 * [
149 *    [
150 *       '{namespaceUri}element-name' => '...',
151 *       '{namespaceUri}element-name2' => '...',
152 *    ]
153 * ]
154 *
155 * This is excellent for simple key->value structures, and here you can also
156 * specify anything for the value.
157 *
158 * You can even mix the two array syntaxes.
159 *
160 * @param Writer $writer
161 * @param string|int|float|bool|array|object
162 * @return void
163 */
164function standardSerializer(Writer $writer, $value) {
165
166    if (is_scalar($value)) {
167
168        // String, integer, float, boolean
169        $writer->text($value);
170
171    } elseif ($value instanceof XmlSerializable) {
172
173        // XmlSerializable classes or Element classes.
174        $value->xmlSerialize($writer);
175
176    } elseif (is_object($value) && isset($writer->classMap[get_class($value)])) {
177
178        // It's an object which class appears in the classmap.
179        $writer->classMap[get_class($value)]($writer, $value);
180
181    } elseif (is_callable($value)) {
182
183        // A callback
184        $value($writer);
185
186    } elseif (is_null($value)) {
187
188        // nothing!
189
190    } elseif (is_array($value) && array_key_exists('name', $value)) {
191
192        // if the array had a 'name' element, we assume that this array
193        // describes a 'name' and optionally 'attributes' and 'value'.
194
195        $name = $value['name'];
196        $attributes = isset($value['attributes']) ? $value['attributes'] : [];
197        $value = isset($value['value']) ? $value['value'] : null;
198
199        $writer->startElement($name);
200        $writer->writeAttributes($attributes);
201        $writer->write($value);
202        $writer->endElement();
203
204    } elseif (is_array($value)) {
205
206        foreach ($value as $name => $item) {
207
208            if (is_int($name)) {
209
210                // This item has a numeric index. We just loop through the
211                // array and throw it back in the writer.
212                standardSerializer($writer, $item);
213
214            } elseif (is_string($name) && is_array($item) && isset($item['attributes'])) {
215
216                // The key is used for a name, but $item has 'attributes' and
217                // possibly 'value'
218                $writer->startElement($name);
219                $writer->writeAttributes($item['attributes']);
220                if (isset($item['value'])) {
221                    $writer->write($item['value']);
222                }
223                $writer->endElement();
224
225            } elseif (is_string($name)) {
226
227                // This was a plain key-value array.
228                $writer->startElement($name);
229                $writer->write($item);
230                $writer->endElement();
231
232            } else {
233
234                throw new InvalidArgumentException('The writer does not know how to serialize arrays with keys of type: ' . gettype($name));
235
236            }
237        }
238
239    } elseif (is_object($value)) {
240
241        throw new InvalidArgumentException('The writer cannot serialize objects of class: ' . get_class($value));
242
243    } else {
244
245        throw new InvalidArgumentException('The writer cannot serialize values of type: ' . gettype($value));
246
247    }
248
249}
250