Rename the Composite class to LogicalComposite to more accurately reflect
its purpose as a validator that combines child validators using logical
operations (AND, OR, NAND, XOR).
This better naming also opens the door for additional composite patterns
beyond logical operations, enabling future validator compositions.
Assisted-by: OpenCode (GLM 4.5)
The documents on translation were updated to feature symfony with
an array provider. Duplicated container notes were extracted to
a single configuration.md file.
An API for accessing the messages, so users don't have to copy
and paste them from the source or docs, was provided and
TemplateResolver was refactored to use it.
This commit introduces a mechanism for validators to return early once
the validation outcome is determined, rather than evaluating all child
validators.
The ShortCircuit validator evaluates validators sequentially and stops
at the first failure, similar to how PHP's && operator works. This is
useful when later validators depend on earlier ones passing, or when
you want only the first error message.
The ShortCircuitCapable interface allows composite validators (AllOf,
AnyOf, OneOf, NoneOf, Each, All) to implement their own short-circuit
logic.
Why "ShortCircuit" instead of "FailFast":
The name "FailFast" was initially considered but proved misleading.
While AllOf stops on failure (fail fast), AnyOf stops on success
(succeed fast), and OneOf stops on the second success. The common
behavior is not about failing quickly, but about returning as soon as
the outcome is determined—which is exactly what short-circuit
evaluation means. This terminology is familiar to developers from
boolean operators (&& and ||), making the behavior immediately
understandable.
Co-authored-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
Assisted-by: Claude Code (Opus 4.5)
Due to the continuous increase in the number of companies and the
imminent exhaustion of available CNPJs (Brazilian taxpayer
identification numbers), the Brazilian Federal Revenue Service is
instituting the alphanumeric CNPJ. The initiative aims to
facilitate the identification of all companies and improve the
business environment, contributing to the economic and social
development of Brazil.
The alphanumeric CNPJ will be assigned, starting in July 2026,
exclusively to new registrations.
Changes:
- Add support for alphanumeric CNPJ validation
- Format code according to PHPCS standards
- Simplify CNPJ conversion to uppercase character array
- Add documentation about CNPJ structure
I ran the `bin/console spdx --fix` with different strategies for
different files. For most of the core classes, since they've been
drastically rebuilt, I've run it with the `git-blame` strategy, for for
the `src/Validators`, in which the API changed completely but the logic
remains the same, I use the `git-log` strategy.
Improves SPDX header linting to ensure consistent license metadata across
the codebase.
Key changes:
- Enforce deterministic tag ordering (License-Identifier, FileCopyrightText,
FileContributor) to ensure consistency, prevent merge conflicts, and
simplify code reviews
- Add contributor alias mapping to consolidate contributors with multiple
emails or name variations (e.g., "nickl-" → "Nick Lombard")
- Add --contributions-strategy option with "blame" (current code authors)
and "log" (all historical contributors) to support different attribution
philosophies
- Add optional path argument to lint specific files or directories
- Add --fix option to automatically correct header issues
Assisted-by: Claude Code (claude-opus-4-5-20251101)
See #1668 for more info.
There is a new test "Deep name collision" on AllOfTest that
exemplifies the kind of collision this change solves.
Some extra `__root__` keys needed to be added to a select few
other scenarios for consistency.
The previous name was confusing as it focused on the implementation
detail (calling a callable) rather than what the validator actually
does. The new name "After" better conveys the validator's purpose:
validating the input after applying a transformation.
Assisted-by: Claude Code (Opus 4.5)
The previous name was confusing as it focused on the implementation
detail (receiving a callback) rather than what the validator actually
does. The new name "Satisfies" better conveys the validator's purpose:
checking whether the input satisfies a given condition.
Assisted-by: Claude Code (Opus 4.5)
The name "Factory" better conveys the validator's purpose: dynamically
creating a validator at runtime based on the input value. "Lazy" was
misleading as it suggested simple deferred evaluation rather than
input-dependent validator construction.
Also renamed the constructor argument from `$validatorCreator` to
`$factory` for improved expressiveness.
Assisted-by: Claude Code (Opus 4.5)
Previously, the URL validator accepted all kinds of URLs.
Combining it with other validators like `Domain` and `Ip` manually
is clumbersome, since the rules are extensive and require small
tweaks such as understanding bracketed IP addresses.
This change makes that composition built-in. The validator still
uses the FILTER_VALIDATE_URL from PHP, but now it also goes deeper
and validate portions of the URL that other validators support.
Changes to `Call` were made so that it can be serialized under
certain circumstances (when invoking a static method call), making
it more flexible.
A static internal method helper for trimming the IP was added to
the Url validator class and marked as internal, as it cannot be
both private, serializable and accessible to the `Call` instance
all at the same time. The `@internal` annotation should advise
users that it's not a public API according to phpdoc conventions.
The Prefix transformer had many loops that could be avoided. This
change replaces them for a compiled PCRE regex, taking advantage
of recent PCRE JIT capabilities introduced in PHP.
These changes offer no performance trade-offs, improving lookup
for all categories of prefixes (property/key with shift, ignore
list and fallback to simple rule).
The most affected is the simple rule (no prefix or no conflict
with any kind of prefix rule), yielding the most gains.
- Changed Property so it doesn't have an unreachable line anymore.
- Rename ValidatorTest to ValidatorBuilderTest to better reflect
the source.
- Added missing tests for KeyExists's ArrayAccess support.
- Added tests for using custom exceptions.
This change makes the ramsey/uuid optional dependency testable
by using the ContainerRegistry to acquire an instance to its factory
instead of relying on `class_exists` directly.
With PHP's promoted properties, there is little reason to have an
abstract class that only provides a constructor and a proxy method.
The greater part of the classes that extended Wrapper had their own
implementation of evaluate(), hence it made very little sense to keep
Wrapper in the codebase.
Assisted-by: Claude Code (Opus 4.5)
Now empty values are again allowed in FilteredArray-style
validators.
To solve the issue with negation, a Result attribute was
added to signal indeciseveness (when a result cannot be
reliably inverted). On such cases, we consider that result
to be valid.
For example, `v::not(v::min(v::equals(10)))` says "The
lowest value of the iterable input should not be equal 10".
If the input is empty, we cannot decide whether its minimum
is equal to 10 or not, so the validator essentially becomes
a null-op.
Users that want to ensure these validators have a valid
decidable target must use it in combination with `Length`
or other similar validators to achieve the same result.
The Masked validator decorates other validators to mask sensitive input
values in error messages while still validating the original unmasked
data.
This validator is essential for applications handling sensitive
information such as passwords, credit cards, or email addresses. Without
it, users would need to implement a custom layer between Validation and
the end user to prevent PII from appearing in error messages or logs.
With Masked, sensitive data protection is built directly into the
validation workflow with no additional abstraction required.
Assisted-by: Claude Code (Opus 4.5)
Adjacent results are results that treat the same input. When overwriting
the input of a result, we should also overwrite the input of its adjacent
result to maintain consistency. Currently, there are no cases where this
has caused issues, but this change prevents potential problems.
Assisted-by: Claude Code (Opus 4.5)
This is a mid-size refactor that affects several validators.
Most prominently, the ones that had an `$identical` parameter
to deal with case sensitiveness.
This parameter was confusing, effectively making validators such
as `Contains` behave very differently for arrays versus strings.
In arrays, `$identical` meant "the same type", while it in strings
it meant "case sensitive".
That parameter was removed, and the default behavior is now to
always compare **case sensitive** and strict typing.
A document explaining how to combine other validators in order
to achieve case _insensitive_ comparisons was added.
Additionally, the `Call` validator was refactored back to be
suitable to take on the task of being a fast, quick composable
validator.
With the introduction of `Circuit`, we can shift the responsibility
of dealing with possible mismatches to the user. This kind of type
handling is demonstrated in how I refactored `Tld` to account for
the type mismatch without setting error handlers.
This commit removes validators described in #1642, refactoring
to clean up after their removal.
- Url was refactored to use the function `filter_var` instead.
- tests/bootstrap.php is no longer needed and was removed.
- Updated migration guide with recommendations for replacements.
Similar to d8e31db (commit that containerized iso code dbs), but
this time for PhoneNumberUtil.
This makes the optional dependency testable.
PhoneNumberUtil doesn't have a public constructor, so a factory
was declared instead.
The main focus of this change is to make those optional dependencies
more testable.
Unfortunately, some phpstan-ignores had to be included, since ::set
is not a PsrContainer method. We're only using it on tests though,
so it's fine. It targets our php-di container for testing purposes
only. The real implementation only relies on ::get.
This change also has the side effect of improving the performance
of those validators by not instantiating their databases each time
a iso validator is built, achieving massive improvements in those
scenarios. A small benchmark with no assertions was added to track
that improvement.
The Each validator was using "Outer" names (the name applied to
the parent) in child results and vice-versa.
This commit fixes it, and also streamlines the Result class,
introducing helper `mapChildren` and `mapAdjacent` methods and
removing unecessary recursive array_maps.
Named children now also display their names, allowing users to
create more meaningful messages that do not spam nested
`.0`...`.0`...etc numeric keys which could be confusing. If the
user does not name them, the previous behavior is kept.
The `findByPath()` method was failing to return results when using nested
dot-notation paths such as `user.email` or `items.1`. However, it’s returning
`null` instead of the expected result in some cases.
The root cause was a mismatch between how paths are stored vs searched:
- Storage: Validators like Key and Each create results where the path is stored
as a linked list. For `user.email`, the "email" result has `path="email"` with
`parent="user"`.
- Search (old): The method expected a tree structure where it would find a child
with `path="user"`, then search that child for `path="email"`. But no child
had `path="user"` - only "email" (with "user" as its parent).
The fix computes each result's full path by walking up the parent chain and
compares it against the search path. Also converts numeric strings to integers
when parsing paths (e.g., `items.1` → `['items', 1]`) since array indices are
stored as integers.
While working on this fix, I also realised that to expose the result's status,
it’s best to use `hasFailed()` instead of `isValid()` in `ResultQuery`, since
users will mostly use results when validation failed, not when it passed.
Assisted-by: Claude Code (Opus 4.5)
We had different ways of saving and loading files from `data/`, so I decided to
unify them to simplify things. I repurposed the `DomainInfo` class and named it
`DataLoader`, so we can use the same class to load anything from the `data/`
directory.
When calling `v::validatorName()`, a `ValidatorBuilder` is instantiated
to manage the validation chain. However, users generally expect the ID
to reflect the specific validator invoked rather than the internal
builder class.
This commit updates `Id::fromValidator()` to resolve the ID from the
underlying validator when it is the sole member of the
`ValidatorBuilder`, ensuring more intuitive identification.
We've always considered renaming this directory, as it's not a common
standard to name `library` the directory where the source code of a
library it. Having it as `src/` is a common pattern we find in several
PHP libraries these days.
Acked-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
I've moved almost all the code for placeholder replacement and parameter
modifiers into an external library called Respect\StringFormatter. This approach
allows us to evolve the template capabilities without making major changes to the
Validation's code.
This commit will introduce another dependency, `respect/string-formatter`, and
will upgrade the version of `respect/string-formatter`, which simplifies our
internal API greatly.
While making this change, I also updated how we generate exceptions. Instead of
rendering the full message and the array of messages, we delegate that creation
to the `ResultQuery`, which improves performance because we don’t need to render
those big messages unless the user actually needs them.
A new workflow, continuous-integration-perf.yml was introduced. It:
- Checks out the `benchmarks` branch locally.
- Runs the benchmarks, accounting for non-existant baselines
and target (main/PR).
- Stores the .phpbench storage folder and a human-readable
report in the `benchmarks` branch.
- Does not make a PR fail, and never reports a failure
when merging to main.
- Allows workflow_dispatch for quick re-runs, and has an
option to reset the baseline in case something changes
(GitHub runner setup gets faster/slower, major refactors,
etc).
Thus, it keeps a historical record of all benchmark results.
These results can be viewed by exploring GitHub via the web
interface and seeing the changes in `latest.md` (the human
file commited).
Additionally, one can clone the `benchmarks` branch and run
`phpbench log` to explore the history in more detail.
Some adjustments to previously added benchmarks were made:
- Assertions were included in order to track time and memory
tresholds.
- The benchmarks are now more surgical, and address the
concrete validators instead of the whole chain validate.
These changes were made to make benchmarks more isolated, with
the intention of adding chain-related benchmarks separately
in the future.
This commit introduces REUSE compliance by annotating all files
with SPDX information and placing the reused licences in the
LICENSES folder.
We additionally removed the docheader tool which is made obsolete
by this change.
The main LICENSE and copyright text of the project is now not under
my personal name anymore, and it belongs to "The Respect Project
Contributors" instead.
This change restores author names to several files, giving the
appropriate attribution for contributions.
This commit concludes the effort to make all current validators
serializable by fixing the remaining ones.
The ability to use `finfo` instances on some filesystem validators
was removed. `Image` was refactored to be a readonly class.
A command was added to the main `composer qa` flow that checks
if all validators are covered by smoke tests (which are currently
used by benchmarks and serialization tests). Therefore, this commit
also ensures that every validator has a benchmark.
This commit introduces a new feature test: SerializableTest, that
tests several validators for their ability to be serialized and
unserialized.
It also makes it so that the same list of validators can be used
by both simple benchmarks and "smoke tests" of all kinds, including
this serialize/unserialize one.
Additionally, the FilterVar validator was modified. Previously, due
to the use of Callback, it was not serializable, but now it is.
After renaming rules to validatores, it doesn't make sense to keep on
having that exception. I renamed it to a more cleaner name, not
mentioning the constructor because I think that if the constructor is
not valid, the validator is not valid, hence the name I chose.
This validator is similar to Contains, but also checks how many
times the needle appears.
Additionally, the Domain validator was changed to use it instead
of relying on an unserializable callback, thus, making it
serializable.
This change introduces a less misleading exception when trying to
construct an instance and failing due to mismatched arguments
coming from ReflectionExceptions.
There's more value on showing how `assert()` displays the validation
messages than simply showing if `isValid()` returns `true` or `false`.
However, that increases the chances of having outdated documentation, so
I created a doc linter that updates the Markdown files with the
correct message.
This commit is the first step in setting up *Continuous Performance*
for the repository.
- Adds phpbench/phpbench to dev dependencies.
- Adds an initial `ValidatorBench.php` file with validate benchmarks
for several validators.
- Adds `composer bench` script to run benchmarks.
- Adds `composer bench:profile` script to generate profiles.
This commit resolves an issue where validation messages would overwrite
each other when multiple validators failed on the same path or key
(e.g., within an `Each` or `Key` validator).
Changes to `NestedArrayFormatter`:
- Implemented a merge strategy: Key collisions now result in a list of
messages instead of the last message winning.
- Improved handling of mixed key types: When both numeric and string
keys are present (common in composite validators), numeric keys are now
replaced by the validator's ID (e.g., `arrayType`, `equals`) to provide
meaningful, distinct keys.
- Preserved list behavior: Purely numeric key sets are treated as lists,
maintaining their sequence without re-keying logic.
- Refactored the class to use smaller, single-purpose methods and
`array_reduce` for clarity.
Tests:
- Updated feature tests (`EachTest`, `AttributesTest`, etc.) to expect the
full set of validation errors.
- Enhanced `NestedArrayFormatterTest` with scenarios for key collisions,
mixed keys, and ID substitution.
I created those validators to make it easy to parse parameters or
console command inputs that were answers to questions one might ask.
One of the biggest problems is that it depends on the machine's locale,
which can be a bit troublesome, rather than receiving a locale in the
constructor. That doesn’t allow for a lot of flexibility when someone
has a multi-lingual application. Additionally, these validators rely on
the regex from `nl_langinfo()`, which is very permissive, resulting in
false positives.
I have a working version of a console command that retrieves data from
the Unicode Common Locale Data Repository (CLDR) and updates a list of
`yesstr` and `nostr` strings from the main XML file of each language.
However, I came to realise that the whole thing is not worth it.
The validators Yes and No can be replaced by using rules like `Regex`
and `In`. They won’t have the ease of multilingual support, but I don’t
think those validators are used a lot. So, I decided I would just remove
them, and if users really ask for it in the next major version, I’d be
happy to revive my branch.
The name "rule" has always been confusing to me. It can be when you talk
about "validation rules", but it’s a very verbose way to describe it,
and it doesn’t work all the time.
This commit will rename the interface `Rule` to `Validator`, but it will
also rename the concept of "rule" to "validator".
The `Validator` class implements the Builder patterns, because it builds
a complex validator within a chain. This is a major breaking change, as
the `Validator` class is the foundation of the library. However, that’s
something relatively easy to replace everywhere.
Missing date information are now reset to 0 (1970-01-01T00:00:00),
because otherwise they are taken from current date/time, so:
`Respect\Validation\Validator::date("m")->isValid('06')` is not valid on
31st of every month.
This can sometimes interfere with how dependency injection containers
work, and since we’re using a dependency injection container, this
becomes easier to manage.