-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New PSR for standardizing Validator (ValidatorInterface) #1337
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
3bfe67d
6284964
b3d9565
9f1ad37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,85 @@ | ||||||
| Validator Meta Document | ||||||
| ======================= | ||||||
|
|
||||||
| ## 1. Summary | ||||||
|
|
||||||
| The SimpleValidatorInterface and ExtendedValidatorInterface define universal, minimalistic contracts for value validation in PHP. They provide a unified way to perform type-agnostic validations with either a simple pass/fail response (suitable for most form or field inputs) or a detailed, structured validation report, supporting error codes for i18n and advanced cases. Inspired by Symfony's Validator and `Respect\Validation`, these interfaces allow maximum interoperability and rapid provider/implementation swap, while enabling framework- and library-level integration without tight coupling. | ||||||
|
|
||||||
| Applications MAY depend only on these interfaces and swap implementations (or chains/compositions of validators) via DI, reducing coupling and simplifying maintenance, testing, and future migrations. | ||||||
|
|
||||||
| ## 2. Why Bother? | ||||||
|
|
||||||
| Form and value validation is a ubiquitous problem across all PHP applications. Today, each framework (Symfony, Laravel, Yii, etc.) exposes its own interfaces, making it difficult to share validators, migrate between frameworks or libraries, or compose validation pipelines with userland tools. | ||||||
|
|
||||||
| A universal validator contract allows: | ||||||
|
|
||||||
| Rapid replacement of validation engines or libraries (vendor-agnostic interface). | ||||||
| Interchangeable custom and vendor-specific validators (e.g., open-source and proprietary). | ||||||
| Testability and integration with dependency injection containers. | ||||||
| Consistent error format for user feedback, translation, or error mapping. | ||||||
| Clean separation of simple (bool) and extended (structured, multi-error) validation. | ||||||
| This pattern avoids having to deeply couple code to a specific validator ecosystem (as in Symfony) and dramatically reduces refactoring when switching stacks or updating validation strategies. | ||||||
|
|
||||||
| #### Pros: | ||||||
|
|
||||||
| - Plug-and-play with any validator implementations (no hard dependency on a specific package) | ||||||
| - Extensible (vendors can add extra methods, but must comply with the common interface) | ||||||
| - Clean and decoupled design | ||||||
| - Advanced: error codes, rich violation objects | ||||||
|
|
||||||
| #### Cons: | ||||||
|
|
||||||
| - Slight abstraction overhead (vs. using one concrete implementation) | ||||||
| - Vendor- or application-specific error codes are not enforced by spec (but spec supports them) | ||||||
| - Contextual or cross-field validation must be built upon (not in base interfaces) | ||||||
|
|
||||||
| ## 3. Design Decisions | ||||||
|
|
||||||
| ### 3.1. Simple vs. Extended | ||||||
|
|
||||||
| To satisfy both lightweight and complex needs, two interfaces are provided: | ||||||
|
|
||||||
| - `SimpleValidatorInterface` — exposes only `isValid(mixed $value): bool`. Suitable for basic needs (is it a valid phone? email? int in range?...) and maximizing performance. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that running a single validation rule against the value? If so, I'd name it differently... like
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well... That probably IS a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe. That would be very useful if you want to replicate the same validation on the client side (mobile, SPA frontend) based on the rules used. |
||||||
| - `ExtendedValidatorInterface` — exposes `validate(mixed $value): ValidatorResponseInterface`, for structured results (validation status, errors, error codes/messages/etc.). | ||||||
|
||||||
| - `ExtendedValidatorInterface` — exposes `validate(mixed $value): ValidatorResponseInterface`, for structured results (validation status, errors, error codes/messages/etc.). | |
| - `ValidatorInterface` — exposes `validate(mixed $value): ValidatorResponseInterface`, for structured results (validation status, errors, error codes/messages/etc.). |
for the reason in https://github.com/php-fig/fig-standards/pull/1337/files#r2213153862
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.
I suggest to resolve this thread in the very end - when we'll handle everything else and then we can concentrate everybody on this
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.
Disagree here. The class name is the machine-readable code. We don't need more opaque inconsistent strings.
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.
@Crell class name doesn't work well for exposing it in APIs i.e. JavaScript or Android/iOS clients should not be dealing with PHP fully qualified class names.
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.
Actually, I thought of some flag-like codes like those ones in json_encode() function (second argument)
But, yeah, class names are not good enough - for example, if you want to pass those violations to frontend
Outdated
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.
See above.
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.
clarified
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.
I'd ensure that these fall into the given interfaces well.
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.
Also, in reality, single value validation without context is not enough.
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.
What about really simple Validators, like, EmailValidator, PhoneValidator, NotEmptyValidator ?
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.
We should think through what compound validation would look like. That should be included.
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.
That will probably require some analogue of Constraints (like in Symfony), which may be just too much for this PSR. Not quite sure
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,144 @@ | ||||||
| Validator PSR: Specification Draft | ||||||
| ================================== | ||||||
|
|
||||||
| This document describes common interfaces to validate values in PHP, in both simple and structured modes. | ||||||
|
|
||||||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", | ||||||
| "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be | ||||||
| interpreted as described in [RFC 2119][]. | ||||||
|
|
||||||
| The final implementations MAY decorate the objects with more | ||||||
| functionality than the one proposed but they MUST implement the indicated | ||||||
| interfaces/functionality first. | ||||||
|
||||||
|
|
||||||
| [RFC 2119]: http://tools.ietf.org/html/rfc2119 | ||||||
|
|
||||||
| ## 1. Specification | ||||||
|
|
||||||
| ### 1.1. Definitions | ||||||
|
|
||||||
| - Validation: Process of checking if input value satisfies certain constraints (type, format, range, etc.). | ||||||
| - Violation: Individual reason why a value fails validation (error). | ||||||
| - Error Code: Machine-readable string/code for a validation failure, suitable for mapping, i18n or programmatic branching. | ||||||
|
|
||||||
| ## 2. Interfaces | ||||||
|
|
||||||
| ### 2.1. SimpleValidatorInterface | ||||||
|
|
||||||
| ```php | ||||||
| namespace Psr\Validator; | ||||||
|
|
||||||
| /** | ||||||
| * Minimalistic validator interface. | ||||||
| */ | ||||||
| interface SimpleValidatorInterface | ||||||
| { | ||||||
| /** | ||||||
| * Validates supplied value. | ||||||
| * MUST return true if $value passes. | ||||||
| * MUST return false if $value fails. | ||||||
| * MUST NOT throw exceptions if $value fails. | ||||||
| * SHOULD throw ValidatorException if the Validator itself could not tell if the value passes or not (e.g. Validator misconfigured) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we use generic
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, because this one is more specific. RuntimeException is basically anything, while package exceptions is described specifically to show that error occures somewhere inside this package functionality
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're not going to handle it specifically, it doesn't make sense to make it specific.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I have long suffered from the absence of a built-in exception in PHP that would semantically denote an "improperly configured entity/application. And I think this is exactly one of those cases. |
||||||
| * | ||||||
| * @param mixed $value | ||||||
| * | ||||||
| * @throws ValidatorException | ||||||
| * | ||||||
| * @return bool | ||||||
| */ | ||||||
| public function isValid(mixed $value): bool; | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## 2.2. ExtendedValidatorInterface | ||||||
|
|
||||||
| ```php | ||||||
| namespace Psr\Validator; | ||||||
|
|
||||||
| /** | ||||||
| * Extended validator interface returning structured response. | ||||||
| */ | ||||||
| interface ExtendedValidatorInterface | ||||||
| { | ||||||
| /** | ||||||
| * Validates supplied value and returns a ValidatorResponseInterface instance. | ||||||
| * MUST return ValidatorResponseInterface if $value was validated at all (with any result). | ||||||
| * MUST NOT throw exceptions if $value fails. | ||||||
| * SHOULD throw ValidatorException if the Validator itself could not tell if the value passes or not (e.g. Validator misconfigured) | ||||||
| * | ||||||
| * @param mixed $value | ||||||
| * | ||||||
| * @throws ValidatorException | ||||||
| * | ||||||
| * @return ValidatorResponseInterface | ||||||
| */ | ||||||
| public function validate(mixed $value): ValidatorResponseInterface; | ||||||
|
||||||
| public function validate(mixed $value): ValidatorResponseInterface; | |
| public function validate(mixed $value, array $context = []): ValidatorResponseInterface; |
Probably?
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.
Yes. Either that or a DTO.
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.
added array $context = [] as second param
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.
This can be a property now, not a method. 😄
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.
There are zero benefits making it a property. Just drawbacks.
Ie. If it is a property you are forcing the implementation to be a value object.
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.
Those drawbacks should be gone with PHP 8.4 though. Not sure if we want to target PHP 8.4 for this standard just to gain access to hooked properties. That would as of today be rather strict and hurt the adoption of the standard.
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.
But there are still no benefits... =)
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.
- No, it does not force it to be a value object. It just changes the spelling to be simpler and more concise.
- The benefit is conceptual integrity. See the talk I gave at PHPTek this year on the new world properties open up for us.
- We should target 8.5, frankly. This wouldn't be ready before 8.5 is released anyway, and we've learned our lesson in the past that targeting an older-than-latest release always backfires and just makes more work for all involved.
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.
Putting on my Core Committee hat for a moment, I will never vote for a spec that goes out of its way to support 10 year old versions of PHP. A spec will always be used far more in the future than in the past. Anyone on PHP 5 still (god help them) isn't going to be able to use any 3rd party validator libraries anyway, because those will all be written for more modern PHP versions.
We learned our lesson from PSR-6, and PSR-7 (if my memory of the timing is right). And did it right in PSR-14 by using object. Use whatever language features are current and useful. If that makes the PHP version requirement high, so be it. The future is longer than the past.
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.
You are absolutely right @Crell about supporting ancient PHP releases. Most validation libraries that could adopt the new standard don't deliver new feature releases with PHP 7 support, so why bother about PHP 7.
But not supporting 10 year old PHP does not mean we have to exclude last year's PHP. As far as I can tell, requiring PHP 8.5 does not unlock any features that we could make use of in this contract, does it?
If hooked properties is what we want for this contract, let's target PHP 8.4? For Symfony at least, that would make adopting the standard way easier because we'll bump our PHP requirement to 8.4 in November.
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.
If there's no 8.5 features that would be useful, then yes, a version target of 8.4 is fine. But if we do find there are 8.5 features that would be useful, we should be very open to then requiring 8.5 to use them.
Off hand, I can't think of any new 8.5 features that would be useful here, but interface properties from 8.4, definitely are useful.
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.
I would've understand if you'd say smth like we can't support PHP 5.6 because we need to use object type as param (which we don't...yet). But drop older versions support just because 8.5 is fancy? Why? It's not like we need to do something overbearing - just not use new syntax in an interface (that may be used, btw, in libraries that extends that interface). It's not like interface suggests usage of money_format() or smth 😁
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.
I think this long discussion proves my point.
There are zero benefits making it a property. Just drawbacks.
I understand we have property hooks and interface properties in PHP 8.4. But these two features did not bring any value to PHP other then "it is nicer to write".
Since a PSR should be a standardized way to do something, I think we should consider adoption rather than try to move libraries to use language features that does not give any value.
My strongest recommendation is to keep these as methods.
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.
For intl, there is a need for:
/**
* Returns parameters used for {@see $message} translation.
*
* @return array A mapping between parameter names and values.
*/
public function getParameters(): arrayThere 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.
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.
I don't think you'd need a getParameters, however it might be best for getMessage to return string|Stringable. Stringable would allow it to be some sort of Phrase object that can be translated before it is output.
What do you think, @samdark?
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.
Yes. That makes sense. Enough flexibility for i18n while not covering it explicitly.
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.
getMessage() now returns string|\Stringable
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.
As specified, this is extraneous and unhelpful.
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.
We should resolve this in #1337 (comment) and then apply to all
Outdated
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.
Could also be a property.
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.
I suggest we decide that in one thread and then apply to all
#1337 (comment)
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.
It's not just about specific applications switching stacks. It's about me being able to use a validation routine in a stand-alone library and not care if it's going to be used with Symfony, Slim, or Laravel. Eg, in Serde I can just tell people "if you want fancier validation, go use PSR-Whatever and leave me alone." (Or integrate a PSR-Whatever bridge into Serde if I feel like it.)
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.
Oh, yeah, I see your point. Tho, I'm struggling with exact words to put that point in them 😕
Feel free to rephrase that in a suggestion)))