From fc220ec5b5a9b33865f4cc40492781bba3f103d4 Mon Sep 17 00:00:00 2001 From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com> Date: Tue, 6 Feb 2024 21:49:09 +0100 Subject: [PATCH] chore: document validators, fix caching issue --- .../features/validation/constraints/index.md | 281 ++++++++++++++++++ .../features/validation/form-models/index.md | 121 ++++++++ .../http-controller-parameters/index.md | 120 ++++++++ docs/pages/docs/features/validation/index.md | 212 +------------ src/Command/StaticPagesBuild.php | 3 + src/Command/StaticPagesDumpContent.php | 3 + src/Command/StaticPagesMakeEmbeddings.php | 3 + src/Feature.php | 1 + src/InputValidator/FrontMatterValidator.php | 3 + src/InputValidatorController.php | 2 - .../InputValidatorCollectionProvider.php | 10 - .../InputValidatorControllerProvider.php | 38 +++ src/StaticPageChunkIterator.php | 2 + src/StaticPageContentRenderer.php | 2 + src/StaticPageMarkdownParser.php | 2 + src/StaticPageProcessor.php | 2 + src/StaticPageSitemapGenerator.php | 2 + 17 files changed, 584 insertions(+), 223 deletions(-) create mode 100644 docs/pages/docs/features/validation/constraints/index.md create mode 100644 docs/pages/docs/features/validation/form-models/index.md create mode 100644 docs/pages/docs/features/validation/http-controller-parameters/index.md create mode 100644 src/SingletonProvider/InputValidatorControllerProvider.php diff --git a/docs/pages/docs/features/validation/constraints/index.md b/docs/pages/docs/features/validation/constraints/index.md new file mode 100644 index 00000000..e35782de --- /dev/null +++ b/docs/pages/docs/features/validation/constraints/index.md @@ -0,0 +1,281 @@ +--- +collections: + - name: documents + next: docs/features/validation/form-models/index +layout: dm:document +next: docs/features/validation/form-models/index +parent: docs/features/validation/index +title: Constraints Schema +description: > + Learn how to build validation schemas to check your incoming data. +--- + +# Constraints Schema + +Resonance provides validation constraints that are roughly equivalent to what +[JSON Schema](https://json-schema.org/) offers. + +They are written in PHP, but they are also convertible into JSON Schema +(they feature is also used internally by Resonance's +{{docs/features/openapi/index}} schema generator). If you need to, you can +export your schemas to JSON. + +# Usage + +All constraints are in `Distantmagic\Resonance\Constraint` namespace. + +## Schema + +### Any + +```php +new AnyConstraint(); +``` +```json +{} +``` + +Accepts any value. + +### Any of + +```php +/** + * @var array<Constraint> $anyOf + */ +new AnyOfConstraint(anyOf: $anyOf); +``` +```json +{ + "anyOf": [...] +} +``` + +Acceepts a value if it passess any of the listed constraints. + +### Boolean + +```php +new BooleanConstraint(); +``` +```json +{ + "type": "boolean" +} +``` + +### Const + +```php +/** + * @var int|float|string $constValue + */ +new ConstConstraint(constValue: $constValue); +``` +```json +{ + "const": ... +} +``` + +Accepts exactly the provided value + +### Enum + +```php +/** + * @var array<string>|list<string> $values + */ +new EnumConstraint(values: $values); +``` +```json +{ + "type": "string", + "enum": [...] +} +``` + +### Integer + +```php +new IntegerConstraint(); +``` +```json +{ + "type": "integer" +} +``` + +### List + +```php +/** + * @var Constraint $constraint + */ +new ListConstraint(valueConstraint: $constraint); +``` +```json +{ + "type": "array", + "items": ... +} +``` + +Accepts an array only if each array's item validates agains the constraint. + +### Map + +```php +/** + * @var Constraint $constraint + */ +new MapConstraint(valueConstraint: $constraint); +``` +```json +{ + "type": "object", + "additionalProperties": ... +} +``` + +Accepts an object if each property validates agains the constraint. + +### Number + +```php +new NumberConstraint(); +``` +```json +{ + "type": "number" +} +``` + +Accepts both integer and float values. + +### Object + +```php +/** + * @var array<non-empty-string,Constraint> $properties + */ +new ObjectConstraint(properties: $properties); +``` +```json +{ + "type": "object", + "properties": ... +} +``` + +Expects an object with exactly the listed properties. + +### String + +```php +new StringConstraint(); +``` +```json +{ + "type": "string", + "minLength": 1 +} +``` + +### Tuple + +```php +/** + * @var list<Constraint> $items + */ +new TupleConstraint(items: $items); +``` +```json +{ + "type": "array", + "items": false, + "prefixItems": ... +} +``` + +Expects an array of exactly the provided shape and length. + +## Exporting to JSON Schema + +You can use `toJsonSchema()` method. Every constraint has it: + +```php +$constraint = new TupleConstraint([ + new StringConstraint(), + new NumberConstraint(), +]); + +$constraint->toJsonSchema(); +``` + +Produces: + +```php +[ + 'type' => 'array', + 'items' => false, + 'prefixItems' => [ + [ + 'type' => 'string', + 'minLength' => 1, + ], + [ + 'type' => 'number', + ], + ], +] +``` + +## Validation Errors + +After validation you can inspect the returned `ConstraintResult` object to +check for error messages and the mapped data. Validators always cast data +to associative arrays. + +```php +$constraint = new ListConstraint( + valueConstraint: new StringConstraint() +); + +$validatedResult = $constraint->validate(['hi', 5]); + +if (!$validatedResult->status->isValid()) { + /** + * Errors are indexed by the field name, value is the error code. + * + * @var Map<string,non-empty-string> $errors + */ + $errors = $validatedResult->getErrors(); +} +``` + +Possible error codes are: + +- `invalid_data_type` +- `invalid_enum_value` +- `invalid_format` +- `invalid_nested_constraint` +- `missing_property` +- `ok` +- `unexpected_property` + +## Examples + +You can compose constraints together: + +```php +$constraint = new ObjectConstraint([ + 'host' => new StringConstraint(), + 'port' => new IntegerConstraint(), +]); + +$constraint->validate([ + 'host' => 'http://example.com', + 'port' => 3306, +]); +``` diff --git a/docs/pages/docs/features/validation/form-models/index.md b/docs/pages/docs/features/validation/form-models/index.md new file mode 100644 index 00000000..110e775a --- /dev/null +++ b/docs/pages/docs/features/validation/form-models/index.md @@ -0,0 +1,121 @@ +--- +collections: + - name: documents + next: docs/features/validation/http-controller-parameters/index +layout: dm:document +next: docs/features/validation/http-controller-parameters/index +parent: docs/features/validation/index +title: Form Models +description: > + Validate HTML forms, incoming WebSocket messages and more. +--- + +# Form Models + +Resonance can map incoming data into models representing a validated data. + +It is a useful abstraction that allows you to make sure you are using validated +data in your application. + +# Usage + +We will use `BlogPostCreateForm` as an example. It represents a request to +create a blog post: + +```php +<?php + +namespace App\InputValidatedData; + +use Distantmagic\Resonance\InputValidatedData; + +readonly class BlogPostCreateForm extends InputValidatedData +{ + public function __construct( + public string $content, + public string $title, + ) {} +} + +``` + +## Validators + +Validators take in any data and check if it adheres to the configuration +schema. The `getConstraint()` method must return a +{{docs/features/validation/constraints/index}} object. + +```php +<?php + +namespace App\InputValidator; + +use App\InputValidatedData\BlogPostCreateForm; +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Constraint; +use Distantmagic\Resonance\Constraint\ObjectConstraint; +use Distantmagic\Resonance\Constraint\StringConstraint; +use Distantmagic\Resonance\InputValidator; +use Distantmagic\Resonance\SingletonCollection; + +/** + * @extends InputValidator<BlogPostCreateForm, array{ + * content: string, + * title: string, + * }> + */ +#[Singleton(collection: SingletonCollection::InputValidator)] +readonly class BlogPostFormValidator extends InputValidator +{ + public function castValidatedData(mixed $data): BlogPostCreateForm + { + return new BlogPostCreateForm( + $data['content'], + $data['title'], + ); + } + + public function getConstraint(): Constraint + { + return new ObjectConstraint([ + 'content' => new StringConstraint(), + 'title' => new StringConstraint() + ]); + } +} +``` + +Preferably validators should be injected somewhere by the +{{docs/features/dependency-injection/index}}, so you don't have to set up their +parameters manually. Then you can call their `validateData()` method. + +```php +<?php + +use Distantmagic\Resonance\InputValidatorController; +use Distantmagic\Resonance\InputValidationResult; + +$inputValidatorController = new InputValidatorController(); + +/** + * @var InputValidationResult $validationResult + */ +$validationResult = $inputValidatorController->validateData($blogPostFormValidator, [ + 'content' => 'test', + 'title' => 'test', +]); + +// If validation is successful, the errors list is empty and validation data +// is set. +assert($validationResult->constraintResult->getErrors()->isEmpty()); + +/** + * It's null if validation failed. + * + * @var ?BlogPostCreateForm $validationResult->inputValidatedData + */ +assert($validationResult->inputValidatedData); +``` + +Validators do not throw an exception since invalid data is not a failure in the +intended application flow. Instead, it's just a normal situation to handle. diff --git a/docs/pages/docs/features/validation/http-controller-parameters/index.md b/docs/pages/docs/features/validation/http-controller-parameters/index.md new file mode 100644 index 00000000..db880938 --- /dev/null +++ b/docs/pages/docs/features/validation/http-controller-parameters/index.md @@ -0,0 +1,120 @@ +--- +collections: + - documents +layout: dm:document +parent: docs/features/validation/index +title: Http Controller Parameters +description: > + Validate and map any incoming data into reusable objects. +--- + +# HTTP Controller Paramateres + +:::note +This feature only works in HTTP {{docs/features/http/controllers}}. It doesn't +work with pure {{docs/features/http/responders}}. +::: + +Controllers allow to validate and map incoming HTTP data into reusable objects. + +# Usage + +## Attributes + +You don't have to call the validator manually if you use +{{docs/features/http/controllers}}. You can use parameter attributes instead. + +For example, if a `MyValidator` exists that returns `MyValidatedData` model, +you can add `#[ValidatedRequest(MyValidator::class)]` annotation to the +parameter of the `MyValidatedData`. A controller is going to validate the +incoming `POST` data using the `MyValidator`, and in case of success, it's +going to inject the data model into the parameter: + +```php +// ... + +public function handle( + #[ValidatedRequest(MyValidator::class)] + MyValidatedData $data, +) { + // ... +} + +// ... +``` + +## Error Handling + +To handle errors, you can create an optional method marked with the +`#[ValidationErrorsHandler]` attribute. If such a method exists, then will be +be called in case of validation failure. + +If no such method exists, the controller is going to return a generic +`400 Bad Request` response. + +:::caution +`handle` method's arguments are forwarded into the error validation method. + +It can only use the parameters that are already resolved in the `handle` method +plus an extra argument with validation errors (marked by the +`#[ValidationErrors]`) and request/response pair. + +Adding new arguments to the error handler besides those is going to cause an +error. +::: + +```php +<?php + +namespace App\HttpResponder; + +use App\HttpRouteSymbol; +use App\InputValidatedData\BlogPostForm; +use App\InputValidator\BlogPostFormValidator; +use Distantmagic\Resonance\Attribute\RespondsToHttp; +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\Attribute\ValidatedRequest; +use Distantmagic\Resonance\Attribute\ValidationErrors; +use Distantmagic\Resonance\Attribute\ValidationErrorsHandler; +use Distantmagic\Resonance\HttpResponder\HttpController; +use Distantmagic\Resonance\HttpResponderInterface; +use Distantmagic\Resonance\RequestMethod; +use Distantmagic\Resonance\SingletonCollection; +use Ds\Map; +use Ds\Set; +use Swoole\Http\Request; +use Swoole\Http\Response; + +#[RespondsToHttp( + method: RequestMethod::POST, + pattern: '/blog_post/create', + routeSymbol: HttpRouteSymbol::BlogPostCreate, +)] +#[Singleton(collection: SingletonCollection::HttpResponder)] +final readonly class BlogPostStore extends HttpController +{ + public function handle( + #[ValidatedRequest(BlogPostFormValidator::class)] + BlogPostForm $blogPostForm, + ): HttpResponderInterface { + /* inser blog post, redirect, etc */ + /* ... */ + } + + /** + * @param Map<string,Set<string>> $errors + */ + #[ValidationErrorsHandler] + public function handleValidationErrors( + Request $request, + Response $response, + #[ValidationErrors] + Map $errors, + ): HttpResponderInterface { + $response->status(400); + + /* render form with errors */ + /* ... */ + } +} +``` diff --git a/docs/pages/docs/features/validation/index.md b/docs/pages/docs/features/validation/index.md index f8b57462..9e32aec5 100644 --- a/docs/pages/docs/features/validation/index.md +++ b/docs/pages/docs/features/validation/index.md @@ -15,214 +15,4 @@ You can use validators to check if any incoming data is correct. For example: form data filled by the user, incoming {{docs/features/websockets/index}} message, and others. -# Usage - -## Validated Data Models (Form Models) - -You should always map the validated data into the validated data models (also -known as Form Models in other frameworks). - -They don't need any attributes or any configuration. Validated data is going to -be mapped onto those models later. - -An example blog post form model may look like this: - -```php -<?php - -namespace App\InputValidatedData; - -use Distantmagic\Resonance\InputValidatedData; - -readonly class BlogPostForm extends InputValidatedData -{ - public function __construct( - public string $content, - public string $title, - ) {} -} - -``` - -## Validators - -Validators take in any data and check if it adheres to the configuration -schema. The `getConstraint()` method must return a constraints object. - -```php -<?php - -namespace App\InputValidator; - -use App\InputValidatedData\BlogPostForm; -use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\Constraint; -use Distantmagic\Resonance\Constraint\ObjectConstraint; -use Distantmagic\Resonance\Constraint\StringConstraint; -use Distantmagic\Resonance\InputValidator; -use Distantmagic\Resonance\SingletonCollection; - -/** - * @extends InputValidator<BlogPostForm, array{ - * content: string, - * title: string, - * }> - */ -#[Singleton(collection: SingletonCollection::InputValidator)] -readonly class BlogPostFormValidator extends InputValidator -{ - public function castValidatedData(mixed $data): BlogPostForm - { - return new BlogPostForm( - $data['content'], - $data['title'], - ); - } - - public function getConstraint(): Constraint - { - return new ObjectConstraint([ - 'content' => new StringConstraint(), - 'title' => new StringConstraint() - ]); - } -} -``` - -Preferably validators should be injected somewhere by the -{{docs/features/dependency-injection/index}}, so you don't have to set up their -parameters manually. Then you can call their `validateData()` method. - -```php -<?php - -use Distantmagic\Resonance\InputValidatorController; -use Distantmagic\Resonance\InputValidationResult; - -$inputValidatorController = new InputValidatorController(); - -/** - * @var InputValidationResult $validationResult - */ -$validationResult = $inputValidatorController->validateData($blogPostFormValidator, [ - 'content' => 'test', - 'title' => 'test', -]); - -// If validation is successful, the errors list is empty and validation data -// is set. -assert($validationResult->constraintResult->getErrors()->isEmpty()); - -/** - * It's null if validation failed. - * - * @var ?BlogPostForm $validationResult->inputValidatedData - */ -assert($validationResult->inputValidatedData); -``` - -Validators do not throw an exception since invalid data is not a failure in the -intended application flow. Instead, it's just a normal situation to handle. - -## Controller Attributes - -:::note -This feature only works in HTTP {{docs/features/http/controllers}}. It doesn't -work with pure {{docs/features/http/responders}}. -::: - -You don't have to call the validator manually if you use -{{docs/features/http/controllers}}. You can use controller parameters instead. - -For example, if a `MyValidator` exists that returns `MyValidatedData` model, -you can add `#[ValidatedRequest(MyValidator::class)]` annotation to the -parameter of the `MyValidatedData`. A controller is going to validate the -incoming `POST` data using the `MyValidator`, and in case of success, it's -going to inject the data model into the parameter: - -```php -// ... - -public function handle( - #[ValidatedRequest(MyValidator::class)] - MyValidatedData $data, -) { - // ... -} - -// ... -``` - -To handle errors, you can create an optional method marked with the -`#[ValidationErrorsHandler]` attribute. If such a method exists, then will be -be called in case of validation failure. - -If no such method exists, the controller is going to return a generic -`400 Bad Request` response. - -:::caution -`handle` method's arguments are forwarded into the error validation method. - -It can only use the parameters that are already resolved in the `handle` method -plus an extra argument with validation errors (marked by the -`#[ValidationErrors]`) and request/response pair. - -Adding new arguments to the error handler besides those is going to cause an -error. -::: - -```php -<?php - -namespace App\HttpResponder; - -use App\HttpRouteSymbol; -use App\InputValidatedData\BlogPostForm; -use App\InputValidator\BlogPostFormValidator; -use Distantmagic\Resonance\Attribute\RespondsToHttp; -use Distantmagic\Resonance\Attribute\Singleton; -use Distantmagic\Resonance\Attribute\ValidatedRequest; -use Distantmagic\Resonance\Attribute\ValidationErrors; -use Distantmagic\Resonance\Attribute\ValidationErrorsHandler; -use Distantmagic\Resonance\HttpResponder\HttpController; -use Distantmagic\Resonance\HttpResponderInterface; -use Distantmagic\Resonance\RequestMethod; -use Distantmagic\Resonance\SingletonCollection; -use Ds\Map; -use Ds\Set; -use Swoole\Http\Request; -use Swoole\Http\Response; - -#[RespondsToHttp( - method: RequestMethod::POST, - pattern: '/blog_post/create', - routeSymbol: HttpRouteSymbol::BlogPostCreate, -)] -#[Singleton(collection: SingletonCollection::HttpResponder)] -final readonly class BlogPostStore extends HttpController -{ - public function handle( - #[ValidatedRequest(BlogPostFormValidator::class)] - BlogPostForm $blogPostForm, - ): HttpResponderInterface { - /* inser blog post, redirect, etc */ - /* ... */ - } - - /** - * @param Map<string,Set<string>> $errors - */ - #[ValidationErrorsHandler] - public function handleValidationErrors( - Request $request, - Response $response, - #[ValidationErrors] - Map $errors, - ): HttpResponderInterface { - $response->status(400); - - /* render form with errors */ - /* ... */ - } -} -``` +{{docs/features/validation/*/index}} diff --git a/src/Command/StaticPagesBuild.php b/src/Command/StaticPagesBuild.php index 2a0eb632..0597f3e6 100644 --- a/src/Command/StaticPagesBuild.php +++ b/src/Command/StaticPagesBuild.php @@ -5,8 +5,10 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\Attribute\ConsoleCommand; +use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; use Distantmagic\Resonance\CoroutineCommand; +use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\StaticPageProcessor; use Distantmagic\Resonance\SwooleConfiguration; use Symfony\Component\Console\Input\InputInterface; @@ -16,6 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface; name: 'static-pages:build', description: 'Generate static pages' )] +#[WantsFeature(Feature::StaticPages)] final class StaticPagesBuild extends CoroutineCommand { public function __construct( diff --git a/src/Command/StaticPagesDumpContent.php b/src/Command/StaticPagesDumpContent.php index 8d608d28..4b88730c 100644 --- a/src/Command/StaticPagesDumpContent.php +++ b/src/Command/StaticPagesDumpContent.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\Attribute\ConsoleCommand; +use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; +use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\StaticPageAggregate; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -14,6 +16,7 @@ use Symfony\Component\Console\Output\OutputInterface; name: 'static-pages:dump-content', description: 'Dumps static pages content into JSONL' )] +#[WantsFeature(Feature::StaticPages)] final class StaticPagesDumpContent extends Command { public function __construct( diff --git a/src/Command/StaticPagesMakeEmbeddings.php b/src/Command/StaticPagesMakeEmbeddings.php index 8953851b..b948cfc6 100644 --- a/src/Command/StaticPagesMakeEmbeddings.php +++ b/src/Command/StaticPagesMakeEmbeddings.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Distantmagic\Resonance\Command; use Distantmagic\Resonance\Attribute\ConsoleCommand; +use Distantmagic\Resonance\Attribute\WantsFeature; use Distantmagic\Resonance\Command; +use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\JsonSerializer; use Distantmagic\Resonance\LlamaCppClient; use Distantmagic\Resonance\LlamaCppEmbeddingRequest; @@ -20,6 +22,7 @@ use Symfony\Component\Console\Output\OutputInterface; name: 'static-pages:make-embeddings', description: 'Create embeddings from static pages contents (requires llama.cpp)' )] +#[WantsFeature(Feature::StaticPages)] final class StaticPagesMakeEmbeddings extends Command { private readonly SQLite3 $embeddingsDatabase; diff --git a/src/Feature.php b/src/Feature.php index 06dc3eca..e1945149 100644 --- a/src/Feature.php +++ b/src/Feature.php @@ -8,6 +8,7 @@ enum Feature implements FeatureInterface { case OAuth2; case Postfix; + case StaticPages; case SwooleTaskServer; case WebSocket; diff --git a/src/InputValidator/FrontMatterValidator.php b/src/InputValidator/FrontMatterValidator.php index 0b1a8bc0..0c0507cc 100644 --- a/src/InputValidator/FrontMatterValidator.php +++ b/src/InputValidator/FrontMatterValidator.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance\InputValidator; +use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\Constraint; use Distantmagic\Resonance\Constraint\AnyOfConstraint; @@ -12,6 +13,7 @@ use Distantmagic\Resonance\Constraint\EnumConstraint; use Distantmagic\Resonance\Constraint\ListConstraint; use Distantmagic\Resonance\Constraint\ObjectConstraint; use Distantmagic\Resonance\Constraint\StringConstraint; +use Distantmagic\Resonance\Feature; use Distantmagic\Resonance\FrontMatterCollectionReference; use Distantmagic\Resonance\InputValidatedData\FrontMatter; use Distantmagic\Resonance\InputValidator; @@ -33,6 +35,7 @@ use RuntimeException; * title: non-empty-string, * }> */ +#[GrantsFeature(Feature::StaticPages)] #[Singleton(collection: SingletonCollection::InputValidator)] readonly class FrontMatterValidator extends InputValidator { diff --git a/src/InputValidatorController.php b/src/InputValidatorController.php index a8e33f33..c41ab01a 100644 --- a/src/InputValidatorController.php +++ b/src/InputValidatorController.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace Distantmagic\Resonance; -use Distantmagic\Resonance\Attribute\Singleton; use Ds\Map; -#[Singleton] readonly class InputValidatorController { /** diff --git a/src/SingletonProvider/InputValidatorCollectionProvider.php b/src/SingletonProvider/InputValidatorCollectionProvider.php index 7cb802cf..9b2e34d1 100644 --- a/src/SingletonProvider/InputValidatorCollectionProvider.php +++ b/src/SingletonProvider/InputValidatorCollectionProvider.php @@ -8,7 +8,6 @@ use Distantmagic\Resonance\Attribute\RequiresSingletonCollection; use Distantmagic\Resonance\Attribute\Singleton; use Distantmagic\Resonance\InputValidator; use Distantmagic\Resonance\InputValidatorCollection; -use Distantmagic\Resonance\InputValidatorController; use Distantmagic\Resonance\PHPProjectFiles; use Distantmagic\Resonance\SingletonCollection; use Distantmagic\Resonance\SingletonContainer; @@ -21,10 +20,6 @@ use Distantmagic\Resonance\SingletonProvider; #[Singleton(provides: InputValidatorCollection::class)] final readonly class InputValidatorCollectionProvider extends SingletonProvider { - public function __construct( - private InputValidatorController $inputValidatorController, - ) {} - public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): InputValidatorCollection { $inputValidatorCollection = new InputValidatorCollection(); @@ -35,11 +30,6 @@ final readonly class InputValidatorCollectionProvider extends SingletonProvider $singleton::class, $singleton, ); - $this - ->inputValidatorController - ->cachedConstraints - ->put($singleton, $singleton->getConstraint()) - ; } } diff --git a/src/SingletonProvider/InputValidatorControllerProvider.php b/src/SingletonProvider/InputValidatorControllerProvider.php new file mode 100644 index 00000000..087f2ccc --- /dev/null +++ b/src/SingletonProvider/InputValidatorControllerProvider.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +namespace Distantmagic\Resonance\SingletonProvider; + +use Distantmagic\Resonance\Attribute\Singleton; +use Distantmagic\Resonance\InputValidatorCollection; +use Distantmagic\Resonance\InputValidatorController; +use Distantmagic\Resonance\PHPProjectFiles; +use Distantmagic\Resonance\SingletonContainer; +use Distantmagic\Resonance\SingletonProvider; +use Nette\PhpGenerator\Printer; + +/** + * @template-extends SingletonProvider<Printer> + */ +#[Singleton(provides: InputValidatorController::class)] +final readonly class InputValidatorControllerProvider extends SingletonProvider +{ + public function __construct( + private InputValidatorCollection $inputValidatorCollection, + ) {} + + public function provide(SingletonContainer $singletons, PHPProjectFiles $phpProjectFiles): InputValidatorController + { + $inputValidatorController = new InputValidatorController(); + + foreach ($this->inputValidatorCollection->inputValidators as $inputValidator) { + $inputValidatorController + ->cachedConstraints + ->put($inputValidator, $inputValidator->getConstraint()) + ; + } + + return $inputValidatorController; + } +} diff --git a/src/StaticPageChunkIterator.php b/src/StaticPageChunkIterator.php index f196621d..d3983d95 100644 --- a/src/StaticPageChunkIterator.php +++ b/src/StaticPageChunkIterator.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; use Generator; use IteratorAggregate; @@ -16,6 +17,7 @@ use League\CommonMark\Node\StringContainerHelper; /** * @template-implements IteratorAggregate<non-empty-string> */ +#[GrantsFeature(Feature::StaticPages)] #[Singleton] readonly class StaticPageChunkIterator implements IteratorAggregate { diff --git a/src/StaticPageContentRenderer.php b/src/StaticPageContentRenderer.php index 6d629b23..66b67a99 100644 --- a/src/StaticPageContentRenderer.php +++ b/src/StaticPageContentRenderer.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; +#[GrantsFeature(Feature::StaticPages)] #[Singleton] readonly class StaticPageContentRenderer { diff --git a/src/StaticPageMarkdownParser.php b/src/StaticPageMarkdownParser.php index 224cd062..fa62488e 100644 --- a/src/StaticPageMarkdownParser.php +++ b/src/StaticPageMarkdownParser.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; @@ -15,6 +16,7 @@ use League\CommonMark\Extension\GithubFlavoredMarkdownExtension; use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension; use League\CommonMark\MarkdownConverter; +#[GrantsFeature(Feature::StaticPages)] #[Singleton] readonly class StaticPageMarkdownParser { diff --git a/src/StaticPageProcessor.php b/src/StaticPageProcessor.php index ccaee1eb..6aa4f782 100644 --- a/src/StaticPageProcessor.php +++ b/src/StaticPageProcessor.php @@ -4,10 +4,12 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +#[GrantsFeature(Feature::StaticPages)] #[Singleton] readonly class StaticPageProcessor { diff --git a/src/StaticPageSitemapGenerator.php b/src/StaticPageSitemapGenerator.php index 1dbd0f39..97fedfa9 100644 --- a/src/StaticPageSitemapGenerator.php +++ b/src/StaticPageSitemapGenerator.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Distantmagic\Resonance; +use Distantmagic\Resonance\Attribute\GrantsFeature; use Distantmagic\Resonance\Attribute\Singleton; +#[GrantsFeature(Feature::StaticPages)] #[Singleton] readonly class StaticPageSitemapGenerator { -- GitLab