| 
<?phpnamespace Aws\DynamoDb;
 
 use Psr\Http\Message\StreamInterface;
 
 /**
 * Marshals and unmarshals JSON documents and PHP arrays into DynamoDB items.
 */
 class Marshaler
 {
 /** @var array Default options to merge into provided options. */
 private static $defaultOptions = [
 'ignore_invalid'  => false,
 'nullify_invalid' => false,
 'wrap_numbers'    => false,
 ];
 
 /** @var array Marshaler options. */
 private $options;
 
 /**
 * Instantiates a DynamoDB Marshaler.
 *
 * The following options are valid.
 *
 * - ignore_invalid: (bool) Set to `true` if invalid values should be
 *   ignored (i.e., not included) during marshaling.
 * - nullify_invalid: (bool) Set to `true` if invalid values should be set
 *   to null.
 * - wrap_numbers: (bool) Set to `true` to wrap numbers with `NumberValue`
 *   objects during unmarshaling to preserve the precision.
 *
 * @param array $options Marshaler options
 */
 public function __construct(array $options = [])
 {
 $this->options = $options + self::$defaultOptions;
 }
 
 /**
 * Creates a special object to represent a DynamoDB binary (B) value.
 *
 * This helps disambiguate binary values from string (S) values.
 *
 * @param mixed $value A binary value compatible with Guzzle streams.
 *
 * @return BinaryValue
 * @see GuzzleHttp\Stream\Stream::factory
 */
 public function binary($value)
 {
 return new BinaryValue($value);
 }
 
 /**
 * Creates a special object to represent a DynamoDB number (N) value.
 *
 * This helps maintain the precision of large integer/float in PHP.
 *
 * @param string|int|float $value A number value.
 *
 * @return NumberValue
 */
 public function number($value)
 {
 return new NumberValue($value);
 }
 
 /**
 * Creates a special object to represent a DynamoDB set (SS/NS/BS) value.
 *
 * This helps disambiguate set values from list (L) values.
 *
 * @param array $values The values of the set.
 *
 * @return SetValue
 *
 */
 public function set(array $values)
 {
 return new SetValue($values);
 }
 
 /**
 * Marshal a JSON document from a string to a DynamoDB item.
 *
 * The result is an array formatted in the proper parameter structure
 * required by the DynamoDB API for items.
 *
 * @param string $json A valid JSON document.
 *
 * @return array Item formatted for DynamoDB.
 * @throws \InvalidArgumentException if the JSON is invalid.
 */
 public function marshalJson($json)
 {
 $data = json_decode($json);
 if (!($data instanceof \stdClass)) {
 throw new \InvalidArgumentException(
 'The JSON document must be valid and be an object at its root.'
 );
 }
 
 return current($this->marshalValue($data));
 }
 
 /**
 * Marshal a native PHP array of data to a DynamoDB item.
 *
 * The result is an array formatted in the proper parameter structure
 * required by the DynamoDB API for items.
 *
 * @param array|\stdClass $item An associative array of data.
 *
 * @return array Item formatted for DynamoDB.
 */
 public function marshalItem($item)
 {
 return current($this->marshalValue($item));
 }
 
 /**
 * Marshal a native PHP value into a DynamoDB attribute value.
 *
 * The result is an associative array that is formatted in the proper
 * `[TYPE => VALUE]` parameter structure required by the DynamoDB API.
 *
 * @param mixed $value A scalar, array, or `stdClass` value.
 *
 * @return array Attribute formatted for DynamoDB.
 * @throws \UnexpectedValueException if the value cannot be marshaled.
 */
 public function marshalValue($value)
 {
 $type = gettype($value);
 
 // Handle string values.
 if ($type === 'string') {
 if ($value === '') {
 return $this->handleInvalid('empty strings are invalid');
 }
 
 return ['S' => $value];
 }
 
 // Handle number values.
 if ($type === 'integer'
 || $type === 'double'
 || $value instanceof NumberValue
 ) {
 return ['N' => (string) $value];
 }
 
 // Handle boolean values.
 if ($type === 'boolean') {
 return ['BOOL' => $value];
 }
 
 // Handle null values.
 if ($type === 'NULL') {
 return ['NULL' => true];
 }
 
 // Handle set values.
 if ($value instanceof SetValue) {
 if (count($value) === 0) {
 return $this->handleInvalid('empty sets are invalid');
 }
 $previousType = null;
 $data = [];
 foreach ($value as $v) {
 $marshaled = $this->marshalValue($v);
 $setType = key($marshaled);
 if (!$previousType) {
 $previousType = $setType;
 } elseif ($setType !== $previousType) {
 return $this->handleInvalid('sets must be uniform in type');
 }
 $data[] = current($marshaled);
 }
 
 return [$previousType . 'S' => array_unique($data)];
 }
 
 // Handle list and map values.
 $dbType = 'L';
 if ($value instanceof \stdClass) {
 $type = 'array';
 $dbType = 'M';
 }
 if ($type === 'array' || $value instanceof \Traversable) {
 $data = [];
 $index = 0;
 foreach ($value as $k => $v) {
 if ($v = $this->marshalValue($v)) {
 $data[$k] = $v;
 if ($dbType === 'L' && (!is_int($k) || $k != $index++)) {
 $dbType = 'M';
 }
 }
 }
 return [$dbType => $data];
 }
 
 // Handle binary values.
 if (is_resource($value) || $value instanceof StreamInterface) {
 $value = $this->binary($value);
 }
 if ($value instanceof BinaryValue) {
 return ['B' => (string) $value];
 }
 
 // Handle invalid values.
 return $this->handleInvalid('encountered unexpected value');
 }
 
 /**
 * Unmarshal a document (item) from a DynamoDB operation result into a JSON
 * document string.
 *
 * @param array $data            Item/document from a DynamoDB result.
 * @param int   $jsonEncodeFlags Flags to use with `json_encode()`.
 *
 * @return string
 */
 public function unmarshalJson(array $data, $jsonEncodeFlags = 0)
 {
 return json_encode(
 $this->unmarshalValue(['M' => $data], true),
 $jsonEncodeFlags
 );
 }
 
 /**
 * Unmarshal an item from a DynamoDB operation result into a native PHP
 * array. If you set $mapAsObject to true, then a stdClass value will be
 * returned instead.
 *
 * @param array $data Item from a DynamoDB result.
 *
 * @return array|\stdClass
 */
 public function unmarshalItem(array $data)
 {
 return $this->unmarshalValue(['M' => $data]);
 }
 
 /**
 * Unmarshal a value from a DynamoDB operation result into a native PHP
 * value. Will return a scalar, array, or (if you set $mapAsObject to true)
 * stdClass value.
 *
 * @param array $value       Value from a DynamoDB result.
 * @param bool  $mapAsObject Whether maps should be represented as stdClass.
 *
 * @return mixed
 * @throws \UnexpectedValueException
 */
 public function unmarshalValue(array $value, $mapAsObject = false)
 {
 list($type, $value) = each($value);
 switch ($type) {
 case 'S':
 case 'BOOL':
 return $value;
 case 'NULL':
 return null;
 case 'N':
 if ($this->options['wrap_numbers']) {
 return new NumberValue($value);
 } else {
 // Use type coercion to unmarshal numbers to int/float.
 return $value + 0;
 }
 case 'M':
 if ($mapAsObject) {
 $data = new \stdClass;
 foreach ($value as $k => $v) {
 $data->$k = $this->unmarshalValue($v, $mapAsObject);
 }
 return $data;
 }
 // NOBREAK: Unmarshal M the same way as L, for arrays.
 case 'L':
 foreach ($value as &$v) {
 $v = $this->unmarshalValue($v, $mapAsObject);
 }
 return $value;
 case 'B':
 return new BinaryValue($value);
 case 'SS':
 case 'NS':
 case 'BS':
 foreach ($value as &$v) {
 $v = $this->unmarshalValue([$type[0] => $v]);
 }
 return new SetValue($value);
 }
 
 throw new \UnexpectedValueException("Unexpected type: {$type}.");
 }
 
 /**
 * Handle invalid value based on marshaler configuration.
 *
 * @param string $message Error message
 *
 * @return array|null
 */
 private function handleInvalid($message)
 {
 if ($this->options['ignore_invalid']) {
 return null;
 } elseif ($this->options['nullify_invalid']) {
 return ['NULL' => true];
 }
 
 throw new \UnexpectedValueException("Marshaling error: {$message}.");
 }
 }
 
 |