Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\DynamoDb;
3
 
4
use Psr\Http\Message\StreamInterface;
5
 
6
/**
7
 * Marshals and unmarshals JSON documents and PHP arrays into DynamoDB items.
8
 */
9
class Marshaler
10
{
11
    /** @var array Default options to merge into provided options. */
12
    private static $defaultOptions = [
13
        'ignore_invalid'  => false,
14
        'nullify_invalid' => false,
15
        'wrap_numbers'    => false,
16
    ];
17
 
18
    /** @var array Marshaler options. */
19
    private $options;
20
 
21
    /**
22
     * Instantiates a DynamoDB Marshaler.
23
     *
24
     * The following options are valid.
25
     *
26
     * - ignore_invalid: (bool) Set to `true` if invalid values should be
27
     *   ignored (i.e., not included) during marshaling.
28
     * - nullify_invalid: (bool) Set to `true` if invalid values should be set
29
     *   to null.
30
     * - wrap_numbers: (bool) Set to `true` to wrap numbers with `NumberValue`
31
     *   objects during unmarshaling to preserve the precision.
32
     *
33
     * @param array $options Marshaler options
34
     */
35
    public function __construct(array $options = [])
36
    {
37
        $this->options = $options + self::$defaultOptions;
38
    }
39
 
40
    /**
41
     * Creates a special object to represent a DynamoDB binary (B) value.
42
     *
43
     * This helps disambiguate binary values from string (S) values.
44
     *
45
     * @param mixed $value A binary value compatible with Guzzle streams.
46
     *
47
     * @return BinaryValue
48
     * @see GuzzleHttp\Stream\Stream::factory
49
     */
50
    public function binary($value)
51
    {
52
        return new BinaryValue($value);
53
    }
54
 
55
    /**
56
     * Creates a special object to represent a DynamoDB number (N) value.
57
     *
58
     * This helps maintain the precision of large integer/float in PHP.
59
     *
60
     * @param string|int|float $value A number value.
61
     *
62
     * @return NumberValue
63
     */
64
    public function number($value)
65
    {
66
        return new NumberValue($value);
67
    }
68
 
69
    /**
70
     * Creates a special object to represent a DynamoDB set (SS/NS/BS) value.
71
     *
72
     * This helps disambiguate set values from list (L) values.
73
     *
74
     * @param array $values The values of the set.
75
     *
76
     * @return SetValue
77
     *
78
     */
79
    public function set(array $values)
80
    {
81
        return new SetValue($values);
82
    }
83
 
84
    /**
85
     * Marshal a JSON document from a string to a DynamoDB item.
86
     *
87
     * The result is an array formatted in the proper parameter structure
88
     * required by the DynamoDB API for items.
89
     *
90
     * @param string $json A valid JSON document.
91
     *
92
     * @return array Item formatted for DynamoDB.
93
     * @throws \InvalidArgumentException if the JSON is invalid.
94
     */
95
    public function marshalJson($json)
96
    {
97
        $data = json_decode($json);
98
        if (!($data instanceof \stdClass)) {
99
            throw new \InvalidArgumentException(
100
                'The JSON document must be valid and be an object at its root.'
101
            );
102
        }
103
 
104
        return current($this->marshalValue($data));
105
    }
106
 
107
    /**
108
     * Marshal a native PHP array of data to a DynamoDB item.
109
     *
110
     * The result is an array formatted in the proper parameter structure
111
     * required by the DynamoDB API for items.
112
     *
113
     * @param array|\stdClass $item An associative array of data.
114
     *
115
     * @return array Item formatted for DynamoDB.
116
     */
117
    public function marshalItem($item)
118
    {
119
        return current($this->marshalValue($item));
120
    }
121
 
122
    /**
123
     * Marshal a native PHP value into a DynamoDB attribute value.
124
     *
125
     * The result is an associative array that is formatted in the proper
126
     * `[TYPE => VALUE]` parameter structure required by the DynamoDB API.
127
     *
128
     * @param mixed $value A scalar, array, or `stdClass` value.
129
     *
130
     * @return array Attribute formatted for DynamoDB.
131
     * @throws \UnexpectedValueException if the value cannot be marshaled.
132
     */
133
    public function marshalValue($value)
134
    {
135
        $type = gettype($value);
136
 
137
        // Handle string values.
138
        if ($type === 'string') {
139
            return ['S' => $value];
140
        }
141
 
142
        // Handle number values.
143
        if ($type === 'integer'
144
            || $type === 'double'
145
            || $value instanceof NumberValue
146
        ) {
147
            return ['N' => (string) $value];
148
        }
149
 
150
        // Handle boolean values.
151
        if ($type === 'boolean') {
152
            return ['BOOL' => $value];
153
        }
154
 
155
        // Handle null values.
156
        if ($type === 'NULL') {
157
            return ['NULL' => true];
158
        }
159
 
160
        // Handle set values.
161
        if ($value instanceof SetValue) {
162
            if (count($value) === 0) {
163
                return $this->handleInvalid('empty sets are invalid');
164
            }
165
            $previousType = null;
166
            $data = [];
167
            foreach ($value as $v) {
168
                $marshaled = $this->marshalValue($v);
169
                $setType = key($marshaled);
170
                if (!$previousType) {
171
                    $previousType = $setType;
172
                } elseif ($setType !== $previousType) {
173
                    return $this->handleInvalid('sets must be uniform in type');
174
                }
175
                $data[] = current($marshaled);
176
            }
177
 
178
            return [$previousType . 'S' => array_values(array_unique($data))];
179
        }
180
 
181
        // Handle list and map values.
182
        $dbType = 'L';
183
        if ($value instanceof \stdClass) {
184
            $type = 'array';
185
            $dbType = 'M';
186
        }
187
        if ($type === 'array' || $value instanceof \Traversable) {
188
            $data = [];
189
            $index = 0;
190
            foreach ($value as $k => $v) {
191
                if ($v = $this->marshalValue($v)) {
192
                    $data[$k] = $v;
193
                    if ($dbType === 'L' && (!is_int($k) || $k != $index++)) {
194
                        $dbType = 'M';
195
                    }
196
                }
197
            }
198
            return [$dbType => $data];
199
        }
200
 
201
        // Handle binary values.
202
        if (is_resource($value) || $value instanceof StreamInterface) {
203
            $value = $this->binary($value);
204
        }
205
        if ($value instanceof BinaryValue) {
206
            return ['B' => (string) $value];
207
        }
208
 
209
        // Handle invalid values.
210
        return $this->handleInvalid('encountered unexpected value');
211
    }
212
 
213
    /**
214
     * Unmarshal a document (item) from a DynamoDB operation result into a JSON
215
     * document string.
216
     *
217
     * @param array $data            Item/document from a DynamoDB result.
218
     * @param int   $jsonEncodeFlags Flags to use with `json_encode()`.
219
     *
220
     * @return string
221
     */
222
    public function unmarshalJson(array $data, $jsonEncodeFlags = 0)
223
    {
224
        return json_encode(
225
            $this->unmarshalValue(['M' => $data], true),
226
            $jsonEncodeFlags
227
        );
228
    }
229
 
230
    /**
231
     * Unmarshal an item from a DynamoDB operation result into a native PHP
232
     * array. If you set $mapAsObject to true, then a stdClass value will be
233
     * returned instead.
234
     *
235
     * @param array $data Item from a DynamoDB result.
236
     * @param bool  $mapAsObject Whether maps should be represented as stdClass.
237
     *
238
     * @return array|\stdClass
239
     */
240
    public function unmarshalItem(array $data, $mapAsObject = false)
241
    {
242
        return $this->unmarshalValue(['M' => $data], $mapAsObject);
243
    }
244
 
245
    /**
246
     * Unmarshal a value from a DynamoDB operation result into a native PHP
247
     * value. Will return a scalar, array, or (if you set $mapAsObject to true)
248
     * stdClass value.
249
     *
250
     * @param array $value       Value from a DynamoDB result.
251
     * @param bool  $mapAsObject Whether maps should be represented as stdClass.
252
     *
253
     * @return mixed
254
     * @throws \UnexpectedValueException
255
     */
256
    public function unmarshalValue(array $value, $mapAsObject = false)
257
    {
258
        $type = key($value);
259
        $value = $value[$type];
260
        switch ($type) {
261
            case 'S':
262
            case 'BOOL':
263
                return $value;
264
            case 'NULL':
265
                return null;
266
            case 'N':
267
                if ($this->options['wrap_numbers']) {
268
                    return new NumberValue($value);
269
                }
270
 
271
                // Use type coercion to unmarshal numbers to int/float.
272
                return $value + 0;
273
            case 'M':
274
                if ($mapAsObject) {
275
                    $data = new \stdClass;
276
                    foreach ($value as $k => $v) {
277
                        $data->$k = $this->unmarshalValue($v, $mapAsObject);
278
                    }
279
                    return $data;
280
                }
281
                // NOBREAK: Unmarshal M the same way as L, for arrays.
282
            case 'L':
283
                foreach ($value as $k => $v) {
284
                    $value[$k] = $this->unmarshalValue($v, $mapAsObject);
285
                }
286
                return $value;
287
            case 'B':
288
                return new BinaryValue($value);
289
            case 'SS':
290
            case 'NS':
291
            case 'BS':
292
                foreach ($value as $k => $v) {
293
                    $value[$k] = $this->unmarshalValue([$type[0] => $v]);
294
                }
295
                return new SetValue($value);
296
        }
297
 
298
        throw new \UnexpectedValueException("Unexpected type: {$type}.");
299
    }
300
 
301
    /**
302
     * Handle invalid value based on marshaler configuration.
303
     *
304
     * @param string $message Error message
305
     *
306
     * @return array|null
307
     */
308
    private function handleInvalid($message)
309
    {
310
        if ($this->options['ignore_invalid']) {
311
            return null;
312
        }
313
 
314
        if ($this->options['nullify_invalid']) {
315
            return ['NULL' => true];
316
        }
317
 
318
        throw new \UnexpectedValueException("Marshaling error: {$message}.");
319
    }
320
}