From df9893428ec5ee6e60e8666c7001bcd54a384953 Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Tue, 21 Aug 2018 12:41:32 -0300 Subject: [PATCH] [JMSModelDescriber] Allow to use free form objects from JMS serialization (#1368) * [JMSModelDescriber] Allow to use free form objects from JMS serialization * Remove unused variable * Refactor `describeItem()` * Add more tests * early detect of free form objects * Add test for array property without type * Merge `findPropertyType()` and `registerPropertyType()` methods * Update `registerPropertyType()` in order to set "object" as type for arrays with undefined item types * Set "additionalProperties" definition for arrays without defined items * Simplified `registerPropertyType()` * Merged `registerPropertyType()` into `describeItem()` * Fix comment * Fix comment --- ModelDescriber/JMSModelDescriber.php | 78 +++++++++----------------- Tests/Functional/Entity/JMSUser.php | 30 ++++++++++ Tests/Functional/JMSFunctionalTest.php | 38 +++++++++++++ composer.json | 2 +- 4 files changed, 96 insertions(+), 52 deletions(-) diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index f3b1057..6ce8083 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -118,54 +118,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn return false; } - /** - * @param string $type - * @param array|null $groups - * - * @return array|null - */ - private function findPropertyType(string $type, array $groups = null) - { - $typeDef = []; - if (in_array($type, ['boolean', 'string', 'array'])) { - $typeDef['type'] = $type; - } elseif (in_array($type, ['int', 'integer'])) { - $typeDef['type'] = 'integer'; - } elseif (in_array($type, ['double', 'float'])) { - $typeDef['type'] = 'number'; - $typeDef['format'] = $type; - } elseif (is_subclass_of($type, \DateTimeInterface::class)) { - $typeDef['type'] = 'string'; - $typeDef['format'] = 'date-time'; - } else { - // we can use property type also for custom handlers, then we don't have here real class name - if (!class_exists($type)) { - return null; - } - - $typeDef['$ref'] = $this->modelRegistry->register( - new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $type), $groups) - ); - } - - return $typeDef; - } - - private function registerPropertyType(array $typeDef, $property) - { - if (isset($typeDef['$ref'])) { - $property->setRef($typeDef['$ref']); - } else { - if (isset($typeDef['type'])) { - $property->setType($typeDef['type']); - } - if (isset($typeDef['format'])) { - $property->setFormat($typeDef['format']); - } - } - } - - private function describeItem(array $type, $property, array $groups = null) + private function describeItem(array $type, Schema $property, array $groups = null) { if (list($nestedType, $isHash) = $this->getNestedTypeInArray($type)) { // @ todo update a bit getNestedTypeInArray and describe ($type = $item->type) if ($isHash) { @@ -173,6 +126,11 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn // in the case of a virtual property, set it as free object type $property->merge(['additionalProperties' => []]); + // this is a free form object (as nested array) + if ('array' === $nestedType['name'] && !isset($nestedType['params'][0])) { + return; + } + $this->describeItem($nestedType, $property->getAdditionalProperties(), $groups); return; @@ -180,10 +138,28 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $property->setType('array'); $this->describeItem($nestedType, $property->getItems(), $groups); - } + } elseif ('array' === $type['name']) { + $property->setType('object'); + $property->merge(['additionalProperties' => []]); + } elseif (in_array($type['name'], ['boolean', 'string'], true)) { + $property->setType($type['name']); + } elseif (in_array($type['name'], ['int', 'integer'], true)) { + $property->setType('integer'); + } elseif (in_array($type['name'], ['double', 'float'], true)) { + $property->setType('number'); + $property->setFormat($type['name']); + } elseif (is_subclass_of($type['name'], \DateTimeInterface::class)) { + $property->setType('string'); + $property->setFormat('date-time'); + } else { + // we can use property type also for custom handlers, then we don't have here real class name + if (!class_exists($type['name'])) { + return null; + } - if ($typeDef = $this->findPropertyType($type['name'], $groups)) { - $this->registerPropertyType($typeDef, $property); + $property->setRef($this->modelRegistry->register( + new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $type['name']), $groups) + )); } } diff --git a/Tests/Functional/Entity/JMSUser.php b/Tests/Functional/Entity/JMSUser.php index 9f457a9..389c09e 100644 --- a/Tests/Functional/Entity/JMSUser.php +++ b/Tests/Functional/Entity/JMSUser.php @@ -132,6 +132,36 @@ class JMSUser */ private $latLonHistory; + /** + * @Serializer\Type("array") + * @Serializer\Expose + */ + private $freeFormObject; + + /** + * @Serializer\Type("array") + * @Serializer\Expose + */ + private $freeFormObjectWithoutType; + + /** + * @Serializer\Type("array>") + * @Serializer\Expose + */ + private $deepObject; + + /** + * @Serializer\Type("array>") + * @Serializer\Expose + */ + private $deepObjectWithItems; + + /** + * @Serializer\Type("array>>") + * @Serializer\Expose + */ + private $deepFreeFormObjectCollection; + public function setRoles($roles) { } diff --git a/Tests/Functional/JMSFunctionalTest.php b/Tests/Functional/JMSFunctionalTest.php index 46ad179..91239fa 100644 --- a/Tests/Functional/JMSFunctionalTest.php +++ b/Tests/Functional/JMSFunctionalTest.php @@ -93,6 +93,44 @@ class JMSFunctionalTest extends WebTestCase ], ], ], + 'free_form_object_without_type' => [ + 'type' => 'object', + 'additionalProperties' => true, + ], + 'free_form_object' => [ + 'type' => 'object', + 'additionalProperties' => true, + ], + 'deep_object' => [ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'string', + 'format' => 'date-time', + ], + ], + ], + 'deep_object_with_items' => [ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'format' => 'date-time', + ], + ], + ], + 'deep_free_form_object_collection' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'additionalProperties' => true, + ], + ], + ], ], ], $this->getModel('JMSUser')->toArray()); } diff --git a/composer.json b/composer.json index 80d7cf1..7f2d50a 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "symfony/framework-bundle": "^3.4|^4.0", "symfony/options-resolver": "^3.4.4|^4.0", "symfony/property-info": "^3.4|^4.0", - "exsyst/swagger": "~0.3|~0.4", + "exsyst/swagger": "^0.4.1", "zircote/swagger-php": "^2.0.9", "phpdocumentor/reflection-docblock": "^3.1|^4.0" },