diff --git a/composer.json b/composer.json index ce04f61..66f5a78 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "wol-soft/php-json-schema-model-generator-production": "dev-jsonSchemaDraft7", + "wol-soft/php-json-schema-model-generator-production": "dev-jsonSchemaDraft2019", "wol-soft/php-micro-template": "^1.10.2", "php": ">=8.4", diff --git a/src/Draft/Draft_07.php b/src/Draft/Draft_07.php index d9de311..5842dcb 100644 --- a/src/Draft/Draft_07.php +++ b/src/Draft/Draft_07.php @@ -57,7 +57,7 @@ public function getDefinition(): DraftBuilder ->addValidator('minItems', new MinItemsValidatorFactory()) ->addValidator('maxItems', new MaxItemsValidatorFactory()) ->addValidator('uniqueItems', new UniqueItemsValidatorFactory()) - ->addValidator('contains', new ContainsValidatorFactory()) + ->addValidator('contains', new ContainsValidatorFactory(false)) ->addModifier(new DefaultArrayToEmptyArrayModifier())) ->addType((new Type('string')) ->addValidator('pattern', new PatternPropertyValidatorFactory()) diff --git a/src/Draft/Draft_2019_09.php b/src/Draft/Draft_2019_09.php new file mode 100644 index 0000000..4996b3a --- /dev/null +++ b/src/Draft/Draft_2019_09.php @@ -0,0 +1,20 @@ +getType('array') + ->addValidator('contains', new ContainsValidatorFactory()); + + return $builder; + } +} diff --git a/src/Draft/Draft_2020_12.php b/src/Draft/Draft_2020_12.php new file mode 100644 index 0000000..ab42d9c --- /dev/null +++ b/src/Draft/Draft_2020_12.php @@ -0,0 +1,11 @@ +draft = new AutoDetectionDraft(); - $this->classNameGenerator = new ClassNameGenerator(); - - // add all built-in filter and format validators $this->initFilter(); $this->initFormatValidator(); } @@ -151,7 +147,7 @@ public function getFilter(string $token): ?FilterInterface public function getClassNameGenerator(): ClassNameGeneratorInterface { - return $this->classNameGenerator; + return $this->classNameGenerator ??= new ClassNameGenerator(); } public function setClassNameGenerator(ClassNameGeneratorInterface $classNameGenerator): self @@ -259,7 +255,7 @@ public function setErrorRegistryClass(string $errorRegistryClass): self public function getDraft(): DraftInterface | DraftFactoryInterface { - return $this->draft; + return $this->draft ??= new AutoDetectionDraft(); } public function setDraft(DraftInterface | DraftFactoryInterface $draft): self diff --git a/src/Model/Validator/AdditionalItemsValidator.php b/src/Model/Validator/AdditionalItemsValidator.php index 3b9225e..8e488ea 100644 --- a/src/Model/Validator/AdditionalItemsValidator.php +++ b/src/Model/Validator/AdditionalItemsValidator.php @@ -5,10 +5,6 @@ namespace PHPModelGenerator\Model\Validator; use PHPModelGenerator\Exception\Arrays\InvalidAdditionalTupleItemsException; -use PHPModelGenerator\Exception\SchemaException; -use PHPModelGenerator\Model\Schema; -use PHPModelGenerator\Model\SchemaDefinition\JsonSchema; -use PHPModelGenerator\SchemaProcessor\SchemaProcessor; /** * Class AdditionalItemsValidator diff --git a/src/Model/Validator/Factory/Arrays/ContainsValidatorFactory.php b/src/Model/Validator/Factory/Arrays/ContainsValidatorFactory.php index c0f9909..bdb3a7b 100644 --- a/src/Model/Validator/Factory/Arrays/ContainsValidatorFactory.php +++ b/src/Model/Validator/Factory/Arrays/ContainsValidatorFactory.php @@ -5,18 +5,24 @@ namespace PHPModelGenerator\Model\Validator\Factory\Arrays; use PHPModelGenerator\Exception\Arrays\ContainsException; +use PHPModelGenerator\Exception\Arrays\MaxContainsException; +use PHPModelGenerator\Exception\Arrays\MinContainsException; use PHPModelGenerator\Exception\SchemaException; use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\Model\Schema; use PHPModelGenerator\Model\SchemaDefinition\JsonSchema; use PHPModelGenerator\Model\Validator\Factory\AbstractValidatorFactory; use PHPModelGenerator\Model\Validator\PropertyTemplateValidator; +use PHPModelGenerator\Model\Validator\PropertyValidator; use PHPModelGenerator\PropertyProcessor\PropertyFactory; use PHPModelGenerator\SchemaProcessor\SchemaProcessor; use PHPModelGenerator\Utils\RenderHelper; class ContainsValidatorFactory extends AbstractValidatorFactory { + public function __construct(private readonly bool $supportMinMaxContains = true) + {} + /** * @throws SchemaException */ @@ -40,18 +46,90 @@ public function modify( $propertySchema->navigate($this->key), ); + $countMatches = $this->supportMinMaxContains && + (array_key_exists('minContains', $json) || array_key_exists('maxContains', $json)); + $property->addValidator( - new PropertyTemplateValidator( + new class( $property, DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayContains.phptpl', [ 'property' => $nestedProperty, 'schema' => $schema, + 'countMatches' => $countMatches, + 'allowNoMatch' => $json['minContains'] ?? 1 === 0, 'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()), 'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(), ], ContainsException::class, - ), + ) extends PropertyTemplateValidator { + public function getValidatorSetUp(): string + { + return $this->templateValues['countMatches'] ? ' + $containsMatches = 0; + ' : ''; + } + }, ); + + if (!$countMatches) { + return; + } + + if (array_key_exists('minContains', $json)) { + if (!is_int($json['minContains']) || $json['minContains'] < 0) { + throw new SchemaException( + sprintf( + "Invalid minContains %s for property '%s' in file %s", + str_replace("\n", '', var_export($json[$this->key], true)), + $property->getName(), + $propertySchema->getFile(), + ), + ); + } + + $property->addValidator( + new PropertyValidator( + $property, + "\$containsMatches < {$json['minContains']}", + MinContainsException::class, + [$json['minContains'], '&$containsMatches'], + ), + ); + } + + if (array_key_exists('maxContains', $json)) { + if (!is_int($json['maxContains']) || $json['maxContains'] < 1) { + throw new SchemaException( + sprintf( + "Invalid minContains %s for property '%s' in file %s", + str_replace("\n", '', var_export($json[$this->key], true)), + $property->getName(), + $propertySchema->getFile(), + ), + ); + } + + $property->addValidator( + new PropertyValidator( + $property, + "\$containsMatches > {$json['maxContains']}", + MaxContainsException::class, + [$json['maxContains'], '&$containsMatches'], + ), + ); + } + + if (isset($json['minContains'], $json['maxContains']) && $json['minContains'] > $json['maxContains']) { + throw new SchemaException( + sprintf( + "minContains (%s) must not be larger than maxContains (%s) for property '%s' in file %s", + $json['minContains'], + $json['maxContains'], + $property->getName(), + $propertySchema->getFile(), + ), + ); + } } } diff --git a/src/Model/Validator/Factory/Arrays/MaxItemsValidatorFactory.php b/src/Model/Validator/Factory/Arrays/MaxItemsValidatorFactory.php index 4d7b940..7af1111 100644 --- a/src/Model/Validator/Factory/Arrays/MaxItemsValidatorFactory.php +++ b/src/Model/Validator/Factory/Arrays/MaxItemsValidatorFactory.php @@ -21,9 +21,9 @@ protected function getValidator(PropertyInterface $property, mixed $value): Prop { return new PropertyValidator( $property, - "is_array(\$value) && count(\$value) > $value", + "is_array(\$value) && (\$count = count(\$value)) > $value", MaxItemsException::class, - [$value], + [$value, '&$count'], ); } } diff --git a/src/Model/Validator/Factory/Arrays/MinItemsValidatorFactory.php b/src/Model/Validator/Factory/Arrays/MinItemsValidatorFactory.php index 188ae00..d99a8a4 100644 --- a/src/Model/Validator/Factory/Arrays/MinItemsValidatorFactory.php +++ b/src/Model/Validator/Factory/Arrays/MinItemsValidatorFactory.php @@ -21,9 +21,9 @@ protected function getValidator(PropertyInterface $property, mixed $value): Prop { return new PropertyValidator( $property, - "is_array(\$value) && count(\$value) < $value", + "is_array(\$value) && (\$count = count(\$value)) < $value", MinItemsException::class, - [$value], + [$value, '&$count'], ); } } diff --git a/src/Model/Validator/Factory/Object/MaxPropertiesValidatorFactory.php b/src/Model/Validator/Factory/Object/MaxPropertiesValidatorFactory.php index 42576f1..6adfd67 100644 --- a/src/Model/Validator/Factory/Object/MaxPropertiesValidatorFactory.php +++ b/src/Model/Validator/Factory/Object/MaxPropertiesValidatorFactory.php @@ -12,16 +12,6 @@ class MaxPropertiesValidatorFactory extends SimpleBaseValidatorFactory { - private const string COUNT_PROPERTIES = - 'count( - array_unique( - array_merge( - array_keys($this->rawModelDataInput), - array_keys($modelData), - ) - ), - )'; - protected function isValueValid(mixed $value): bool { return is_int($value) && $value >= 0; @@ -31,9 +21,9 @@ protected function getValidator(PropertyInterface $property, mixed $value): Prop { return new PropertyValidator( $property, - sprintf('%s > %d', self::COUNT_PROPERTIES, $value), + sprintf('%s > %d', MinPropertiesValidatorFactory::COUNT_PROPERTIES, $value), MaxPropertiesException::class, - [$value], + [$value, '&$count'], ); } } diff --git a/src/Model/Validator/Factory/Object/MinPropertiesValidatorFactory.php b/src/Model/Validator/Factory/Object/MinPropertiesValidatorFactory.php index e08a15a..9f0fc37 100644 --- a/src/Model/Validator/Factory/Object/MinPropertiesValidatorFactory.php +++ b/src/Model/Validator/Factory/Object/MinPropertiesValidatorFactory.php @@ -5,7 +5,6 @@ namespace PHPModelGenerator\Model\Validator\Factory\Object; use PHPModelGenerator\Exception\Object\MinPropertiesException; -use PHPModelGenerator\Model\Property\Property; use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\Model\Validator\Factory\SimpleBaseValidatorFactory; use PHPModelGenerator\Model\Validator\PropertyValidator; @@ -13,15 +12,15 @@ class MinPropertiesValidatorFactory extends SimpleBaseValidatorFactory { - private const string COUNT_PROPERTIES = - 'count( + public const string COUNT_PROPERTIES = + '($count = count( array_unique( array_merge( array_keys($this->rawModelDataInput), array_keys($modelData), ) ), - )'; + ))'; protected function isValueValid(mixed $value): bool { @@ -34,7 +33,7 @@ protected function getValidator(PropertyInterface $property, mixed $value): Prop $property, sprintf('%s < %d', self::COUNT_PROPERTIES, $value), MinPropertiesException::class, - [$value], + [$value, '&$count'], ); } } diff --git a/src/Model/Validator/Factory/SimplePropertyValidatorFactory.php b/src/Model/Validator/Factory/SimplePropertyValidatorFactory.php index 29a71d7..01a5060 100644 --- a/src/Model/Validator/Factory/SimplePropertyValidatorFactory.php +++ b/src/Model/Validator/Factory/SimplePropertyValidatorFactory.php @@ -39,7 +39,7 @@ protected function hasValidValue(PropertyInterface $property, JsonSchema $proper if (!$this->isValueValid($json[$this->key])) { throw new SchemaException( sprintf( - "Invalid %s %s for property '%s' in file %s", + "Invalid %s (%s) for property '%s' in file %s", $this->key, str_replace("\n", '', var_export($json[$this->key], true)), $property->getName(), diff --git a/src/Model/Validator/PropertyTemplateValidator.php b/src/Model/Validator/PropertyTemplateValidator.php index bb17652..bbec44d 100644 --- a/src/Model/Validator/PropertyTemplateValidator.php +++ b/src/Model/Validator/PropertyTemplateValidator.php @@ -17,8 +17,6 @@ */ class PropertyTemplateValidator extends AbstractPropertyValidator { - /** @var array */ - protected $templateValues; /** @var Schema|null */ protected $scope; @@ -30,12 +28,10 @@ class PropertyTemplateValidator extends AbstractPropertyValidator public function __construct( PropertyInterface $property, protected string $template, - array $templateValues, + protected array $templateValues, string $exceptionClass, array $exceptionParams = [], ) { - $this->templateValues = $templateValues; - parent::__construct($property, $exceptionClass, $exceptionParams); } diff --git a/src/Templates/Validator/ArrayContains.phptpl b/src/Templates/Validator/ArrayContains.phptpl index 633ebbe..d50847f 100644 --- a/src/Templates/Validator/ArrayContains.phptpl +++ b/src/Templates/Validator/ArrayContains.phptpl @@ -1,4 +1,4 @@ -is_array($value) && (function (&$items) { +is_array($value) && (function (&$items){% if countMatches %} use (&$countMatches){% endif %} { if (empty($items)) { return true; } @@ -27,8 +27,12 @@ is_array($value) && (function (&$items) { $this->errorRegistry = $originalErrorRegistry; {% endif %} - // one matched item is enough to pass the contains check - return false; + {% if countMatches %} + $countMatches++; + {% else %} + // one matched item is enough to pass the contains check + return false; + {% endif %} } catch (\Exception $e) { continue; } @@ -38,5 +42,9 @@ is_array($value) && (function (&$items) { $this->errorRegistry = $originalErrorRegistry; {% endif %} - return true; + return {% if countMatches %} + {% if allowNoMatch %}false{% else %}$countMatches === 0{% endif %} + {% else %} + true + {% endif %}; })($value) \ No newline at end of file