From e1735c6daf8e4fd4760fddbf75f93b26e9cc41f9 Mon Sep 17 00:00:00 2001 From: milos-pejanovic-devtech Date: Tue, 5 Jul 2016 09:56:45 +0200 Subject: [PATCH] Added ModelPropertyType. Various bug fixes and refactoring --- src/Common/Mapper/ObjectMapper.php | 108 ++++++++--------- .../{ModelClassData.php => ModelClass.php} | 8 +- ...odelPropertyData.php => ModelProperty.php} | 43 +++++-- src/Common/Models/ModelPropertyType.php | 91 ++++++++++++++ src/Common/Validator/ObjectValidator.php | 112 ++++++++---------- tests/Common/Traits/ConvertibleTraitTest.php | 2 +- 6 files changed, 231 insertions(+), 133 deletions(-) rename src/Common/Models/{ModelClassData.php => ModelClass.php} (87%) rename src/Common/Models/{ModelPropertyData.php => ModelProperty.php} (67%) create mode 100644 src/Common/Models/ModelPropertyType.php diff --git a/src/Common/Mapper/ObjectMapper.php b/src/Common/Mapper/ObjectMapper.php index e5ba1d1..dd212d6 100644 --- a/src/Common/Mapper/ObjectMapper.php +++ b/src/Common/Mapper/ObjectMapper.php @@ -7,8 +7,9 @@ */ namespace Common\Mapper; -use Common\Models\ModelClassData; -use Common\Models\ModelPropertyData; +use Common\Models\ModelClass; +use Common\Models\ModelProperty; +use Common\Models\ModelPropertyType; class ObjectMapper { @@ -23,14 +24,14 @@ public function map($sourceObject, $customObject) { if(self::isObjectEmpty($sourceObject)) { throw new \InvalidArgumentException('Invalid object(s) supplied for mapping.'); } - $modelClassData = new ModelClassData($customObject); + $modelClass = new ModelClass($customObject); - if(self::hasRoot($sourceObject, $modelClassData->rootName)) { - $sourceObject = $sourceObject->{$modelClassData->rootName}; + if(self::hasRoot($sourceObject, $modelClass->rootName)) { + $sourceObject = $sourceObject->{$modelClass->rootName}; } - foreach($modelClassData->properties as $property) { - $sourcePropertyValue = $this->findObjectValue($property, $sourceObject); + foreach($modelClass->properties as $property) { + $sourcePropertyValue = $this->findObjectValueByName($property, $sourceObject); $mappedPropertyValue = $this->mapValueByType($property->type, $sourcePropertyValue); $property->setPropertyValue($mappedPropertyValue); @@ -49,10 +50,10 @@ public function unmap($customObject) { throw new ObjectMapperException('Invalid object supplied for unmapping.'); } - $modelClassData = new ModelClassData($customObject); + $modelClass = new ModelClass($customObject); $unmappedObject = new \stdClass(); - foreach($modelClassData->properties as $property) { - $propertyKey = $property->name; + foreach($modelClass->properties as $property) { + $propertyKey = $property->getName(); $propertyValue = $property->getPropertyValue(); if(empty($propertyValue)) { continue; @@ -60,8 +61,8 @@ public function unmap($customObject) { $unmappedObject->$propertyKey = $this->unmapValueByType($property->type, $propertyValue); } - if(!empty($modelClassData->rootName)) { - $unmappedObject = $this->addRootElement($unmappedObject, $modelClassData->rootName); + if(!empty($modelClass->rootName)) { + $unmappedObject = $this->addRootElement($unmappedObject, $modelClass->rootName); } return $unmappedObject; @@ -75,12 +76,12 @@ protected function addRootElement($object, string $rootName) { } /** - * @param string $type + * @param ModelPropertyType $type * @param mixed $value * @return array|mixed|object * @throws ObjectMapperException */ - protected function mapValueByType(string $type, $value) { + protected function mapValueByType(ModelPropertyType $type, $value) { switch(gettype($value)) { case 'object': $mappedValue = $this->mapObjectValue($type, $value); @@ -104,44 +105,50 @@ protected function mapValueByType(string $type, $value) { } /** - * @param string $type + * @param ModelPropertyType $type * @param object $value * @return object */ - protected function mapObjectValue(string $type, $value) { + protected function mapObjectValue(ModelPropertyType $type, $value) { $mappedValue = $value; - if(self::isCustomType($type)) { - $customTypeObject = new $type(); - $mappedValue = $this->map($value, $customTypeObject); + if($type->isCustomType) { + $customClassName = $type->getCustomClassName(); + $customClass = new $customClassName(); + $mappedValue = $this->map($value, $customClass); } return $mappedValue; } /** - * @param string $type + * @param ModelPropertyType $type * @param array $value * @return array * @throws ObjectMapperException */ - protected function mapArrayValue(string $type, array $value) { - $mappedValue = []; - if(strpos($type, '[]')) { - $type = rtrim($type, '[]'); + protected function mapArrayValue(ModelPropertyType $type, array $value) { + if(strpos($type->annotatedType, '[]')) { + $arrayType = rtrim($type->annotatedType, '[]'); } + + $mappedValue = []; foreach($value as $key => $val) { - $mappedValue[$key] = $this->mapValueByType($type, $val); + if(empty($arrayType)) { + $arrayType = gettype($val); + } + $newType = new ModelPropertyType(gettype($value), $arrayType, $type->namespace); + $mappedValue[$key] = $this->mapValueByType($newType, $val); } return $mappedValue; } /** - * @param string $type + * @param ModelPropertyType $type * @param mixed $value * @return mixed */ - protected function unmapValueByType(string $type, $value) { + protected function unmapValueByType(ModelPropertyType $type, $value) { switch(gettype($value)) { case 'object': $unmappedValue = $this->unmapObjectValue($type, $value); @@ -158,14 +165,14 @@ protected function unmapValueByType(string $type, $value) { } /** - * @param string $type + * @param ModelPropertyType $type * @param object $value * @return object * @throws ObjectMapperException */ - protected function unmapObjectValue(string $type, $value) { + protected function unmapObjectValue(ModelPropertyType $type, $value) { $unmappedValue = $value; - if(self::isCustomType($type)) { + if($type->isCustomType) { $unmappedValue = $this->unmap($value); } @@ -173,31 +180,37 @@ protected function unmapObjectValue(string $type, $value) { } /** - * @param string $type + * @param ModelPropertyType $type * @param array $value * @return array */ - protected function unmapArrayValue(string $type, array $value) { - $mappedValue = []; - if(strpos($type, '[]')) { - $type = rtrim($type, '[]'); + protected function unmapArrayValue(ModelPropertyType $type, array $value) { + if(strpos($type->annotatedType, '[]')) { + $arrayType = rtrim($type->annotatedType, '[]'); } + + $unmappedValue = []; foreach($value as $key => $val) { - $mappedValue[$key] = $this->unmapValueByType($type, $val); + if(empty($arrayType)) { + $arrayType = gettype($val); + } + $newType = new ModelPropertyType(gettype($value), $arrayType, $type->namespace); + $unmappedValue[$key] = $this->unmapValueByType($newType, $val); } - return $mappedValue; + return $unmappedValue; } /** - * @param ModelPropertyData $ModelPropertyData + * Takes default model value if any set + * @param ModelProperty $ModelProperty * @param $sourceObject * @return object|null */ - protected function findObjectValue(ModelPropertyData $ModelPropertyData, $sourceObject) { - $objectValue = null; + protected function findObjectValueByName(ModelProperty $ModelProperty, $sourceObject) { + $objectValue = $ModelProperty->getPropertyValue(); foreach($sourceObject as $key => $value) { - if($ModelPropertyData->name == $key) { + if($ModelProperty->getName() == $key) { $objectValue = $value; break; } @@ -206,19 +219,6 @@ protected function findObjectValue(ModelPropertyData $ModelPropertyData, $source return $objectValue; } - public static function isCustomType($type) { - $result = true; - $simpleTypes = ['boolean', 'integer', 'double', 'string', 'array', 'object']; - foreach($simpleTypes as $simpleType) { - if($type == $simpleType) { - $result = false; - break; - } - } - - return $result; - } - /** * @param object $sourceObject * @param string $rootName diff --git a/src/Common/Models/ModelClassData.php b/src/Common/Models/ModelClass.php similarity index 87% rename from src/Common/Models/ModelClassData.php rename to src/Common/Models/ModelClass.php index 4f50cf3..44a8bd4 100644 --- a/src/Common/Models/ModelClassData.php +++ b/src/Common/Models/ModelClass.php @@ -8,7 +8,7 @@ namespace Common\Models; -class ModelClassData { +class ModelClass { /** * @var string @@ -26,7 +26,7 @@ class ModelClassData { public $rootName; /** - * @var ModelPropertyData[] + * @var ModelProperty[] */ public $properties; @@ -42,9 +42,7 @@ class ModelClassData { public function __construct($customObject) { $reflectionClass = new \ReflectionClass($customObject); $this->docBlock = new DocBlock($reflectionClass->getDocComment()); - $this->className = $reflectionClass->getName(); - $this->namespace = $reflectionClass->getNamespaceName(); $this->rootName = ''; @@ -54,7 +52,7 @@ public function __construct($customObject) { $properties = $reflectionClass->getProperties(); foreach($properties as $property) { - $this->properties[] = new ModelPropertyData($property, $customObject, $this->namespace); + $this->properties[] = new ModelProperty($property, $customObject, $this->namespace); } } } \ No newline at end of file diff --git a/src/Common/Models/ModelPropertyData.php b/src/Common/Models/ModelProperty.php similarity index 67% rename from src/Common/Models/ModelPropertyData.php rename to src/Common/Models/ModelProperty.php index e29eb0b..fe6ecae 100644 --- a/src/Common/Models/ModelPropertyData.php +++ b/src/Common/Models/ModelProperty.php @@ -9,23 +9,28 @@ namespace Common\Models; use Common\Mapper\ObjectMapper; -class ModelPropertyData { +class ModelProperty { /** * @var string */ - public $propertyName; + public $className; /** * @var string */ - public $name; + public $propertyName; /** - * @var string + * @var ModelPropertyType */ public $type; + /** + * @var string + */ + public $annotatedName; + /** * @var bool */ @@ -61,21 +66,20 @@ public function __construct(\ReflectionProperty $property, $object, string $name $this->property = $property; $this->object = $object; $this->docBlock = new DocBlock($property->getDocComment()); - $this->propertyName = $property->getName(); - $this->name = $property->getName(); + $this->className = get_class($object); + + $this->propertyName = $property->getName(); if($this->docBlock->annotationExists('name')) { - $this->name = $this->docBlock->getFirstAnnotation('name'); + $this->annotatedName = $this->docBlock->getFirstAnnotation('name'); } - $this->type = 'NULL'; + $propertyType = gettype($this->property->getValue($object)); + $annotatedType = 'NULL'; if($this->docBlock->annotationExists('var')) { - $type = $this->docBlock->getFirstAnnotation('var'); - if(ObjectMapper::isCustomType($type) && !strpos($type, '\\')) { - $type = $namespace . '\\' . $type; - } - $this->type = $type; + $annotatedType = $this->docBlock->getFirstAnnotation('var'); } + $this->type = new ModelPropertyType($propertyType, $annotatedType, $namespace); $this->isRequired = false; if($this->docBlock->annotationExists('required')) { @@ -97,4 +101,17 @@ public function setPropertyValue($value) { public function getPropertyValue() { return $this->property->getValue($this->object); } + + /** + * Returns the given property name, or @name value, if set + * @return string + */ + public function getName() { + $name = $this->propertyName; + if(!empty($this->annotatedName)) { + $name = $this->annotatedName; + } + + return $name; + } } \ No newline at end of file diff --git a/src/Common/Models/ModelPropertyType.php b/src/Common/Models/ModelPropertyType.php new file mode 100644 index 0000000..564d8da --- /dev/null +++ b/src/Common/Models/ModelPropertyType.php @@ -0,0 +1,91 @@ +propertyType = $propertyType; + $this->annotatedType = $annotatedType; + $this->namespace = $namespace; + + $this->actualType = $this->annotatedType; + if(self::isCustomType($this->annotatedType)) { + $this->isCustomType = true; + $this->actualType = 'object'; + } + if(strpos($this->annotatedType, '[]')) { + $this->actualType = 'array'; + } + } + + /** + * @param string $type + * @return bool + */ + public static function isCustomType(string $type) { + $result = true; + $simpleTypes = ['boolean', 'integer', 'double', 'string', 'array', 'object', + 'boolean[]', 'integer[]', 'double[]', 'string[]', '[]', 'object[]']; + foreach($simpleTypes as $simpleType) { + if($type == $simpleType) { + $result = false; + break; + } + } + + return $result; + } + + /** + * @return string + * @throws \Exception + */ + public function getCustomClassName() { + if(!$this->isCustomType) { + throw new \Exception('Property type is not custom.'); + } + $customType = $this->annotatedType; + if(strpos($customType, '[]')) { + $customType = rtrim($this->annotatedType, '[]'); + } + if(!strpos($customType, '\\')) { + $customType = '\\' . $customType; + } + $className = $this->namespace . $customType; + + return $className; + } +} \ No newline at end of file diff --git a/src/Common/Validator/ObjectValidator.php b/src/Common/Validator/ObjectValidator.php index 29f2c2b..4a4edf4 100644 --- a/src/Common/Validator/ObjectValidator.php +++ b/src/Common/Validator/ObjectValidator.php @@ -7,9 +7,8 @@ */ namespace Common\Validator; -use Common\Models\ModelClassData; -use Common\Models\ModelPropertyData; -use Common\Mapper\ObjectMapper; +use Common\Models\ModelClass; +use Common\Models\ModelProperty; class ObjectValidator { @@ -30,86 +29,80 @@ class ObjectValidator { * @throws \InvalidArgumentException */ public function validate($object, string $validationRequiredType = '') { - if(is_null($object)) { - throw new \InvalidArgumentException('Invalid object(s) supplied for validation.'); + if(!is_object($object)) { + throw new \InvalidArgumentException('Invalid object supplied for validation.'); } - $modelClassData = new ModelClassData($object); - $this->className = $modelClassData->className; + $modelClass = new ModelClass($object); + $this->className = $modelClass->className; - foreach($modelClassData->properties as $property) { + foreach($modelClass->properties as $property) { $this->propertyName = $property->propertyName; - $this->validateByType($property, $validationRequiredType); + $this->validateProperty($property, $validationRequiredType); } } /** - * @param ModelPropertyData $property + * @param ModelProperty $property * @param string $requiredType */ - protected function validateByType(ModelPropertyData $property, string $requiredType) { - switch($property->type) { - case 'object': - case 'array': - case 'boolean': - case 'integer': - case 'double': - case 'string': - case 'NULL': - $this->validateSimpleType($property, $requiredType); - break; - default: - $this->validateCustomType($property, $requiredType); - break; + protected function validateProperty(ModelProperty $property, string $requiredType) { + if($property->isRequired) { + $this->validateRequiredProperty($property, $requiredType); + } + $this->validatePropertyType($property, $requiredType); + + if($property->type->isCustomType) { + $this->validateCustomTypeValue($property, $requiredType); } } /** - * @param ModelPropertyData $property + * @param ModelProperty $property * @param string $requiredType */ - protected function validateCustomType(ModelPropertyData $property, string $requiredType) { - $this->validateRequired($property, $requiredType); - - if(!empty($property->getPropertyValue()) && strpos($property->type, '[]')) { - foreach($property->getPropertyValue() as $value) { - $this->validate($value); + protected function validateCustomTypeValue(ModelProperty $property, string $requiredType) { + $propertyValue = $property->getPropertyValue(); + if(!empty($propertyValue)) { + if(is_array($propertyValue)) { + foreach ($propertyValue as $value) { + $this->validate($value); + } + } + if(is_object($propertyValue)) { + $this->validate($propertyValue); } - } - elseif(!empty($property->getPropertyValue())) { - $this->validate($property->getPropertyValue()); } } /** - * @param ModelPropertyData $property + * @param ModelProperty $property * @param string $requiredType - */ - protected function validateSimpleType(ModelPropertyData $property, string $requiredType) { - $this->validateRequired($property, $requiredType); - $this->validateType($property); - } - - /** - * @param ModelPropertyData $property * @throws ObjectValidatorException */ - protected function validateType(ModelPropertyData $property) { - $expectedType = $property->type; + protected function validatePropertyType(ModelProperty $property, string $requiredType) { + $expectedType = $property->type->actualType; $actualType = gettype($property->getPropertyValue()); - $this->assertType($expectedType, $actualType); + + if(!$property->isRequired && $actualType != 'NULL') { + $this->assertPropertyType($expectedType, $actualType); + } + if($property->isRequired && array_search($requiredType, $property->requiredTypes) !== false) { + $this->assertPropertyType($expectedType, $actualType); + } } /** - * @param ModelPropertyData $property + * @param ModelProperty $property * @param string $requiredType * @throws ObjectValidatorException */ - protected function validateRequired(ModelPropertyData $property, string $requiredType) { - if($property->isRequired) { - $expectedRequired = $property->isRequired; - $actualRequired = !empty($property->getPropertyValue()); - foreach ($property->requiredTypes as $expectedRequiredType) { - $this->assertRequired($expectedRequired, $actualRequired, $expectedRequiredType, $requiredType); + protected function validateRequiredProperty(ModelProperty $property, string $requiredType) { + $expectedRequired = $property->isRequired; + $actualRequired = !empty($property->getPropertyValue()); + + foreach($property->requiredTypes as $expectedRequiredType) { + if(($expectedRequiredType == '' || $requiredType == '') || $expectedRequiredType == $requiredType) { + $this->assertRequiredProperty($expectedRequired, $actualRequired, $property); } } } @@ -119,8 +112,8 @@ protected function validateRequired(ModelPropertyData $property, string $require * @param string $actual * @throws ObjectValidatorException */ - protected function assertType(string $expected, string $actual) { - if($actual != 'NULL' && $expected != $actual) { + protected function assertPropertyType(string $expected, string $actual) { + if($expected != $actual) { throw new ObjectValidatorException('Expecting ' . $expected . ' type but got ' . $actual . ' while validating ' . $this->className . '::' . $this->propertyName); } } @@ -128,13 +121,12 @@ protected function assertType(string $expected, string $actual) { /** * @param bool $expected * @param bool $actual - * @param string $expectedType - * @param string $actualType + * @param ModelProperty $propertyData * @throws ObjectValidatorException */ - protected function assertRequired(bool $expected, bool $actual, string $expectedType, string $actualType) { - if($actualType == '' && ($expectedType == '' || $expectedType == $actualType) && $expected != $actual) { - throw new ObjectValidatorException('Required property ' . $this->className . '::' . $this->propertyName . ' not set.'); + protected function assertRequiredProperty(bool $expected, bool $actual, ModelProperty $propertyData) { + if($expected != $actual) { + throw new ObjectValidatorException('Required property ' . $propertyData->className . '::' . $propertyData->propertyName . ' not set.'); } } } \ No newline at end of file diff --git a/tests/Common/Traits/ConvertibleTraitTest.php b/tests/Common/Traits/ConvertibleTraitTest.php index d184da2..68dd733 100644 --- a/tests/Common/Traits/ConvertibleTraitTest.php +++ b/tests/Common/Traits/ConvertibleTraitTest.php @@ -17,7 +17,7 @@ public function testToArray($json, $model) { /** @var MapperModel|MapperModelWithRoot $model */ $actualArray = $model->toArray(); - $this->assertEquals(json_encode($expectedArray), json_encode($actualArray)); + $this->assertEquals($expectedArray, $actualArray); } /**