-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MLSS-2212 & PREF-309 | Search Criteria Validators #265
Open
arielallon
wants to merge
11
commits into
8.x
Choose a base branch
from
MLSS-2212-search-criteria-validators
base: 8.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
74a785f
MLSS-2212 | Add exception-component to composer dependencies
arielallon a8aa4a3
MLSS-2212 | Add all the new Validator actors under http/fab/Prefab5/S…
arielallon 58cf86d
MLSS-2212 | Add ValidationException handling to HTTP
arielallon 558068a
MLSS-2212 | Add/update templates to call validator from PrimaryActorN…
arielallon f302e9c
MLSS-2212 | Fix typo in filename
arielallon 625e46e
MLSS-2212 | Change relative location of log file
arielallon 25921c9
MLSS-2212 | Make ValidationException play nicer with the middleware
arielallon f94f808
MLSS-2212 | Include messages in ValidationException output
arielallon 3ae3e10
MLSS-2212 | Remove unused catch in HTTP class
arielallon 899de68
MLSS-2212 | Have ValidationException rely on the Mezzio\ProblemDetail…
arielallon 1a7246b
MLSS-2212 | Add docs
arielallon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
BuphaloTemplates/Prefab5/PrimaryActorName/Map/Repository/Validator/Builder.service.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
services: | ||
Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Map\Repository\Validator\BuilderInterface: | ||
class: PREFAB_PLACEHOLDER_VENDOR\PREFAB_PLACEHOLDER_PRODUCT\Prefab5\SearchCriteria\Validator\Builder | ||
public: false | ||
shared: false | ||
calls: | ||
- [setValidatorFactory, ['@PREFAB_PLACEHOLDER_VENDOR\PREFAB_PLACEHOLDER_PRODUCT\Prefab5\SearchCriteria\Validator\FactoryInterface']] |
7 changes: 7 additions & 0 deletions
7
...loTemplates/Prefab5/PrimaryActorName/Map/Repository/Validator/Builder/Factory.service.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
services: | ||
Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Map\Repository\Validator\Builder\FactoryInterface: | ||
class: PREFAB_PLACEHOLDER_VENDOR\PREFAB_PLACEHOLDER_PRODUCT\Prefab5\SearchCriteria\Validator\Builder\Factory | ||
public: false | ||
shared: true | ||
calls: | ||
- [setValidatorBuilder, ['@Neighborhoods\BuphaloTemplateTree\PrimaryActorName\Map\Repository\Validator\BuilderInterface']] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
# Search Criteria Validators | ||
|
||
Search Criteria Validators allow you to validate the *semantics* of the search criteria provided in a request. | ||
|
||
## Adding Search Criteria Validators to your HTTP Handlers | ||
|
||
### Per Application | ||
|
||
1. Upgrade your composer dependency of Prefab to a version that includes Search Criteria Validators. //@todo include minimum version here | ||
2. Ensure that you have resolved any discrepancies in overrides in your project's `/src/` directory | ||
for those called out in the [Modifications in `./Prefab/`](#modifications-in----prefab5--) section below. | ||
|
||
### Per Repository | ||
1. If overridden in `/src/`, update your `Map/Repository.service.yml` and `Map/Reposiory.php` files to include the modifications to the fabbed version of those files. | ||
(See the [Modifications to Actor Templates](#modifications-to-actor-templates) section below. | ||
2. Move the `<PrimaryActorName>/Map/Repository/Validator/Builder.service.yml` from `/fab/` to `/src/`. | ||
As you create Validator Decorators, you will add them to the stack of decorators in this file. | ||
They are called by the runtime from the bottom of the list to the top. | ||
The below example includes the one Validator Decorator created in [Per Decorator](#per-decorator). | ||
|
||
> `PrimaryActorName/Map/Repository/Validator/Builder.service.yml` | ||
>```yml | ||
>services: | ||
> VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\BuilderInterface: | ||
> class: VendorName\ProductName\Prefab5\SearchCriteria\Validator\Builder | ||
> public: false | ||
> shared: false | ||
> calls: | ||
> - [ setValidatorFactory, ['@VendorName\ProductName\Prefab5\SearchCriteria\Validator\FactoryInterface' ] ] | ||
> - [ addFactory, [ '@VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\CustomDecorator\FactoryInterface']] | ||
>``` | ||
|
||
### Per Decorator | ||
These files will all be in the `PrimaryActorName` tree. The examples below are all in `PrimaryActorName/Map/Repository/Validator/`. | ||
|
||
#### Custom Decorator Interface and Service Container | ||
|
||
> `CustomDecoratorInterface.php` | ||
>```php | ||
><?php | ||
>declare(strict_types=1); | ||
> | ||
>namespace VendorName\ProductName\PrimaryActorName\Map\Repository\Validator; | ||
> | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\Validator\DecoratorInterface; | ||
> | ||
>interface CustomDecoratorInterface extends DecoratorInterface | ||
>{ | ||
>} | ||
>``` | ||
> `CustomDecorator.service.yml` | ||
>```yml | ||
>services: | ||
> VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\CustomDecoratorInterface: | ||
> class: VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\CustomDecorator | ||
> public: false | ||
> shared: false | ||
>``` | ||
#### Custom Decorator | ||
|
||
> `CustomDecorator.php` | ||
>```php | ||
><?php | ||
>declare(strict_types=1); | ||
> | ||
>namespace VendorName\ProductName\PrimaryActorName\Map\Repository\Validator; | ||
> | ||
>use VendorName\ProductName\PrimaryActorNameInterface; | ||
>use VendorName\ProductName\Prefab5\HTTP\SearchCriteriaBuilderException; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\ValidationException; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\ValidatorInterface; | ||
>use VendorName\ProductName\Prefab5\SearchCriteriaInterface; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\Validator; | ||
> | ||
>final class CustomDecorator implements CustomDecoratorInterface | ||
>{ | ||
> use Validator\AwareTrait; | ||
> | ||
> private const FIELD_INDEXES = [ | ||
> [ | ||
> PrimaryActorNameInterface::PROP_ID, | ||
> ], | ||
> [ | ||
> PrimaryActorNameInterface::PROP_NAME, | ||
> PrimaryActorNameInterface::PROP_CREATED_AT, | ||
> ], | ||
> ]; | ||
> | ||
> public function validate(SearchCriteriaInterface $searchCriteria): ValidatorInterface | ||
> { | ||
> $filterFields = $this->extractFiltersFieldListFromSearchCriteria($searchCriteria); | ||
> $sortOrderFields = $this->extractSortOrdersFieldListFromSearchCriteria($searchCriteria); | ||
> $this->validateAtLeastOneIndexedFieldPresent($filterFields); | ||
> if (!empty($sortOrderFields)) { | ||
> $this->validateAtLeastOneIndexedFieldPresent($sortOrderFields); | ||
> } | ||
> | ||
> if ($this->hasValidator()) { | ||
> $this->getValidator()->validate($searchCriteria); | ||
> } | ||
> | ||
> return $this; | ||
> } | ||
> | ||
> private function validateAtLeastOneIndexedFieldPresent(array $searchCriteriaFields): void | ||
> { | ||
> foreach (self::FIELD_INDEXES as $indexFields) { | ||
> if (count(array_diff($indexFields, $searchCriteriaFields)) === 0) { | ||
> return; | ||
> } | ||
> } | ||
> $errorMessage = sprintf('Invalid Search Criteria. No indexed fields present.'); | ||
> $previousException = new SearchCriteriaBuilderException($errorMessage); | ||
> throw (new ValidationException())->setCode('422')->setPrevious($previousException); | ||
> } | ||
> | ||
> private function extractFiltersFieldListFromSearchCriteria(SearchCriteriaInterface $searchCriteria) : array | ||
> { | ||
> $fieldList = []; | ||
> | ||
> foreach ($searchCriteria->getFilters() as $filter) { | ||
> $fieldList[] = $filter->getField(); | ||
> } | ||
> | ||
> return $fieldList; | ||
> } | ||
> | ||
> private function extractSortOrdersFieldListFromSearchCriteria(SearchCriteriaInterface $searchCriteria) : array | ||
> { | ||
> $fieldList = []; | ||
> | ||
> foreach ($searchCriteria->getSortOrders() as $sortOrder) { | ||
> $fieldList[] = $sortOrder->getField(); | ||
> } | ||
> | ||
> return $fieldList; | ||
> } | ||
>} | ||
>``` | ||
#### Supporting Actors | ||
|
||
> `CustomDecorator.buphalo.v1.fabrication.yml` | ||
> ```yml | ||
> actors: | ||
> <PrimaryActorName>.service.yml: | ||
> template: PrimaryActorName.service.yml | ||
> <PrimaryActorName>/Factory.service.yml: | ||
> template: PrimaryActorName/Factory.service.yml | ||
> ``` | ||
> | ||
> `CustomDecorator/FactoryInterface.php` | ||
>```php | ||
><?php | ||
>declare(strict_types=1); | ||
> | ||
>namespace VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\CustomDecorator; | ||
> | ||
>use VendorName\ProductName\HTTP1\PropertyIdentity\Map\Repository\Validator\CustomDecoratorInterface; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\Validator\DecoratorInterface; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\Validator\Decorator\FactoryInterface as PrefabValidatorDecoratorFactoryInterface; | ||
> | ||
>interface FactoryInterface extends PrefabValidatorDecoratorFactoryInterface | ||
>{ | ||
> public function create(): DecoratorInterface; | ||
>} | ||
>``` | ||
> | ||
> `CustomDecorator/Factory.php` | ||
>```php | ||
><?php | ||
>declare(strict_types=1); | ||
> | ||
>namespace VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\CustomDecorator; | ||
> | ||
>use VendorName\ProductName\PrimaryActorName\Map\Repository\Validator\CustomDecoratorInterface; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\Validator\DecoratorInterface; | ||
> | ||
>class Factory implements FactoryInterface | ||
>{ | ||
> use AwareTrait; | ||
> | ||
> public function create(): DecoratorInterface | ||
> { | ||
> return clone $this->getPrimaryActorNameMapRepositoryValidatorCustomDecorator(); | ||
> } | ||
>} | ||
>``` | ||
> `CustomDecorator/AwareTrait.php` | ||
>```php | ||
><?php | ||
>declare(strict_types=1); | ||
> | ||
>namespace VendorName\ProductName\HTTP1\PropertyIdentity\Map\Repository\Validator\CustomDecorator; | ||
> | ||
>use VendorName\ProductName\HTTP1\PropertyIdentity\Map\Repository\Validator\CustomDecoratorInterface; | ||
>use VendorName\ProductName\Prefab5\SearchCriteria\Validator\DecoratorInterface; | ||
> | ||
>trait AwareTrait | ||
>{ | ||
> protected $PrimaryActorNameMapRepositoryValidatorCustomDecorator; | ||
> | ||
> public function setPrimaryActorNameMapRepositoryValidatorCustomDecorator(DecoratorInterface $CustomDecorator): self | ||
> { | ||
> if ($this->hasPrimaryActorNameMapRepositoryValidatorCustomDecorator()) { | ||
> throw new \LogicException('PrimaryActorNameMapRepositoryValidatorCustomDecorator is already set.'); | ||
> } | ||
> | ||
> $this->PrimaryActorNameMapRepositoryValidatorCustomDecorator = $CustomDecorator; | ||
> | ||
> return $this; | ||
> } | ||
> | ||
> protected function getPrimaryActorNameMapRepositoryValidatorCustomDecorator(): DecoratorInterface | ||
> { | ||
> if (!$this->hasPrimaryActorNameMapRepositoryValidatorCustomDecorator()) { | ||
> throw new \LogicException('PrimaryActorNameMapRepositoryValidatorCustomDecorator is not set.'); | ||
> } | ||
> | ||
> return $this->PrimaryActorNameMapRepositoryValidatorCustomDecorator; | ||
> } | ||
> | ||
> protected function hasPrimaryActorNameMapRepositoryValidatorCustomDecorator(): bool | ||
> { | ||
> return isset($this->PrimaryActorNameMapRepositoryValidatorCustomDecorator); | ||
> } | ||
> | ||
> protected function unsetPrimaryActorNameMapRepositoryValidatorCustomDecorator(): self | ||
> { | ||
> if (!$this->hasPrimaryActorNameMapRepositoryValidatorCustomDecorator()) { | ||
> throw new \LogicException('PrimaryActorNameMapRepositoryValidatorCustomDecorator is not set.'); | ||
> } | ||
> unset($this->PrimaryActorNameMapRepositoryValidatorCustomDecorator); | ||
> | ||
> return $this; | ||
> } | ||
>} | ||
>``` | ||
|
||
|
||
## Recommendations | ||
|
||
- Create a dedicated Validator Decorator for a single logical/semantic validation need. | ||
You can and should stack multiple Validator Decorators with distinct logical/semantic validations purposes on a given Repository. | ||
- Be sure to carefully consider the behavior of each decorator as it relates to the procession down the stack of decorators. | ||
A runtime analysis diagram and meeting is highly recommended. | ||
Generally, if a decorator is _certain_ that the request is valid without needing further validation down the stack, it may return immediately. | ||
Similarly, if a decorator is _certain_ that the request is not valid, it may throw an exception immediately. | ||
If the decorator is _not certain_ that other decorators down the stack may have more insight, it should call the next decorator in the stack. | ||
- The last custom decorator in the stack (the first one in the list of `addDecorator` calls in the `Builder.service.yml`) should _not_ call `$this->getValidator()->validate()`. | ||
The default Validator is designed to act as a failsafe to indicate that no custom decorators were implemented for a handler. | ||
|
||
|
||
## Modified Classes | ||
|
||
The following classes have been modified in Prefab as part of the implementation of Search Criteria Validators. | ||
If you have overrides of these in your project's `/src/` directory, you will need to resolve any discrepancies. | ||
|
||
Note that in addition to those detailed below, other files were added, | ||
but should not need reconciliation with existing files as they are net-new. | ||
|
||
### Modifications in `./Prefab5/` | ||
|
||
- `Prefab5\HTTP` has been refactored a little, though there are no changes to its API. | ||
- `Prefab5\HTTPSearchCriteriaBuilderException` has updated the vendor of a `use` statement to reflect where the package migrated to. | ||
|
||
### Modifications to Actor Templates | ||
|
||
- `PrimaryActorName\Map\Repository` and associated `Repository.service.yml` have been updated to set and use the `Validator\Builder\Factory`. | ||
|
||
## Breaking Changes | ||
|
||
In version 8.x, implementing Search Criteria Validators is optional and isn't enforced by Prefab. | ||
In a future major version update of Prefab, they will be required. | ||
Specifically, in a future major version update for Prefab, | ||
1. the `try`/`catch` will be removed from `Prefab5\SearchCriteria\Validator::validate()`, requiring that at least one custom Validator Decorator be present per Map/Repository, and | ||
2. the `if ($this->hasValidatorBuilderFactory())` check will be removed from `PrimaryActorName\Map\Repository`. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
potential blocker: This looks like every repo Validator Builder Factory will have their own service but use the same class. Is that intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from synchronous review:
pro:
having prefab build per primary actors versions of this could avoid the covariance/contravariance problem and might be more in line with how prefab operates otherwise.
cons:
might make it harder to have shared validators?