-
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 all commits
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. | ||||||
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 |
||||||
- `ExtendedValidatorInterface` — exposes `validate(mixed $value): ValidatorResponseInterface`, for structured results (validation status, errors, error codes/messages/etc.). | ||||||
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.
Suggested change
for the reason in https://github.com/php-fig/fig-standards/pull/1337/files#r2213153862 |
||||||
This separation enables applications to pick the level of detail they need. Many use cases will use only the simple interface. | ||||||
|
||||||
### 3.2. ValidatorResponseInterface | ||||||
|
||||||
A response from ExtendedValidatorInterface must expose: | ||||||
|
||||||
- `isValid(): bool` — Was validation successful? | ||||||
- `getViolations(): array<ViolationInterface>` — Why did it fail, with detailed error objects. | ||||||
|
||||||
### 3.3. ViolationInterface | ||||||
|
||||||
Every violation contains: | ||||||
|
||||||
- Machine-readable code (for programmatic matching, i18n, etc.). | ||||||
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. 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 commentThe 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. |
||||||
- Human-readable message (for fallback user feedback). | ||||||
- Optionally: parameters for message templates, offending value, etc. | ||||||
Spec only mandates code and message; implementations may extend. | ||||||
|
||||||
### 3.4. Exception Handling | ||||||
|
||||||
Validation failures (value rejected) are NOT exceptional: response returned with errors. | ||||||
|
||||||
Exceptions are for implementation/configuration/usage errors — not for negative validation. | ||||||
|
||||||
### 3.5. Swappability & DI | ||||||
|
||||||
Core purpose — to allow hot-swapping validator engines and compositions via DI or configuration, zero code refactor. | ||||||
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. See above. |
||||||
|
||||||
### 3.6. Scope | ||||||
|
||||||
Only single-value validation in scope; not object/collection/nested. | ||||||
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. I'd ensure that these fall into the given interfaces well. 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. Also, in reality, single value validation without context is not enough. |
||||||
Contextual validation may be layered by implementors using extensions. | ||||||
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. We should think through what compound validation would look like. That should be included. |
||||||
|
||||||
## 4. People | ||||||
|
||||||
### 4.1 Editor(s) | ||||||
|
||||||
- TBA | ||||||
|
||||||
### 4.2 Working Group members | ||||||
|
||||||
- TBA |
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. | ||
Comment on lines
+10
to
+12
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. This block is unnecessary. |
||
|
||
[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) | ||
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 |
||
* | ||
* @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; | ||
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 the validation passed, we don't need detailed information. We can likely short-circuit that to something like 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. @Crell that would create more code handling it. Having a single return value works better in this case. 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. Usually there's a context that matters, such as in the case when you need a password to be repeated exactly. |
||
} | ||
``` | ||
|
||
## 2.3. ValidatorResponseInterface | ||
|
||
```php | ||
namespace Psr\Validator; | ||
|
||
/** | ||
* Wraps the result of validation including status and violations list. | ||
*/ | ||
interface ValidatorResponseInterface | ||
{ | ||
/** | ||
* MUST return true if $value passed, and false otherwise. | ||
* MUST be immutable. | ||
* | ||
* @return bool | ||
*/ | ||
public function isValid(): bool; | ||
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. This can be a property now, not a method. 😄 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. 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 commentThe 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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more.
For Symfony this would mean that we cannot easily implement the standard before version 9 which will be released in November 2027. That's quite a stretch. |
||
|
||
/** | ||
* Returns all violations (validation errors) as ViolationInterface objects. | ||
* | ||
* @return ViolationInterface[] | ||
*/ | ||
public function getViolations(): array; | ||
} | ||
``` | ||
|
||
## 2.4. ViolationInterface | ||
|
||
```php | ||
namespace Psr\Validator; | ||
|
||
/** | ||
* A single validation violation. | ||
*/ | ||
interface ViolationInterface | ||
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. 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(): array |
||
{ | ||
/** | ||
* Machine-readable error code (for mapping, i18n, client logic). | ||
* | ||
* @return string | ||
*/ | ||
public function getCode(): string; | ||
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. As specified, this is extraneous and unhelpful. |
||
|
||
/** | ||
* Error message (human-readable, MAY be locale-dependent). | ||
* | ||
* @return string | ||
*/ | ||
public function getMessage(): string; | ||
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. Could also be a property. |
||
} | ||
``` | ||
|
||
## 2.5. ValidatorException | ||
|
||
```php | ||
namespace Psr\Validator; | ||
|
||
/** | ||
* Thrown ONLY if validator is misconfigured or cannot process request. | ||
* MUST NOT be thrown if $value was actually tested, no matter the result. | ||
*/ | ||
class ValidatorException extends \InvalidArgumentException | ||
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. I believe FIG convention is to us an interface here to mark exceptions from this package, not a base class. |
||
{ | ||
} | ||
``` |
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.)