From 186bff4eabb36ef56e1db44917334bc9be664d1d Mon Sep 17 00:00:00 2001 From: Yoanm Date: Sat, 20 Apr 2019 02:07:43 +0200 Subject: [PATCH 1/5] Improve --- src/App/Helper/MinMaxHelper.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/App/Helper/MinMaxHelper.php b/src/App/Helper/MinMaxHelper.php index ff4f159..824b7d6 100644 --- a/src/App/Helper/MinMaxHelper.php +++ b/src/App/Helper/MinMaxHelper.php @@ -81,12 +81,7 @@ private function appendCollectionDoc(CollectionDoc $doc, Constraint $constraint) } elseif ($constraint instanceof Assert\NotBlank && null === $doc->getMinItem()) { // Not blank so minimum 1 item $doc->setMinItem(1); - }/* // Documentation does not mention array, counter to NotBlank constraint - elseif ($constraint instanceof Assert\Blank && null === $doc->getMaxItem()) { - // Blank so maximum 0 item - $doc->setMaxItem(0); - }*/ - if ($constraint instanceof Assert\GreaterThan || $constraint instanceof Assert\GreaterThanOrEqual) { + } elseif ($constraint instanceof Assert\GreaterThan || $constraint instanceof Assert\GreaterThanOrEqual) { $doc->setMinItem( $constraint instanceof Assert\GreaterThanOrEqual ? $constraint->value @@ -98,7 +93,11 @@ private function appendCollectionDoc(CollectionDoc $doc, Constraint $constraint) ? $constraint->value : $constraint->value - 1 ); - } + } /* Documentation does not mention array, counter to NotBlank constraint + elseif ($constraint instanceof Assert\Blank && null === $doc->getMaxItem()) { + // Blank so maximum 0 item + $doc->setMaxItem(0); + }*/ } /** From 7dc4d29a3fb208f6d77f04f5718ca57917b75f08 Mon Sep 17 00:00:00 2001 From: Yoanm Date: Sat, 20 Apr 2019 02:27:06 +0200 Subject: [PATCH 2/5] Imprve --- src/App/Helper/MinMaxHelper.php | 34 +++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/App/Helper/MinMaxHelper.php b/src/App/Helper/MinMaxHelper.php index 824b7d6..3dfe19c 100644 --- a/src/App/Helper/MinMaxHelper.php +++ b/src/App/Helper/MinMaxHelper.php @@ -81,23 +81,12 @@ private function appendCollectionDoc(CollectionDoc $doc, Constraint $constraint) } elseif ($constraint instanceof Assert\NotBlank && null === $doc->getMinItem()) { // Not blank so minimum 1 item $doc->setMinItem(1); - } elseif ($constraint instanceof Assert\GreaterThan || $constraint instanceof Assert\GreaterThanOrEqual) { - $doc->setMinItem( - $constraint instanceof Assert\GreaterThanOrEqual - ? $constraint->value - : $constraint->value + 1 - ); - } elseif ($constraint instanceof Assert\LessThan || $constraint instanceof Assert\LessThanOrEqual) { - $doc->setMaxItem( - $constraint instanceof Assert\LessThanOrEqual - ? $constraint->value - : $constraint->value - 1 - ); } /* Documentation does not mention array, counter to NotBlank constraint elseif ($constraint instanceof Assert\Blank && null === $doc->getMaxItem()) { // Blank so maximum 0 item $doc->setMaxItem(0); }*/ + $this->appendLessGreaterThanMinMaxItem($doc, $constraint); } /** @@ -123,4 +112,25 @@ private function appendNumberMinMax(NumberDoc $doc, Constraint $constraint) : vo $doc->setMin($constraint->value); } } + + /** + * @param CollectionDoc $doc + * @param Constraint $constraint + */ + private function appendLessGreaterThanMinMaxItem(CollectionDoc $doc, Constraint $constraint): void + { + if ($constraint instanceof Assert\GreaterThan || $constraint instanceof Assert\GreaterThanOrEqual) { + $doc->setMinItem( + $constraint instanceof Assert\GreaterThanOrEqual + ? $constraint->value + : $constraint->value + 1 + ); + } elseif ($constraint instanceof Assert\LessThan || $constraint instanceof Assert\LessThanOrEqual) { + $doc->setMaxItem( + $constraint instanceof Assert\LessThanOrEqual + ? $constraint->value + : $constraint->value - 1 + ); + } + } } From 3721a09c9a5788dd3ca0f93c181022cb9b9bddef Mon Sep 17 00:00:00 2001 From: Yo Date: Sat, 20 Apr 2019 20:25:54 +0200 Subject: [PATCH 3/5] Polish (#19) --- src/App/Helper/ClassComparatorTrait.php | 32 ++++ src/App/Helper/DocTypeHelper.php | 109 +++++++---- src/App/Helper/MinMaxHelper.php | 117 ++++++++---- src/App/Helper/StringDocHelper.php | 137 ++++++++------ src/App/Helper/TypeGuesser.php | 83 ++++----- .../ConstraintToParamsDocTransformer.php | 173 +++++++++++------- 6 files changed, 405 insertions(+), 246 deletions(-) create mode 100644 src/App/Helper/ClassComparatorTrait.php diff --git a/src/App/Helper/ClassComparatorTrait.php b/src/App/Helper/ClassComparatorTrait.php new file mode 100644 index 0000000..7430e35 --- /dev/null +++ b/src/App/Helper/ClassComparatorTrait.php @@ -0,0 +1,32 @@ + ScalarDoc::class, + 'string' => StringDoc::class, + 'bool' => BooleanDoc::class, + 'boolean' => BooleanDoc::class, + 'int' => IntegerDoc::class, + 'integer' => IntegerDoc::class, + 'float' => FloatDoc::class, + 'long' => FloatDoc::class, + 'double' => FloatDoc::class, + 'real' => FloatDoc::class, + 'numeric' => NumberDoc::class, + 'array' => ArrayDoc::class, + 'object' => ObjectDoc::class, + ]; + /** * @param ConstraintPayloadDocHelper $constraintPayloadDocHelper * @param TypeGuesser $typeGuesser @@ -56,23 +72,7 @@ protected function getDocFromTypeConstraintOrPayloadDocIfExist(array $constraint $doc = null; // Check if a Type constraint exist or if a constraint have a type documentation foreach ($constraintList as $constraint) { - if (null !== ($typeFromPayload = $this->constraintPayloadDocHelper->getTypeIfExist($constraint))) { - $doc = $this->normalizeType($typeFromPayload); - } elseif ($constraint instanceof Assert\Type) { - $doc = $this->normalizeType(strtolower($constraint->type)); - } elseif ($constraint instanceof Assert\Existence && count($constraint->constraints) > 0) { - $doc = $this->guess($constraint->constraints); - } elseif ($constraint instanceof Assert\IdenticalTo) { - // Strict comparison so value define the type - $doc = $this->normalizeType(gettype($constraint->value)); - } elseif ($constraint instanceof Assert\Callback) { - $callbackResult = call_user_func($constraint->callback); - $doc = $this->guess( - is_array($callbackResult) - ? $callbackResult - : [$callbackResult] - ); - } + $doc = $this->createDocFromConstraint($constraint); if (null !== $doc) { break; @@ -89,24 +89,67 @@ protected function getDocFromTypeConstraintOrPayloadDocIfExist(array $constraint */ private function normalizeType(string $type) : ?TypeDoc { - if ('scalar' === $type) { - return new ScalarDoc(); - } elseif ('string' === $type) { - return new StringDoc(); - } elseif (in_array($type, ['bool', 'boolean'])) { - return new BooleanDoc(); - } elseif (in_array($type, ['int', 'integer'])) { - return new IntegerDoc(); - } elseif (in_array($type, ['float', 'long', 'double', 'real'])) { - return new FloatDoc(); - } elseif ('numeric' === $type) { - return new NumberDoc(); - } elseif ('array' === $type) { - return new ArrayDoc(); - } elseif ('object' === $type) { - return new ObjectDoc(); + if (array_key_exists($type, self::MANAGED_TYPE_CLASS_LIST)) { + $class = self::MANAGED_TYPE_CLASS_LIST[$type]; + + return new $class(); } return null; } + + /** + * @param Constraint $constraint + * + * @return TypeDoc|null + */ + private function createDocFromConstraint(Constraint $constraint) : ?TypeDoc + { + $doc = null; + + if (null !== ($stringType = $this->getStringType($constraint))) { + $doc = $this->normalizeType($stringType); + } elseif ($constraint instanceof Assert\Callback) { + $doc = $this->getTypeFromCallbackConstraint($constraint); + } elseif ($constraint instanceof Assert\Existence && count($constraint->constraints) > 0) { + $doc = $this->guess($constraint->constraints); + } + + return $doc; + } + + /** + * @param Assert\Callback $constraint + * + * @return TypeDoc + */ + private function getTypeFromCallbackConstraint(Assert\Callback $constraint): TypeDoc + { + $callbackResult = call_user_func($constraint->callback); + $doc = $this->guess( + is_array($callbackResult) + ? $callbackResult + : [$callbackResult] + ); + return $doc; + } + + /** + * @param Constraint $constraint + * + * @return string|null + */ + private function getStringType(Constraint $constraint) : ?string + { + $stringType = null; + if (null !== ($typeFromPayload = $this->constraintPayloadDocHelper->getTypeIfExist($constraint))) { + $stringType = $typeFromPayload; + } elseif ($constraint instanceof Assert\Type) { + $stringType = strtolower($constraint->type); + } elseif ($constraint instanceof Assert\IdenticalTo) {// Strict comparison so value define the type + $stringType = gettype($constraint->value); + } + + return $stringType; + } } diff --git a/src/App/Helper/MinMaxHelper.php b/src/App/Helper/MinMaxHelper.php index 3dfe19c..c11bd31 100644 --- a/src/App/Helper/MinMaxHelper.php +++ b/src/App/Helper/MinMaxHelper.php @@ -13,6 +13,8 @@ */ class MinMaxHelper { + use ClassComparatorTrait; + /** * @param TypeDoc $doc * @param Constraint $constraint @@ -34,20 +36,19 @@ public function append(TypeDoc $doc, Constraint $constraint) : void */ private function appendStringDoc(StringDoc $doc, Constraint $constraint) : void { + $min = $max = null; if ($constraint instanceof Assert\Length) { - if (null !== $constraint->min) { - $doc->setMinLength((int) $constraint->min); - } - if (null !== $constraint->max) { - $doc->setMaxLength((int) $constraint->max); - } + $min = $constraint->min; + $max = $constraint->max; } elseif ($constraint instanceof Assert\NotBlank && null === $doc->getMinLength()) { // Not blank so minimum 1 character - $doc->setMinLength(1); + $min = 1; } elseif ($constraint instanceof Assert\Blank && null === $doc->getMaxLength()) { // Blank so maximum 0 character - $doc->setMaxLength(0); + $max = 0; } + + $this->setMinMaxLengthIfNotNull($doc, $min, $max); } /** @@ -71,21 +72,19 @@ private function appendNumberDoc(NumberDoc $doc, Constraint $constraint) : void */ private function appendCollectionDoc(CollectionDoc $doc, Constraint $constraint) : void { + $min = $max = null; if ($constraint instanceof Assert\Choice || $constraint instanceof Assert\Count) { - if (null !== $constraint->min) { - $doc->setMinItem((int) $constraint->min); - } - if (null !== $constraint->max) { - $doc->setMaxItem((int) $constraint->max); - } + $min = $constraint->min; + $max = $constraint->max; } elseif ($constraint instanceof Assert\NotBlank && null === $doc->getMinItem()) { // Not blank so minimum 1 item - $doc->setMinItem(1); + $min = 1; } /* Documentation does not mention array, counter to NotBlank constraint elseif ($constraint instanceof Assert\Blank && null === $doc->getMaxItem()) { // Blank so maximum 0 item - $doc->setMaxItem(0); + $max = 0; }*/ + $this->setMinMaxItemIfNotNull($doc, $min, $max); $this->appendLessGreaterThanMinMaxItem($doc, $constraint); } @@ -95,22 +94,21 @@ private function appendCollectionDoc(CollectionDoc $doc, Constraint $constraint) */ private function appendNumberMinMax(NumberDoc $doc, Constraint $constraint) : void { + $min = $max = null; if ($constraint instanceof Assert\Range) { - if (null !== $constraint->min) { - $doc->setMin($constraint->min); - } - if (null !== $constraint->max) { - $doc->setMax($constraint->max); - } + $min = $constraint->min; + $max = $constraint->max; } elseif ($constraint instanceof Assert\LessThanOrEqual || $constraint instanceof Assert\LessThan ) { - $doc->setMax($constraint->value); + $max = $constraint->value; } elseif ($constraint instanceof Assert\GreaterThanOrEqual || $constraint instanceof Assert\GreaterThan ) { - $doc->setMin($constraint->value); + $min = $constraint->value; } + + $this->setMinMaxIfNotNull($doc, $min, $max); } /** @@ -119,18 +117,65 @@ private function appendNumberMinMax(NumberDoc $doc, Constraint $constraint) : vo */ private function appendLessGreaterThanMinMaxItem(CollectionDoc $doc, Constraint $constraint): void { - if ($constraint instanceof Assert\GreaterThan || $constraint instanceof Assert\GreaterThanOrEqual) { - $doc->setMinItem( - $constraint instanceof Assert\GreaterThanOrEqual - ? $constraint->value - : $constraint->value + 1 - ); - } elseif ($constraint instanceof Assert\LessThan || $constraint instanceof Assert\LessThanOrEqual) { - $doc->setMaxItem( - $constraint instanceof Assert\LessThanOrEqual - ? $constraint->value - : $constraint->value - 1 - ); + $min = $max = null; + $gtConstraintList = [Assert\GreaterThan::class, Assert\GreaterThanOrEqual::class]; + $ltConstraintList = [Assert\LessThan::class, Assert\LessThanOrEqual::class]; + if (null !== ($match = $this->getMatchingClassNameIn($constraint, $gtConstraintList))) { + $min = ($match === Assert\GreaterThanOrEqual::class) + ? $constraint->value + : $constraint->value + 1; + } elseif (null !== ($match = $this->getMatchingClassNameIn($constraint, $ltConstraintList))) { + $max = ($match === Assert\LessThanOrEqual::class) + ? $constraint->value + : $constraint->value - 1 + ; + } + + $this->setMinMaxItemIfNotNull($doc, $min, $max); + } + + /** + * @param StringDoc $doc + * @param null|int|mixed $min + * @param null|int|mixed $max + */ + private function setMinMaxLengthIfNotNull(StringDoc $doc, $min, $max): void + { + if (null !== $min) { + $doc->setMinLength((int)$min); + } + if (null !== $max) { + $doc->setMaxLength((int)$max); + } + } + + /** + * @param CollectionDoc $doc + * @param null|int|mixed $min + * @param null|int|mixed $max + */ + private function setMinMaxItemIfNotNull(CollectionDoc $doc, $min, $max): void + { + if (null !== $min) { + $doc->setMinItem((int) $min); + } + if (null !== $max) { + $doc->setMaxItem((int) $max); + } + } + + /** + * @param NumberDoc $doc + * @param null|int|mixed $min + * @param null|int|mixed $max + */ + private function setMinMaxIfNotNull(NumberDoc $doc, $min, $max): void + { + if (null !== $min) { + $doc->setMin($min); + } + if (null !== $max) { + $doc->setMax($max); } } } diff --git a/src/App/Helper/StringDocHelper.php b/src/App/Helper/StringDocHelper.php index 6577874..6edfa87 100644 --- a/src/App/Helper/StringDocHelper.php +++ b/src/App/Helper/StringDocHelper.php @@ -2,7 +2,7 @@ namespace Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper; use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\StringDoc; use Yoanm\JsonRpcServerDoc\Domain\Model\Type\TypeDoc; @@ -11,6 +11,35 @@ */ class StringDocHelper { + use ClassComparatorTrait; + + const CONSTRAINT_WITH_FORMAT_FROM_CLASSNAME = [ + Constraints\Bic::class, + Constraints\CardScheme::class, + Constraints\Country::class, + Constraints\Currency::class, + Constraints\Date::class, + Constraints\DateTime::class, + Constraints\Range::class, + Constraints\Email::class, + Constraints\File::class, + Constraints\Iban::class, + Constraints\Ip::class, + Constraints\Isbn::class, + Constraints\Issn::class, + Constraints\Language::class, + Constraints\Locale::class, + Constraints\Luhn::class, + Constraints\Time::class, + Constraints\Url::class, + Constraints\Uuid::class, + ]; + + const CONSTRAINT_WITH_FORMAT_FROM_PROPERTY = [ + Constraints\Regex::class => 'pattern', + Constraints\Expression::class => 'expression', + ]; + /** * @param TypeDoc $doc * @param Constraint $constraint @@ -19,72 +48,60 @@ class StringDocHelper */ public function append(TypeDoc $doc, Constraint $constraint) : void { - // If format already defined or type is defined and is not a string nor scalar => give up + // If not a string nor scalar => give up if (!$doc instanceof StringDoc) { return; } - $constraintClass = get_class($constraint); - $constraintForFormatList = [ - Assert\Bic::class, - Assert\CardScheme::class, - Assert\Country::class, - Assert\Currency::class, - Assert\Date::class, - Assert\DateTime::class, - Assert\Email::class, - Assert\File::class, - Assert\Iban::class, - Assert\Ip::class, - Assert\Isbn::class, - Assert\Issn::class, - Assert\Language::class, - Assert\Locale::class, - Assert\Luhn::class, - Assert\Time::class, - Assert\Url::class, - Assert\Uuid::class, - ]; + if (null !== $this->getMatchingClassNameIn($constraint, self::CONSTRAINT_WITH_FORMAT_FROM_CLASSNAME)) { + $this->enhanceFromClassName($doc, $constraint); + } elseif (null !== ($match = $this->getMatchingClassNameIn( + $constraint, + array_keys(self::CONSTRAINT_WITH_FORMAT_FROM_PROPERTY) + ))) { + $doc->setFormat($constraint->{self::CONSTRAINT_WITH_FORMAT_FROM_PROPERTY[$match]}); + } + } - if (in_array($constraintClass, $constraintForFormatList)) { - if (Assert\DateTime::class === $constraintClass) { - $format = 'datetime'; - } else { - $format = lcfirst((new \ReflectionClass($constraint))->getShortName()); - } - $doc->setFormat($format); + /** + * @param StringDoc $doc + * @param Constraint $constraint + * + * @throws \ReflectionException + */ + private function enhanceFromClassName(StringDoc $doc, Constraint $constraint): void + { + static $dateTimeClassList = [Constraints\DateTime::class, Constraints\Range::class]; + if (null !== $this->getMatchingClassNameIn($constraint, $dateTimeClassList)) { + // If it's a string range it must be a date range check (either it must be an integer or float value) + $format = 'datetime'; + } else { + $format = lcfirst((new \ReflectionClass($constraint))->getShortName()); + } + $doc->setFormat($format); - if ($constraint instanceof Assert\Uuid) { - $formatDescription = sprintf( - '%s (%s)', - ucfirst($format), - implode( - ', ', - array_map( - function ($version) { - return sprintf('v%s', $version); - }, - $constraint->versions - ) - ) - ); - $doc->setDescription( - sprintf( - '%s%s%s', - $doc->getDescription(), - strlen($doc->getDescription()) ? ' ' : '', - $formatDescription + if ($constraint instanceof Constraints\Uuid) { + $formatDescription = sprintf( + '%s (%s)', + ucfirst($format), + implode( + ', ', + array_map( + function ($version) { + return sprintf('v%s', $version); + }, + $constraint->versions ) - ); - } - } elseif ($constraint instanceof Assert\Regex) { - $doc->setFormat($constraint->pattern); - } elseif ($constraint instanceof Assert\Range) { - // If it's a string range it must be a date range check (either it must be an integer or float value) - $doc->setFormat('datetime'); - } elseif ($constraint instanceof Assert\Expression) { - // If it's a string range it must be a date range check (either it must be an integer or float value) - $doc->setFormat($constraint->expression); + ) + ); + $doc->setDescription( + sprintf( + '%s%s%s', + $doc->getDescription(), + strlen($doc->getDescription()) ? ' ' : '', + $formatDescription + ) + ); } } } diff --git a/src/App/Helper/TypeGuesser.php b/src/App/Helper/TypeGuesser.php index 4d7969f..121177a 100644 --- a/src/App/Helper/TypeGuesser.php +++ b/src/App/Helper/TypeGuesser.php @@ -17,6 +17,33 @@ */ class TypeGuesser { + use ClassComparatorTrait; + + const STRING_CONSTRAINT_CLASS_LIST = [ + Assert\Length::class, // << Applied on string only + Assert\Date::class, // << validator expect a string with specific format + Assert\Time::class, // << validator expect a string with specific format + Assert\Bic::class, + Assert\CardScheme::class, + Assert\Country::class, + Assert\Currency::class, + Assert\Email::class, + Assert\File::class, + Assert\Iban::class, + Assert\Ip::class, + Assert\Isbn::class, + Assert\Issn::class, + Assert\Language::class, + Assert\Locale::class, + Assert\Luhn::class, + Assert\Url::class, + Assert\Uuid::class, + ]; + const BOOLEAN_CONSTRAINT_CLASS_LIST = [ + Assert\IsTrue::class, + Assert\IsFalse::class, + ]; + /** * @param array $constraintList * @@ -88,35 +115,10 @@ private function isAbstractType(TypeDoc $doc) : bool */ private function guessPrimaryTypeFromConstraint(Constraint $constraint) : ?TypeDoc { - static $stringConstraintClassList = [ - Assert\Length::class, // << Applied on string only - Assert\Date::class, // << validator expect a string with specific format - Assert\Time::class, // << validator expect a string with specific format - Assert\Bic::class, - Assert\CardScheme::class, - Assert\Country::class, - Assert\Currency::class, - Assert\Email::class, - Assert\File::class, - Assert\Iban::class, - Assert\Ip::class, - Assert\Isbn::class, - Assert\Issn::class, - Assert\Language::class, - Assert\Locale::class, - Assert\Luhn::class, - Assert\Url::class, - Assert\Uuid::class, - ]; - static $booleanConstraintClassList = [ - Assert\IsTrue::class, - Assert\IsFalse::class, - ]; - // Try to guess primary types - if ($this->isInstanceOfOneClassIn($constraint, $stringConstraintClassList)) { + if (null !== $this->getMatchingClassNameIn($constraint, self::STRING_CONSTRAINT_CLASS_LIST)) { return new StringDoc(); - } elseif ($this->isInstanceOfOneClassIn($constraint, $booleanConstraintClassList)) { + } elseif (null !== $this->getMatchingClassNameIn($constraint, self::BOOLEAN_CONSTRAINT_CLASS_LIST)) { return new BooleanDoc(); } elseif ($constraint instanceof Assert\DateTime) { return $this->guessDateTimeType($constraint); @@ -124,11 +126,7 @@ private function guessPrimaryTypeFromConstraint(Constraint $constraint) : ?TypeD return $this->guestCollectionType($constraint); } elseif ($constraint instanceof Assert\Regex) { return new ScalarDoc(); - } elseif ($constraint instanceof Assert\All // << Applied only on array - || ($constraint instanceof Assert\Choice - && true === $constraint->multiple // << expect an array multiple choices - ) - ) { + } elseif ($this->isArrayConstraint($constraint)) { return new ArrayDoc(); } @@ -166,24 +164,15 @@ private function guestCollectionType(Assert\Collection $constraint) : TypeDoc } /** - * @param $object - * @param array $classList + * @param Constraint $constraint * * @return bool */ - private function isInstanceOfOneClassIn($object, array $classList) : bool + private function isArrayConstraint(Constraint $constraint): bool { - $actualClassList = array_merge( - [get_class($object)], - class_implements($object), - class_uses($object) - ); - $parentClass = get_parent_class($object); - while (false !== $parentClass) { - $actualClassList[] = $parentClass; - $parentClass = get_parent_class($parentClass); - } - - return count(array_intersect($actualClassList, $classList)) > 0; + return $constraint instanceof Assert\All // << Applied only on array + || ($constraint instanceof Assert\Choice + && true === $constraint->multiple // << expect an array multiple choices + ); } } diff --git a/src/Infra/Transformer/ConstraintToParamsDocTransformer.php b/src/Infra/Transformer/ConstraintToParamsDocTransformer.php index c535afa..7930c7a 100644 --- a/src/Infra/Transformer/ConstraintToParamsDocTransformer.php +++ b/src/Infra/Transformer/ConstraintToParamsDocTransformer.php @@ -3,6 +3,7 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints as Assert; +use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\ClassComparatorTrait; use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\ConstraintPayloadDocHelper; use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\DocTypeHelper; use Yoanm\JsonRpcParamsSymfonyConstraintDoc\App\Helper\MinMaxHelper; @@ -16,6 +17,8 @@ */ class ConstraintToParamsDocTransformer { + use ClassComparatorTrait; + /** @var DocTypeHelper */ private $docTypeHelper; /** @var StringDocHelper */ @@ -25,6 +28,27 @@ class ConstraintToParamsDocTransformer /** @var ConstraintPayloadDocHelper */ private $constraintPayloadDocHelper; + const CONSTRAINT_WITH_ALLOWED_VALUE_LIST = [ + Assert\IsTrue::class => [true, 1, '1'], + Assert\IsFalse::class => [false, 0, '0'], + Assert\IsNull::class => [null], + ]; + + const CONSTRAINT_WITH_ALLOWED_VALUE_LIST_FROM_PROPERTY = [ + Assert\IdenticalTo::class => 'value', + Assert\EqualTo::class => 'value', + ]; + + const NULL_NOT_NULL_CONSTRAINT_LIST = [ + Assert\NotNull::class, + Assert\IsTrue::class, // If it is true, it cannot be null ... + Assert\IsFalse::class, // If it is false, it cannot be null ... + // If should be identical to something, it cannot be null (but can be identical to null) + Assert\IdenticalTo::class, + ]; + + + /** * @param DocTypeHelper $docTypeHelper * @param StringDocHelper $stringDocHelper @@ -129,28 +153,20 @@ private function appendCollectionDoc(TypeDoc $doc, Constraint $constraint) : voi private function appendValidItemListDoc(TypeDoc $doc, Constraint $constraint) : void { if ($constraint instanceof Assert\Choice) { - if ($constraint->callback && is_callable($constraint->callback)) { - $choiceList = call_user_func($constraint->callback); - } else { - $choiceList = $constraint->choices ?? []; - } - foreach ($choiceList as $choice) { - $this->addToAllowedValueListIfNotExist($doc, $choice); - } - } elseif ($constraint instanceof Assert\IsNull) { - $this->addToAllowedValueListIfNotExist($doc, null); - } elseif ($constraint instanceof Assert\IdenticalTo) { - $this->addToAllowedValueListIfNotExist($doc, $constraint->value); - } elseif ($constraint instanceof Assert\IsTrue) { - $this->addToAllowedValueListIfNotExist($doc, true); - $this->addToAllowedValueListIfNotExist($doc, 1); - $this->addToAllowedValueListIfNotExist($doc, '1'); - } elseif ($constraint instanceof Assert\IsFalse) { - $this->addToAllowedValueListIfNotExist($doc, false); - $this->addToAllowedValueListIfNotExist($doc, 0); - $this->addToAllowedValueListIfNotExist($doc, '0'); - } elseif ($constraint instanceof Assert\EqualTo) { - $this->addToAllowedValueListIfNotExist($doc, $constraint->value); + $this->appendChoiceAllowedValue($doc, $constraint); + } elseif (null !== ($match = $this->getMatchingClassNameIn( + $constraint, + array_keys(self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST_FROM_PROPERTY) + ))) { + $this->addToAllowedValueListIfNotExist( + $doc, + $constraint->{self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST_FROM_PROPERTY[$match]} + ); + } elseif (null !== ($match = $this->getMatchingClassNameIn( + $constraint, + array_keys(self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST) + ))) { + $this->addListToAllowedValueListIfNotExist($doc, self::CONSTRAINT_WITH_ALLOWED_VALUE_LIST[$match]); } } @@ -171,27 +187,20 @@ private function appendAllConstraintToDoc(ArrayDoc $doc, Assert\All $constraint) } /** - * @param $object - * @param array $classList - * - * @return bool + * @param TypeDoc $doc + * @param mixed[] $valueList */ - private function isInstanceOfOneClassIn($object, array $classList) : bool + private function addListToAllowedValueListIfNotExist(TypeDoc $doc, array $valueList) : void { - $actualClassList = array_merge( - [get_class($object)], - class_implements($object), - class_uses($object) - ); - $parentClass = get_parent_class($object); - while (false !== $parentClass) { - $actualClassList[] = $parentClass; - $parentClass = get_parent_class($parentClass); + foreach ($valueList as $value) { + $this->addToAllowedValueListIfNotExist($doc, $value); } - - return count(array_intersect($actualClassList, $classList)) > 0; } + /** + * @param TypeDoc $doc + * @param mixed $value + */ private function addToAllowedValueListIfNotExist(TypeDoc $doc, $value) : void { if (!in_array($value, $doc->getAllowedValueList(), true)) { @@ -207,14 +216,6 @@ private function addToAllowedValueListIfNotExist(TypeDoc $doc, $value) : void */ private function basicAppendToDoc(TypeDoc $doc, Constraint $constraint): void { - static $notNullConstraintList = [ - Assert\NotNull::class, - Assert\IsTrue::class, // If it is true, it cannot be null ... - Assert\IsFalse::class, // If it is false, it cannot be null ... - // If should be identical to something, it cannot be null (but can be identical to null) - Assert\IdenticalTo::class, - ]; - $this->stringDocHelper->append($doc, $constraint); $this->appendCollectionDoc($doc, $constraint); @@ -222,30 +223,62 @@ private function basicAppendToDoc(TypeDoc $doc, Constraint $constraint): void $this->appendValidItemListDoc($doc, $constraint); if ($constraint instanceof Assert\Existence) { - $doc->setRequired($constraint instanceof Assert\Required); - foreach ($constraint->constraints as $subConstraint) { - $this->appendToDoc($doc, $subConstraint); - } - } elseif ($this->isInstanceOfOneClassIn($constraint, $notNullConstraintList)) { - $doc->setNullable( - ($constraint instanceof Assert\IdenticalTo) - ? is_null($constraint->value) - : false - ); - $defaultValue = $exampleValue = null; - switch (true) { - case $constraint instanceof Assert\IsTrue: - $defaultValue = $exampleValue = true; - break; - case $constraint instanceof Assert\IsFalse: - $defaultValue = $exampleValue = false; - break; - case $constraint instanceof Assert\IdenticalTo: - $defaultValue = $exampleValue = $constraint->value; - break; - } - $doc->setDefault($doc->getDefault() ?? $defaultValue); - $doc->setExample($doc->getExample() ?? $exampleValue); + $this->appendExistenceConstraintData($doc, $constraint); + } elseif (null !== ($match = $this->getMatchingClassNameIn($constraint, self::NULL_NOT_NULL_CONSTRAINT_LIST))) { + $this->setNulNotNullConstraintData($doc, $constraint, $match); + } + } + + /** + * @param TypeDoc $doc + * @param Assert\Choice $constraint + */ + private function appendChoiceAllowedValue(TypeDoc $doc, Assert\Choice $constraint): void + { + if ($constraint->callback && is_callable($constraint->callback)) { + $choiceList = call_user_func($constraint->callback); + } else { + $choiceList = $constraint->choices ?? []; + } + $this->addListToAllowedValueListIfNotExist($doc, $choiceList); + } + + /** + * @param TypeDoc $doc + * @param Constraint $constraint + * @param string $sanitizedClass + */ + private function setNulNotNullConstraintData(TypeDoc $doc, Constraint $constraint, string $sanitizedClass): void + { + $isIdenticalTo = $sanitizedClass === Assert\IdenticalTo::class; + $doc->setNullable($isIdenticalTo ? is_null($constraint->value) : false); + $defaultValue = $exampleValue = null; + switch (true) { + case $sanitizedClass === Assert\IsTrue::class: + $defaultValue = $exampleValue = true; + break; + case $sanitizedClass === Assert\IsFalse::class: + $defaultValue = $exampleValue = false; + break; + case $isIdenticalTo: + $defaultValue = $exampleValue = $constraint->value; + break; + } + $doc->setDefault($doc->getDefault() ?? $defaultValue); + $doc->setExample($doc->getExample() ?? $exampleValue); + } + + /** + * @param TypeDoc $doc + * @param Assert\Existence $constraint + * + * @throws \ReflectionException + */ + private function appendExistenceConstraintData(TypeDoc $doc, Assert\Existence $constraint): void + { + $doc->setRequired($constraint instanceof Assert\Required); + foreach ($constraint->constraints as $subConstraint) { + $this->appendToDoc($doc, $subConstraint); } } } From 5524edc199abd7c011d2722fe5f08b1b2ba15a99 Mon Sep 17 00:00:00 2001 From: Yoanm Date: Sat, 20 Apr 2019 20:34:14 +0200 Subject: [PATCH 4/5] Fix --- src/App/Helper/MinMaxHelper.php | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/App/Helper/MinMaxHelper.php b/src/App/Helper/MinMaxHelper.php index f52a1c6..5c8fd7e 100644 --- a/src/App/Helper/MinMaxHelper.php +++ b/src/App/Helper/MinMaxHelper.php @@ -179,25 +179,4 @@ private function setMinMaxIfNotNull(NumberDoc $doc, $min, $max): void $doc->setMax($max); } } - - /** - * @param CollectionDoc $doc - * @param Constraint $constraint - */ - private function appendLessGreaterThanMinMaxItem(CollectionDoc $doc, Constraint $constraint): void - { - if ($constraint instanceof Assert\GreaterThan || $constraint instanceof Assert\GreaterThanOrEqual) { - $doc->setMinItem( - $constraint instanceof Assert\GreaterThanOrEqual - ? $constraint->value - : $constraint->value + 1 - ); - } elseif ($constraint instanceof Assert\LessThan || $constraint instanceof Assert\LessThanOrEqual) { - $doc->setMaxItem( - $constraint instanceof Assert\LessThanOrEqual - ? $constraint->value - : $constraint->value - 1 - ); - } - } } From 465fc59f3595de4c1bb7c90486f6029b1f1050db Mon Sep 17 00:00:00 2001 From: Yo Date: Sat, 20 Apr 2019 21:32:24 +0200 Subject: [PATCH 5/5] Polish (#21) --- src/App/Helper/TypeGuesser.php | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/App/Helper/TypeGuesser.php b/src/App/Helper/TypeGuesser.php index 121177a..8a37d83 100644 --- a/src/App/Helper/TypeGuesser.php +++ b/src/App/Helper/TypeGuesser.php @@ -116,18 +116,12 @@ private function isAbstractType(TypeDoc $doc) : bool private function guessPrimaryTypeFromConstraint(Constraint $constraint) : ?TypeDoc { // Try to guess primary types - if (null !== $this->getMatchingClassNameIn($constraint, self::STRING_CONSTRAINT_CLASS_LIST)) { - return new StringDoc(); - } elseif (null !== $this->getMatchingClassNameIn($constraint, self::BOOLEAN_CONSTRAINT_CLASS_LIST)) { - return new BooleanDoc(); + if (null !== ($type = $this->guessSimplePrimaryTypeFromConstraint($constraint))) { + return $type; } elseif ($constraint instanceof Assert\DateTime) { return $this->guessDateTimeType($constraint); } elseif ($constraint instanceof Assert\Collection) { return $this->guestCollectionType($constraint); - } elseif ($constraint instanceof Assert\Regex) { - return new ScalarDoc(); - } elseif ($this->isArrayConstraint($constraint)) { - return new ArrayDoc(); } return null; @@ -175,4 +169,24 @@ private function isArrayConstraint(Constraint $constraint): bool && true === $constraint->multiple // << expect an array multiple choices ); } + + /** + * @param Constraint $constraint + * + * @return TypeDoc|null + */ + private function guessSimplePrimaryTypeFromConstraint(Constraint $constraint) : ?TypeDoc + { + if (null !== $this->getMatchingClassNameIn($constraint, self::STRING_CONSTRAINT_CLASS_LIST)) { + return new StringDoc(); + } elseif (null !== $this->getMatchingClassNameIn($constraint, self::BOOLEAN_CONSTRAINT_CLASS_LIST)) { + return new BooleanDoc(); + } elseif ($constraint instanceof Assert\Regex) { + return new ScalarDoc(); + } elseif ($this->isArrayConstraint($constraint)) { + return new ArrayDoc(); + } + + return null; + } }