diff --git a/README.md b/README.md index d5ae8ed44bbe184e75539c7f466c705a56b907f2..05cda9606d44204e0463d1029173cd7809514105 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ ## 特点 1. 支持增加注解来标明是否允许为空; -2. 支持枚举类型。 +2. 支持枚举类型,如果类继承自 `MyCLabs\Enum\Enum` 则会进行判断; +3. 支持注解标明别名映射。 ## 安装教程 ```bash @@ -13,9 +14,30 @@ composer require dreamcat/array_2_class ``` ## 使用说明 -todo +首先定义一个输出的目标类,例如 +```php +class Pojo +{ + private $name; + /** + * @var string + * @from key_word + */ + private $keyWord; -## todo -1. 优化notNull的判定 -1. 抽离注解文档到属性分析的代码 + public function setName(string $name) + { + $this->name = $name; + } +} +``` + +然后调用执行如下代码 +```php +$converter = new Array2ClassConverter(); +$data = ['name' => 'abc', 'key_word' => 'kkk']; +$obj = $converter->convert($data, Pojo::class); +``` + +执行完成后,`$obj` 的 `name` 被赋值为 `abc`,`keyWord` 被赋值为 `kkk`。 diff --git a/composer.json b/composer.json index 0190f43aeebaed06fec7ae6dfb7ac2cf469f17ec..aff4a2a1ccdb16986caf6776d762e37618f8d5ff 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,9 @@ ], "require": { "php": ">=7.2", - "myclabs/php-enum": "^1.7", - "dreamcat/utils": "^2" + "myclabs/php-enum": "^1", + "dreamcat/utils": "^2", + "dreamcat/property_analysis": "^1" }, "require-dev": { "phpunit/phpunit": "^8" diff --git a/src/main/Array2ClassConverter.php b/src/main/Array2ClassConverter.php index 9317b8cb11541f4b6c7511c4f590d16dcd638faf..1b7aa9ad1a27da707bc3e811391d7a1faca6bf9b 100644 --- a/src/main/Array2ClassConverter.php +++ b/src/main/Array2ClassConverter.php @@ -2,58 +2,61 @@ namespace DreamCat\Array2Class; -use Components\Utils\Funcs\PhpFileHelper; use DreamCat\Array2Class\Exception\ConvertFailed; +use DreamCat\Array2Class\Utils\AttentionKeywords; +use Dreamcat\PropertyAnalysis\Pojo\BuildinType; +use Dreamcat\PropertyAnalysis\Pojo\PropertyResult; +use Dreamcat\PropertyAnalysis\Pojo\TypeDescription; +use Dreamcat\PropertyAnalysis\PropertyAnalysis; +use Dreamcat\PropertyAnalysis\PropertyAnalysisInterface; +use Dreamcat\PropertyAnalysis\Utils\BuildinTypeHelper; use MyCLabs\Enum\Enum; use ReflectionClass; -use ReflectionMethod; -use ReflectionParameter; use ReflectionProperty; /** * 数组转类对象的转换器 * @author vijay */ -class Array2ClassConverter +class Array2ClassConverter implements Array2ClassInterface { - /** @var string[] 内置类型及其标准化函数 */ - const BUILDIN_TYPE = [ - "int" => "intval", - "float" => "floatval", - "bool" => "boolval", - "string" => "strval", - ]; /** @var bool 是否创建对象原本不存在的属性 */ private $createNewProperty = false; /** @var bool 是否尝试执行不调用构造函数创建对象 */ private $tryWithoutConstructor = false; /** @var bool 是否尝试调用 setter */ private $tryCallSetter = true; - /** @var string[][] use的使用情况,一级键是文件名,二级键是短名称,值为全名称 */ - private $shortMap = []; + /** @var bool 尝试在构造函数只有一个参数时调用构造函数 */ + private $tryConstructWithParam = true; + /** @var PropertyAnalysisInterface 类的属性分析器 */ + private $propertyAnalysis; /** - * @param bool $tryWithoutConstructor 是否尝试执行不调用构造函数创建对象 - * @return static - * @note 如果构建函数不满足条件,会直接构造对象,并不调用构造函数 + * @return bool 尝试在构造函数只有一个参数时调用构造函数 */ - public function setTryWithoutConstructor(bool $tryWithoutConstructor): Array2ClassConverter + public function isTryConstructWithParam(): bool { - $this->tryWithoutConstructor = $tryWithoutConstructor; - return $this; + return $this->tryConstructWithParam; } /** - * @param bool $tryCallSetter 是否尝试调用 setter - * @return static - * @note 设置为false或找不到setter会直接修改属性值 + * @param bool $tryConstructWithParam 尝试在构造函数只有一个参数时调用构造函数 + * @return static 对象本身 */ - public function setTryCallSetter(bool $tryCallSetter): Array2ClassConverter + public function setTryConstructWithParam(bool $tryConstructWithParam): Array2ClassConverter { - $this->tryCallSetter = $tryCallSetter; + $this->tryConstructWithParam = $tryConstructWithParam; return $this; } + /** + * @return bool 是否创建对象原本不存在的属性 + */ + public function isCreateNewProperty(): bool + { + return $this->createNewProperty; + } + /** * 是否创建对象原本不存在的属性 * @param bool $createNewProperty 是否创建对象原本不存在的属性 @@ -66,6 +69,44 @@ class Array2ClassConverter return $this; } + /** + * @return bool 是否尝试执行不调用构造函数创建对象 + */ + public function isTryWithoutConstructor(): bool + { + return $this->tryWithoutConstructor; + } + + /** + * @param bool $tryWithoutConstructor 是否尝试执行不调用构造函数创建对象 + * @return static + * @note 如果构建函数不满足条件,会直接构造对象,并不调用构造函数 + */ + public function setTryWithoutConstructor(bool $tryWithoutConstructor): Array2ClassConverter + { + $this->tryWithoutConstructor = $tryWithoutConstructor; + return $this; + } + + /** + * @return bool 是否尝试调用 setter + */ + public function isTryCallSetter(): bool + { + return $this->tryCallSetter; + } + + /** + * @param bool $tryCallSetter 是否尝试调用 setter + * @return static + * @note 设置为false或找不到setter会直接修改属性值 + */ + public function setTryCallSetter(bool $tryCallSetter): Array2ClassConverter + { + $this->tryCallSetter = $tryCallSetter; + return $this; + } + /** * 数据转换 * @param array $data 输入的数组 @@ -97,15 +138,19 @@ class Array2ClassConverter /** @noinspection PhpUnhandledExceptionInspection */ $reflectionClass = new ReflectionClass($object); } - } elseif ( - !($constructor = $reflectionClass->getConstructor()) - || $constructor->getNumberOfRequiredParameters() == 0 - ) { - $object = $reflectionClass->newInstance(); - } elseif ($this->tryWithoutConstructor) { - $object = $reflectionClass->newInstanceWithoutConstructor(); } else { - throw new ConvertFailed("指定的输出类{$reflectionClass->getName()}构造函数需要参数且未实现接口" . PlainOBjectFactory::class); + $constructor = $reflectionClass->getConstructor(); + if (!$constructor || $constructor->getNumberOfRequiredParameters() == 0) { + $object = $reflectionClass->newInstance(); + } elseif ($this->tryConstructWithParam + && $constructor->getNumberOfParameters() > 0 + && $constructor->getNumberOfRequiredParameters() <= 1) { + return $reflectionClass->newInstance($data); + } elseif ($this->tryWithoutConstructor) { + $object = $reflectionClass->newInstanceWithoutConstructor(); + } else { + throw new ConvertFailed("指定的输出类{$reflectionClass->name}构造函数需要参数且未实现接口" . PlainOBjectFactory::class); + } } return $this->assignClassProperty($data, $object, $reflectionClass); } @@ -119,39 +164,32 @@ class Array2ClassConverter */ private function assignClassProperty(array $data, $object, ReflectionClass $reflectionClass) { - $hasValues = []; + $analysisResult = $this->getAnalysisResult($reflectionClass); foreach ($data as $key => $value) { - if ($reflectionClass->hasProperty($key)) { + if (isset($analysisResult[$key])) { # 存在相应属性 - /** @noinspection PhpUnhandledExceptionInspection */ - $reflectionProperty = $reflectionClass->getProperty($key); - if ($reflectionProperty->isStatic()) { - throw new ConvertFailed("{$reflectionClass->getName()}::{$key}是静态属性,不可赋值"); + $propertyResult = $analysisResult[$key]; + $property = $propertyResult->getProperty(); + $valueType = null; + if ($this->tryCallSetter && $propertyResult->isHasSetter()) { + $valueType = $propertyResult->getSetterTypeDescription(); } - $setter = $this->setterInfo($reflectionClass, $reflectionProperty); - if ($setter) { - $typeInfo = $this->getPropertyTypeBySetter($setter); - } else { - $typeInfo = $this->getPropertyTypeByDoc($reflectionProperty); + if (!$valueType) { + $valueType = $propertyResult->getTypeDescription(); } - - $value = $this->createValue($value, $typeInfo, $reflectionProperty); - - if ($setter) { - $setter->invoke($object, $value); + $fixedValue = $this->createValue($value, $valueType, $property); + if ($this->tryCallSetter && $propertyResult->isHasSetter()) { + $propertyResult->getSetter()->invoke($object, $fixedValue); } else { - $allow = $reflectionProperty->isPublic(); + $allow = $property->isPublic(); if (!$allow) { - $reflectionProperty->setAccessible(true); + $property->setAccessible(true); } - $reflectionProperty->setValue($object, $value); + $property->setValue($object, $fixedValue); if (!$allow) { - $reflectionProperty->setAccessible(false); + $property->setAccessible(false); } } - if ($value !== null) { - $hasValues[$key] = true; - } } elseif ($this->createNewProperty) { # 不存在,直接创建属性并赋值 /** @noinspection PhpVariableVariableInspection */ @@ -160,230 +198,104 @@ class Array2ClassConverter } # 判断非空项是否均已赋值 - foreach ($reflectionClass->getProperties( - ReflectionProperty::IS_PRIVATE - | ReflectionProperty::IS_PRIVATE - | ReflectionProperty::IS_PRIVATE - ) as $reflectionProperty) { - $name = $reflectionProperty->getName(); - if (isset($hasValues[$name])) { + foreach ($analysisResult as $key => $propertyResult) { + if (!isset($propertyResult->getOtherKeywordResult()[AttentionKeywords::NOT_NULL])) { continue; } - $doc = $reflectionProperty->getDocComment(); - if (!$doc) { - continue; + $property = $propertyResult->getProperty(); + $allow = $property->isPublic(); + if (!$allow) { + $property->setAccessible(true); + } + $currentValue = $property->getValue($object); + if (!$allow) { + $property->setAccessible(false); } - if (strpos($doc, "@NotNull") !== false) { - throw new ConvertFailed("{$reflectionClass->getName()}::{$reflectionProperty->getName()}是必填属性但是未赋值"); + if ($currentValue === null) { + throw new ConvertFailed("{$reflectionClass->getName()}::{$property->name}是必填属性但是未赋值"); } } return $object; } /** - * 获取setter函数反射 - * @param ReflectionClass $reflectionClass 反射类 - * @param ReflectionProperty $reflectionProperty 反射属性 - * @return ReflectionMethod|null setter函数反射 - */ - private function setterInfo( - ReflectionClass $reflectionClass, - ReflectionProperty $reflectionProperty - ): ?ReflectionMethod { - if (!$this->tryCallSetter) { - return null; - } - $setterName = "set" . $reflectionProperty->getName(); - if (!$reflectionClass->hasMethod($setterName)) { - return null; - } - /** @noinspection PhpUnhandledExceptionInspection */ - $reflectionMethod = $reflectionClass->getMethod($setterName); - if (!$reflectionMethod->isPublic() || $reflectionMethod->isStatic()) { - return null; - } - if ($reflectionMethod->getNumberOfParameters() > 0 && $reflectionMethod->getNumberOfRequiredParameters() <= 1) { - return $reflectionMethod; - } else { - return null; - } - } - - /** - * 获取属性setter第一参数的类型限定 - * @param ReflectionMethod $reflectionMethod 方法反射 - * @return array 参数信息 { - * type : 五种值 - * class : 表示参数限定为某个类 - * type : 表示参数限定为某个基本类型 - * array : 表示参数一定是数组 - * mixed : 表示参数无限制 - * custom : 表示参数有定义 - * name : class时表示类反射,type时表示类型反射,array时表示元素类型(可能不存在),custom表示定义的名称 - * } + * 获取类的分析结果 + * @param ReflectionClass $reflectionClass 类反射 + * @return PropertyResult[] */ - private function getPropertyTypeBySetter(ReflectionMethod $reflectionMethod) + private function getAnalysisResult(ReflectionClass $reflectionClass): array { - $reflectionParameter = $reflectionMethod->getParameters()[0]; - if ($reflectionParameter->isCallable()) { - throw new ConvertFailed("{$reflectionMethod->class}::{$reflectionMethod->getName()}的参数要求为callable,无法调用"); - } - $class = $reflectionParameter->getClass(); - if ($class !== null) { - return [ - "type" => "class", - "name" => $class, - ]; - } - $isArray = $reflectionParameter->isArray(); - if (!$isArray && ($type = $reflectionParameter->getType())) { - return [ - "type" => "type", - "name" => $type, - ]; + $analysisResult = $this->getPropertyAnalysis()->analysis($reflectionClass)->getPropertyList(); + $result = []; + foreach ($analysisResult as $key => $propertyResult) { + $otherWord = $propertyResult->getOtherKeywordResult(); + $name = $otherWord[AttentionKeywords::ALIAS][0] ?? $key; + $result[$name] = $propertyResult; } - return $this->parseTypeNotSimple($reflectionMethod, $reflectionParameter, $isArray); + return $result; } /** - * 处理不能直接通过限定得到类型的情况 - * @param ReflectionMethod $reflectionMethod 方法反射 - * @param ReflectionParameter $reflectionParameter 参数反射 - * @param bool $isArray 参数是否数组 - * @return array 参数信息 { - * type : 三种值 - * array : 表示参数一定是数组 - * mixed : 表示参数无限制 - * custom : 表示参数有定义 - * name : array时表示元素类型(可能不存在),custom表示定义的名称 - * } + * @return PropertyAnalysisInterface 类的属性分析器 */ - private function parseTypeNotSimple( - ReflectionMethod $reflectionMethod, - ReflectionParameter $reflectionParameter, - bool $isArray - ): array { - # todo 目前不考虑多级数组限定描述,此种情况忽略 - $docType = $this->getTypeFromDoc( - $this->splitDoc($reflectionMethod->getDocComment()), - "#@param (?[\\w\\\\]+(?:\\[\\])?) \\\${$reflectionParameter->getName()}#" - ); - if ($docType == "" || $docType == "mixed") { - return ["type" => "mixed"]; - } elseif (substr($docType, -2) == "[]") { - return [ - "type" => "array", - "name" => substr($docType, 0, -2), - ]; - } elseif ($isArray) { - return ["type" => "array"]; - } else { - return [ - "type" => "custom", - "name" => $docType, - ]; - } - } - - /** - * 从注释中尝试获取参数类型 - * @param array $docs 注解,一行一条数据 - * @param string $preg 搜索正则,类型的捕获名为 t - * @return string 类型描述,搜索不到返回空字符串 - */ - private function getTypeFromDoc(array $docs, string $preg): string + public function getPropertyAnalysis(): PropertyAnalysisInterface { - foreach ($docs as $doc) { - if (preg_match($preg, $doc, $match)) { - return $match["t"]; + if (!$this->propertyAnalysis) { + $this->propertyAnalysis = new PropertyAnalysis(); + foreach (AttentionKeywords::toArray() as $keyword) { + $this->propertyAnalysis->addVarKeyWord($keyword); } } - return ""; + return $this->propertyAnalysis; } /** - * 分割注释文档 - * @param string $docComment 注释文档 - * @return string[] 分割过滤后的数据 + * @param PropertyAnalysisInterface $propertyAnalysis 类的属性分析器 + * @return static 对象本身 */ - private function splitDoc($docComment): array + public function setPropertyAnalysis(PropertyAnalysisInterface $propertyAnalysis): Array2ClassConverter { - if (!is_string($docComment)) { - return []; - } - return array_filter( - array_map( - function ($str) { - return trim(trim(trim($str), "/*")); - }, - explode("\n", $docComment) - ), - function ($str) { - return strlen($str) > 0; - } - ); + $this->propertyAnalysis = $propertyAnalysis; + return $this; } /** - * 根据属性注解 - * @param ReflectionProperty $reflectionProperty - * @return array 参数信息,结构定义参数 getPropertyTypeBySetter - * @see getPropertyTypeBySetter + * 根据数据和数据描述生成数据 + * @param mixed $orgValue 原始数据 + * @param TypeDescription $typeDescription 类型描述 + * @param ReflectionProperty $property 属性反射,用来提供错误信息 + * @return mixed */ - private function getPropertyTypeByDoc(ReflectionProperty $reflectionProperty): array + private function createValue($orgValue, ?TypeDescription $typeDescription, ReflectionProperty $property) { - $docType = $this->getTypeFromDoc( - $this->splitDoc($reflectionProperty->getDocComment()), - "#@var (?[\\w\\\\]+(?:\\[\\])?)#" - ); - if (substr($docType, -2) == "[]") { - return [ - "type" => "array", - "name" => substr($docType, 0, -2), - ]; - } elseif ($docType == "" || $docType == "mixed") { - return ["type" => "mixed"]; - } else { - return [ - "type" => "custom", - "name" => $docType, - ]; + if (!$typeDescription) { + # 表示不受限 + return $orgValue; } - } - - /** - * 根据值与属性定义生成数据 - * @param mixed $value 源数据 - * @param array $typeInfo 参数信息,结构见getPropertyTypeBySetter - * @param ReflectionProperty $reflectionProperty 属性反射 - * @return mixed 生成后的值 - * @see getPropertyTypeBySetter - */ - private function createValue($value, array $typeInfo, ReflectionProperty $reflectionProperty) - { - switch ($typeInfo["type"]) { - case "class": - return $this->createValueByRefClass($value, $typeInfo["name"]); - case "type": - /** @var \ReflectionType $type */ - $type = $typeInfo["name"]; - return $this->createValueByBuildinName($value, $type->getName()); - case "mixed": - return $value; - case "array": - if (!is_array($value)) { - throw new ConvertFailed("{$reflectionProperty->class}::{$reflectionProperty->name}要求数组而传入非数组"); - } - if (!isset($typeInfo["name"])) { - return $value; + $type = $typeDescription->getPropertyType(); + if ($typeDescription->isClass()) { + return $this->createValueByRefClass($orgValue, $type); + } + switch ($type) { + case BuildinType::CALLABLE: + throw new ConvertFailed("{$property->class}::{$property->name}类型限定不能是回调函数"); + case BuildinType::ARRAY: + if (!is_array($orgValue)) { + throw new ConvertFailed("{$property->class}::{$property->name}要求数组而传入非数组"); } - $result = []; - foreach ($value as $item) { - $result[] = $this->createValueByName($item, $typeInfo["name"], $reflectionProperty); + $elementType = $typeDescription->getArrayElement(); + if ($elementType) { + $list = []; + foreach ($orgValue as $item) { + $list[] = $this->createValue($item, $elementType, $property); + } + return $list; + } else { + return $orgValue; } - return $result; default: - return $this->createValueByName($value, $typeInfo["name"], $reflectionProperty); + $formater = BuildinTypeHelper::VALUE_FORMAT[$type]; + return $formater($orgValue); } } @@ -408,70 +320,6 @@ class Array2ClassConverter return $this->convertByRef($value, $reflectionClass); } } - - /** - * 根据内置参数类名生成值 - * @param mixed $value 输入的数据 - * @param string $typeName 预期输出的内置类型名,不包含array和callable - * @return mixed - */ - private function createValueByBuildinName($value, string $typeName) - { - $fnName = self::BUILDIN_TYPE[$typeName]; - return $fnName($value); - } - - /** - * 根据名称创建数据 - * @param mixed $value 数据 - * @param string $typeName 名称 - * @param ReflectionProperty $reflectionProperty 属性反射 - * @return mixed 创建后的数据 - */ - private function createValueByName($value, string $typeName, ReflectionProperty $reflectionProperty) - { - if (isset(self::BUILDIN_TYPE[$typeName])) { - $fnName = self::BUILDIN_TYPE[$typeName]; - return $fnName($value); - } else { - $fullName = $this->getFullClassName($reflectionProperty->getDeclaringClass(), $typeName); - if (class_exists($fullName)) { - /** @noinspection PhpUnhandledExceptionInspection */ - return $this->createValueByRefClass($value, new ReflectionClass($fullName)); - } elseif ($fullName == "mixed") { - return $value; - } else { - throw new ConvertFailed( - "{$reflectionProperty->class}::{$reflectionProperty->name}使用的类型标识{$typeName}无法识别" - ); - } - } - } - - /** - * 根据类的反射和短名称,确认其完整类名 - * @param ReflectionClass $reflectionClass 属性所属类的反射 - * @param string $shortName 短名称 - * @return string 完整名称 - */ - private function getFullClassName(ReflectionClass $reflectionClass, string $shortName): string - { - if ($shortName[0] == "\\") { - return substr($shortName, 1); - } - - $fileName = $reflectionClass->getFileName(); - if (!isset($this->shortMap[$fileName])) { - # 解析 use 列表 - $this->shortMap[$fileName] = PhpFileHelper::parseUseList($fileName); - } - if (isset($this->shortMap[$fileName][$shortName])) { - return $this->shortMap[$fileName][$shortName]; - } - - $class = $reflectionClass->getNamespaceName() . "\\{$shortName}"; - return class_exists($class) ? $class : $shortName; - } } # end of file diff --git a/src/main/Array2ClassInterface.php b/src/main/Array2ClassInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d72fc1a23ba8f208658980b28b9b3f7d494df767 --- /dev/null +++ b/src/main/Array2ClassInterface.php @@ -0,0 +1,30 @@ +value = $value; + } +} + +# end of file diff --git a/src/test/Demo/WithFrom.php b/src/test/Demo/WithFrom.php new file mode 100644 index 0000000000000000000000000000000000000000..93814401e2c070c8f8b48f754b4b0b3ab8b93695 --- /dev/null +++ b/src/test/Demo/WithFrom.php @@ -0,0 +1,22 @@ + 4], - WithStaticPo::class, - WithStaticPo::class . "::name是静态属性,不可赋值", - ["tryWithoutConstructor" => true], - ], [ [], WithStaticPo::class, "指定的输出类" . WithStaticPo::class . "构造函数需要参数且未实现接口" . PlainOBjectFactory::class, + ["tryConstructWithParam" => false], ], [ [], @@ -71,7 +66,7 @@ class ErrorTest extends TestCase [ ["call" => 5], Pojo2::class, - Pojo2::class . "::setCall的参数要求为callable,无法调用", + Pojo2::class . "::call类型限定不能是回调函数", ], [ ["ary" => 3], @@ -88,11 +83,6 @@ class ErrorTest extends TestCase Pojo1::class, Pojo3::class . "无法使用非数组构建", ], - [ - ["errorTip" => ""], - Pojo3::class, - Pojo3::class . "::errorTip使用的类型标识errorTip无法识别", - ], ]; } } diff --git a/src/test/TestCases/NormalTest.php b/src/test/TestCases/NormalTest.php index 22b470035a2e15893ca47beeeee4af509159a5d8..fdb277d302116c8d1bff4ea941b05d946c0a17cf 100644 --- a/src/test/TestCases/NormalTest.php +++ b/src/test/TestCases/NormalTest.php @@ -7,8 +7,11 @@ use DreamCat\Array2Class\Demo\Inner\Pojo3; use DreamCat\Array2Class\Demo\Inner\Status; use DreamCat\Array2Class\Demo\Pojo1; use DreamCat\Array2Class\Demo\Pojo2; +use DreamCat\Array2Class\Demo\SinglePojo; use DreamCat\Array2Class\Demo\SpecSetter; +use DreamCat\Array2Class\Demo\WithFrom; use DreamCat\Array2Class\Demo\WithStaticPo; +use Dreamcat\PropertyAnalysis\PropertyAnalysis; use PHPUnit\Framework\TestCase; /** @@ -36,6 +39,7 @@ class NormalTest extends TestCase foreach ($config as $key => $val) { $fn = "set{$key}"; $convert->$fn($val); + self::assertEquals($val, $convert->{"is{$key}"}()); } $result = $convert->convert($data, $class); self::assertEquals($expect, $result, "第" . self::$normalIdx . "条测试用例未通过"); @@ -110,6 +114,7 @@ class NormalTest extends TestCase [ "createNewProperty" => true, "tryWithoutConstructor" => true, + "tryConstructWithParam" => false, ], ], [ @@ -179,8 +184,54 @@ class NormalTest extends TestCase ] ), ], + [ + [ + "key_word" => "key", + "array" => [ + "abc", + "ccd", + ], + ], + WithFrom::class, + WithFrom::builder([ + "keyWord" => "key", + "array" => new SinglePojo([ + "abc", + "ccd", + ]), + ]), + ], ]; } + + /** + * 测试属性分析器手动设置 + * @return void + */ + public function testSetPropertyAnalysis() + { + $data = [ + "key_word" => "key", + "array" => [ + "abc", + "ccd", + ], + ]; + $expect = WithFrom::builder([ + "array" => new SinglePojo([ + "abc", + "ccd", + ]), + "key_word" => "key", + ]); + + $analysis = new PropertyAnalysis(); + $convert = new Array2ClassConverter(); + $convert->setPropertyAnalysis($analysis) + ->setCreateNewProperty(true); + $result = $convert->convert($data, WithFrom::class); + self::assertEquals($expect, $result, "testSetPropertyAnalysis测试未通过"); + } } # end of file