diff --git a/src/Datasource/Marshaller.php b/src/Datasource/Marshaller.php index 0170beb..e2d9cd8 100644 --- a/src/Datasource/Marshaller.php +++ b/src/Datasource/Marshaller.php @@ -5,8 +5,14 @@ use ArrayObject; use Cake\Collection\Collection; +use Cake\Core\Configure; +use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; +use Cake\Error\Debugger; +use Cake\Http\Exception\InternalErrorException; +use Cake\ORM\PropertyMarshalInterface; +use Cake\Utility\Inflector; use Muffin\Webservice\Model\Endpoint; use RuntimeException; @@ -34,6 +40,33 @@ public function __construct(Endpoint $endpoint) $this->_endpoint = $endpoint; } + /** + * Build the map of property => marshalling callable. + * + * @param array $data The data being marshalled. + * @param array $options List of options containing the 'associated' key. + * @throws \InvalidArgumentException When associations do not exist. + * @return array + */ + protected function _buildPropertyMap(array $data, array $options): array + { + $map = []; + $schema = $this->_endpoint->getSchema(); + + // Is a concrete column? + foreach (array_keys($data) as $prop) { + $prop = (string)$prop; + $columnType = $schema->getColumnType($prop); + if ($columnType) { + $map[$prop] = function ($value, $entity) use ($columnType) { + return TypeFactory::build($columnType)->marshal($value); + }; + } + } + + return $map; + } + /** * Hydrate one entity. * @@ -65,6 +98,7 @@ public function one(array $data, array $options = []): EntityInterface } $errors = $this->_validate($data, $options, true); + $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { @@ -75,7 +109,11 @@ public function one(array $data, array $options = []): EntityInterface // Skip marshalling '' for pk fields. continue; } - $properties[$key] = $value; + if (isset($propertyMap[$key])) { + $properties[$key] = $propertyMap[$key]($value, $entity); + } else { + $properties[$key] = $value; + } } if (!isset($options['fieldList'])) { @@ -92,6 +130,7 @@ public function one(array $data, array $options = []): EntityInterface } $entity->setErrors($errors); + $this->dispatchAfterMarshal($entity, $data, $options); return $entity; } @@ -216,6 +255,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } $errors = $this->_validate($data + $keys, $options, $isNew); + $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { @@ -224,7 +264,33 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } continue; } - + $original = $entity->get($key); + + if (isset($propertyMap[$key])) { + $value = $propertyMap[$key]($value, $entity); + + // Don't dirty scalar values and objects that didn't + // change. Arrays will always be marked as dirty because + // the original/updated list could contain references to the + // same objects, even though those objects may have changed internally. + if ( + ( + is_scalar($value) + && $original === $value + ) + || ( + $value === null + && $original === $value + ) + || ( + is_object($value) + && !($value instanceof EntityInterface) + && $original == $value + ) + ) { + continue; + } + } $properties[$key] = $value; } @@ -242,6 +308,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } $entity->setErrors($errors); + $this->dispatchAfterMarshal($entity, $data, $options); return $entity; } @@ -313,4 +380,19 @@ public function mergeMany($entities, array $data, array $options = []): array return $output; } + + /** + * dispatch Model.afterMarshal event. + * + * @param \Cake\Datasource\EntityInterface $entity The entity that was marshaled. + * @param array $data readOnly $data to use. + * @param array $options List of options that are readOnly. + * @return void + */ + protected function dispatchAfterMarshal(EntityInterface $entity, array $data, array $options = []): void + { + $data = new ArrayObject($data); + $options = new ArrayObject($options); + $this->_endpoint->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options')); + } } diff --git a/src/Webservice/Webservice.php b/src/Webservice/Webservice.php index 54d3c6f..8bcf126 100644 --- a/src/Webservice/Webservice.php +++ b/src/Webservice/Webservice.php @@ -356,13 +356,11 @@ protected function _transformResults(Endpoint $endpoint, array $results): array */ protected function _transformResource(Endpoint $endpoint, array $result): Resource { - $properties = []; + $entity = $endpoint->newEntity($result); + $entity->setNew(false); + $entity->clean(); - foreach ($result as $property => $value) { - $properties[$property] = $value; - } - - return $this->_createResource($endpoint->getResourceClass(), $properties); + return $entity; } /**