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)
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.
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.
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.
Mutable objects can be challenging to work with in larger codebases
because different parts of a system may modify the same instance, making
it difficult to trace where and when changes occurred. This becomes
especially problematic when debugging unexpected behaviour.
By making `Validator` immutable, we ensure that adding rules via
`with()` returns a new instance rather than mutating the original, and
we use the `with()` method inside `__call()`, making every call to a
rule into a clone of the current `Validator`.
This provides several benefits:
1. Predictability: A `Validator` instance will always behave the same
way throughout its lifetime, regardless of what other parts of the
codebase do.
2. Safe dependency injection: Users can now confidently inject a base
`Validator` from a DI container, knowing that any modifications made
elsewhere will not affect their instance.
3. Easier debugging: Since validators cannot be mutated after creation,
there's no need to track down where an unexpected rule was added.
4. Reusability: Users can create an initial `Validator` with some base
rules and reuse it by just adding new rules to the chain without
affecting the base `Validator`.
This method hasn’t been released in any version yet, and after
reconsidering the current design, I decided to simplify the `Validator`
and remove it. Unfortunately, there isn’t a substitute for this method
at the moment, but I’m fine with keeping things as they are.
The `setTemplate()` method can be confusing, as it can be tricky for
someone to determine which chain is being templated. Using the
`Templated` rule makes this much more explicit and adds a little bit of
verbosity. For users, this will be a significant change, but there are
easy ways to update this code in their projects, so I’m not overly
concerned about it.
Another benefit of this change is that it makes the `Validator` much
simpler, as it no longer needs to track the template. This change also
makes the `Reducer` simpler, for similar reasons to the `Validator`.
However, because the `Validator` is simpler, we can remove the code that
the `Reducer` had to meet the specific needs of the `Validator`.
Since we have the ability to use `not` as a prefix, having rules that
validate negative behaviour makes them a bit inflexible, verbose, and
harder to understand.
This commit will refactor the `NotEmpty`, and rename it to `Falsy`. It
will no longer trim strings, because Blank does a much better job at it;
it only simulates the behaviour of PHP’s native `empty()` function.
Because `Falsy`, `Blank`, and `Undef` have similar behaviour, I created
a page to demonstrate the difference and show when the user should use
one or the other.
Assisted-by: Cursor (claude-4.5-opus-high)
Currently, the only way to handle when a validation fails is by
`assert()`, which either throws an exception or doesn't. That means that
every time a user wants to handle the results, they must use try-catch
blocks, which may add some overhead.
This commit introduces the `ResultQuery` class that wraps a `Result`
object, providing an alternative to exception-based validation. This
allows users to handle results directly without try-catch blocks.
I’m taking a risky move here using the old method `validate()`, but I
can’t think of a better name for this method.
Currently, the templates that a user provides when running `assert()`
can significantly impact how the message is displayed. Because of this,
the formatters become complex as they all need to handle similar
conditions to format results.
This commit changes this behaviour, letting only the
`InterpolationRenderer` handle the templates. This makes the code
simpler and allows people to use the `InterpolationRenderer` directly,
without needing to figure out how to handle templates. Thinking about it
further, I believe handling templates is a concern for the `Renderer`
anyway, and this will open the way to other improvements using the
renderer.
I also removed the exception that is thrown when the template is not a
string, because I think that after validation has failed, we should not
throw any other exceptions, as that could cause unexpected errors for
users.
There are a few use cases in which you would like to have a custom
exception but, at the same time, reuse the message or exception that
validation might give you.
This commit creates a new feature that allows users to define a callable
that will generate an exception when it fails.
Because we now have a single "assert()" method, we have more freedom to
add more customizations to it. This specific one is handy if someone
wants to use the library to validate but wants to use their own
exceptions.
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
After many refactorings, no rules use the previous validation engine.
That means we can remove the unused code from the repository and switch
from the previous to the new validation engine everywhere.
This commit will also soft deprecate the methods "validate()", and
"check()" in all the rules and the "assert()" in all rules but the
Validator itself. That means using those methods will still be allowed,
but static analysis tools might complain.
This is a big step toward releasing the next major version, as the code
is pretty much the way it should be when I release the next version.
There's some documentation to be updated, and I would like to change the
behavior of a couple of rules.
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
There are a few "problems" with the current engine:
- Allowing each rule to execute assert() and check() means duplication
in some cases.
- Because we use exceptions to assert/check, we can only invert a
validation (with Not) if there are errors. That means that we have
limited granularity control.
- There is a lot of logic in the exceptions. That means that even after
it throws an exception, something could still happen. We're stable on
that front, but I want to simplify them. Besides, debugging exception
code is painful because the stack trace does not go beyond the
exception.
Apart from that, there are many limitations with templating, and working
that out in the current implementation makes it much harder.
These changes will improve the library in many aspects, but they will
also change the behavior and break backward compatibility. However,
that's a price I'm willing to pay for the improvements we'll have.
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This change will bring many breaking changes. The good thing is that we
can finally use more modern resources available in PHP.
I can imagine that's not a popular change since it will bring many
breaking changes to users, but we shouldn't be stuck in time because of
that. Using some of those features will make it easier to contribute to
the project. At least, I hope so.
There are still some useless doc-blocks, and we're not using "readonly"
properties when we could. I aim to send those changes soon.
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
Whenever is possible it is better to declare our classes as final. The
PHPUnit tests should not be extended, therefore there is no reason for
them to not be final.
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
Due to the current status of the development of the library, it seems
like we will be supporting version 1.1 for a long time. Even when we
release version 2.0 we will still give support for version 1.1 for a
while.
This commit will make sure that version 1.1 is fully supported for PHP
7.2 and 7.3. Also, it will remove the support for HHVM since it will not
keep the compatibility with PHP anymore [1].
In order to make that happen, this commit will create a TestCase from
Validation so we can use the same API to create mocks in both PHPUnit
versions 4.0 and 5.0.
During the development of this commit, I noticed that PHPUnit 4.0 had
issues to mock "SplFileInfo" and for that reason, this commit will also
replace those mocks by "SplFileInfo" instances.
[1]: https://hhvm.com/blog/2018/09/12/end-of-php-support-future-of-hack.html
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit will make sure that every class, interface, or trait will
have the "@author" annotation in it.
In order to create a list of authors, I used the "git blame" command,
which means that if someone changed or even created the file but does
not have any remaining line will not be shown in the list; it's a
trade-off worth but it is worth it. The other way to do it would be
carefully checking each file.
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>