Commit graph

37 commits

Author SHA1 Message Date
Alexandre Gomes Gaigalas
91ceaafd2d Update translation docs
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.
2026-02-06 17:11:34 +00:00
Henrique Moody
7c681fec66
Fix SPDX headers in all files
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.
2026-02-03 15:23:23 +01:00
Alexandre Gomes Gaigalas
1b584d88a3 Handle more collision scenarios on NestedArrayFormatter
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.
2026-02-02 20:21:14 +00:00
Henrique Moody
a372a0b90b
Replace template rendering code with Respect\StringFormatter
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.
2026-01-21 17:29:15 +01:00
Alexandre Gomes Gaigalas
d9cdc118b2 Introduce REUSE compliance
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.
2026-01-21 06:28:11 +00:00
Alexandre Gomes Gaigalas
cd96d01364 Fix message overriding bug in NestedArrayFormatter
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.
2026-01-12 10:42:11 +00:00
Henrique Moody
54ac76adce
Rename Rule to Validator
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".
2026-01-05 17:36:34 +01:00
Henrique Moody
a5ac80c733
Do not instantiate objects in the constructor of core classes
This can sometimes interfere with how dependency injection containers
work, and since we’re using a dependency injection container, this
becomes easier to manage.
2026-01-04 15:46:40 +01:00
Henrique Moody
ec70cf7bc2
Ensure formatters only format, not modify results
When I changed the library to not overwrite existing names [1], I wasn't
happy with how `FirstResultStringFormatter` was changing the results,
because the results should be completely ready by the time they arrive
in the formatters.

This commit changes that behaviour, ensuring the results are complete
with all necessary information by the time they reach the formatters.

Along with those changes, I refactored some stringifiers and simplified
the `InterpolationRenderer`; we were not overwriting the "name"
parameter anyway, as it was just an unnecessary overhead.

[1] 8332d28acc
2026-01-03 19:47:30 +01:00
Henrique Moody
cd6bcd470b
Enable adding modifiers without changing InterpolationRenderer
The `InterpolationRenderer` was violating the open-closed principle,
because every time we would want to add a new modifier, we would need to
change its implementation.

This commit changes that behaviour by creating a `Modifier` interface.
The classes implementing that interface are using a chain of
responsibility to pass the data to the next one. Using a chain of
responsibility makes a lot of sense, since it's only possible to have
one modifier at a time.
2025-12-30 11:17:11 +01:00
Henrique Moody
48405271c5
Replace placeholder "name" with "subject"
The `{{name}}` placeholder could represent different things depending on
the state of the Result, and referring to it as `{{name}}` seems
arbitrary. This commit changes it to `{{subject}}`, which is much more
generic and it describes well what that placeholder can mean.
2025-12-26 21:30:01 +01:00
Henrique Moody
8332d28acc
Do not overwrite existing names
This commit addresses the skipped tests by modifying certain core
concepts in the library. The way names work has always been somewhat
confusing, even to me. I’ve established that existing names will never
be overwritten, and if a path is already defined, we will change the
name to also include the path.

I’m not very happy with how I’m solving this problem, because the
`FirstResultStringFormatter` is changing some behaviour in the results
that seems to be something that should always happen before. But, for
the moment, I will keep it as is.
2025-12-26 14:48:23 +01:00
Henrique Moody
5b00d69766
Update how we handle templates
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.
2025-12-22 14:05:55 +01:00
Henrique Moody
644ecb5190
Change the contract of the formatters
Both `ArrayFormatter` and `StringFormatter` accept an instance of the
`Translator`. Thinking about it a bit better, I realised that a
formatter might not always need a `Translator`, but it will surely need
a `Renderer`.

Besides, the `InterpolationRenderer` needs to take translation into
account, so it seems more natural to me that this is the one that will
get an instance of the `Translator`, as other implementations of the
`Renderer` might not even deal with translations.
2025-12-22 13:43:59 +01:00
Henrique Moody
bf9b970e24
Move important value objects to the root namespace
The `Id`, `Name`, and `Path` value objects are not only message-related
concerns, they're part of the core of the library, hence it makes sense
to place them at the root namespace.
2025-12-21 11:14:47 +01:00
Henrique Moody
137c74c5b3
Change how we trace the path of results
Currently, we’re using scalar values to trace paths. The problem with
that approach is that we can’t create a reliable hierarchy with them, as
we can’t know for sure when a path is the same for different rules. By
using an object, we can easily compare and create a parent-child
relationship with it.

While making these changes, I deemed it necessary to also create objects
to handle Name and Id, which makes the code simpler and more robust. By
having Name and Path, we can create specific stringifiers that allow us
to customise how we render those values.

I didn’t manage to make those changes atomically, which is why this
commit makes so many changes. I found myself moving back and forth, and
making all those changes at once was the best solution I found.
2025-12-20 22:19:17 +01:00
Henrique Moody
28390400df
Rename StandardRenderer to InterpolationRenderer
The name `StandardRenderer` is ambiguous because it doesn't describe the
class's behavior, only that it is the "standard" or default
implementation. This follows the same reasoning as the recent
refactoring of `StandardFormatter`.

The renderer's primary function is to process a message template by
replacing placeholders (like `{{name}}`) with their corresponding
values. This process is known as string interpolation.

To better reflect this responsibility, the class has been renamed to
`InterpolationRenderer`.

This new name makes the class's purpose immediately clear, which
improves the overall clarity and maintainability of the rendering
system. It also helps to establish a clearer naming convention for any
future renderers (e.g., `JsonRenderer`, `HtmlRenderer`).
2025-12-19 17:00:34 +01:00
Henrique Moody
423172985b
Remove unused argument from Renderer::render()
Apparently, we were not using the argument anywhere, but I didn't notice
that before.
2025-12-19 17:00:31 +01:00
Henrique Moody
82e0722443
Split the Formatter into different formatters
I've noticed that the `StandardFormatter` was quite bloated, which made
it difficult to maintain. Understanding what each method was doing was
quite complicated. Besides, the name "Standard" doesn't mean anything,
because it doesn't say what the implementation does.

I split the `Formatter` into two different interfaces: `StringFormatter`
and `ArrayFormatter`, and I moved some code around:

*  `StandardFormatter::main()` -> `FirstResultStringFormatter`
* `StandardFormatter::full()` -> `NestedListStringFormatter`
* `StandardFormatter::array()` -> `NestedArrayFormatter`

That opens up new ways of handling error messages, potentially
introducing features like `JsonStringFormatter` or `FlatArrayFormatter`
in the future.

While working on this, I removed a significant amount of unnecessary
code, which also improved my overall understanding of those formatters.

I'm not very happy with all the methods in `ValidatorDefaults`, but I
will refactor that later.
2025-12-19 16:20:28 +01:00
Henrique Moody
cfeb01e89e
Bump respect/coding-standard from 4 to 5 2025-12-18 19:03:39 +01:00
Fabio Ribeiro
0e8ac1817b
Rename Result->isValid method 2025-03-31 21:29:37 +02:00
Henrique Moody
901774f6b7
Improve naming and delete unnecessary Mode
I don't expect us to have more modes, hence a simple boolean value
should be enough for indicating the mode of the teamplate. Apart from
that, the name "inverted" woudln't always make sense, because if you
invert something that is inverted, it gets back to its original mode.

This commit will remove the `Mode` enum, and also improve the naming of
some methods in the `Result`.
2025-01-17 10:51:18 +01:00
Henrique Moody
a8ae57fdef
Create a stringifier for "listed" values
This can be extremely useful when dealing with custom templates.
2024-12-27 04:29:00 +01:00
Henrique Moody
4379f66bec
Create a stringifier for "quoted" values
The `StandardQuoter` adds backticks around strings, which indicates that
it's not a simple string but a code. With this stringifier, we can add
quotes to placeholders directly into templates.
2024-12-27 00:49:12 +01:00
Henrique Moody
18d8246fa5
Remove duplication and improve naming of adjacent results
I identified a pattern among rules that create results with adjacent
results, so I created a method that abstracts that. I did have to
compromise with the DateTimeDiff, having to escape the input instead of
using the name itself, but that seems like a good trade-off.

I've also renamed "Subsequent" to "Adjacent" because it sounded better.
This is the second time I've renamed this concept, and I hope it will be
the last.
2024-12-20 17:12:11 +01:00
Henrique Moody
94daa8d669
Use Pest instead of PHPT files
Although I love PHPT files, and I've done my fair share of making it
easier to write them in this library, they're very slow, and running
them has become a hindrance.

I've been fidgeting with the idea of using Pest for a while, and I think
it's the right tool for the job. I had to create a couple of functions
to make it easier to run those tests, and now they're working really
alright.

I migrated all the PHPT files into Pest files -- I automated most of the
work with a little script using "nikic/php-parser"; this commit should
contain all the previous PHPT tests as Pest tests.

The previous integration tests would take sixteen seconds, and the Pest
tests take less than a second.
2024-12-16 17:07:47 +01:00
Henrique Moody
82cb05b197
Handle siblings when creating messages
The way we display messages could have been better, and it took me a
while to realise that to make it better, I would need to handle the
siblings of a result before deciding whether we should render it.

Another issue was that rules like Key and Property had to create a
"dumb" parent just so we would display the messages correctly, and in
some cases, that wasn't even enough.

This commit introduces quite a few changes to how the library works,
making the messages much more straightforward.
2024-12-13 02:34:27 +01:00
Henrique Moody
52e628fc6f
Rename "siblings" to "subsequent"
The name "subsequent" better represents those results. What I would
consider a "sibling" would be another child from the same result.
2024-12-13 02:16:16 +01:00
Henrique Moody
e6af762fe4
Rename "Validatable" to "Rule"
Besides the interface's name, everything already calls this type "Rule",
not "Validatable." This commit puts a stone on it and renames the
interface for better naming.
2024-12-05 19:32:14 +01:00
Henrique Moody
d356696af9
Upgrade translation mechanism
Currently, defining translations is quite cumbersome, and the translator
callback is passed to the constructor of multiple classes, which makes
it quite ugly and could make translations inconsistent.

This commit completely changes how translations are done in Validation.
Instead of using a callback, it uses a specific class, and `Validator`
will pass that object through the objects that render the messages.
2024-12-05 15:42:40 +01:00
Henrique Moody
b894593135
Rename "negative" to "inverted" mode
The original name was heavily influenced by the fact that we get those
messages when using the "Not" rule; however, that rule inverts the
validation despite the current validation mode. It can be confusing at
times with certain rules, so naming it as "inverted" makes more sense
than "negative".
2024-12-02 22:00:18 +01:00
Henrique Moody
fefe905e0b
Include "__root__" when getting message as an array
When converting an object into an array, we exclude the message root
message from it. Since we're using a convention to template those
messages as an array, we could also use the same convention to return
those messages.

While working on it, I noticed that the name "__self__" wasn't
reflecting what that really meant, so I renamed it "__root__" because it
better reflects the meaning of those messages/templates.

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
2024-03-25 22:38:19 +01:00
Henrique Moody
66faefd695
Remove previous validation engine
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>
2024-03-25 12:28:25 +01:00
Henrique Moody
fe68eab37d
Allow to validate ArrayObject in key-related values
Because of that, I also updated some data providers to distinguish
between "values" and "types", similar to some of the rules we already
have.

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
2024-03-08 21:36:49 +01:00
Henrique Moody
238f2d506a
Update validation engine
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>
2024-02-22 16:54:44 +01:00
Henrique Moody
d25557cd72
Extract parameter processor into multiple classes
By extracting them into different processors, the code in the rendered
becomes much more straightforward, and the design makes it possible to
create processors easily. This change will allow users to customize that
behavior if they want to.

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
2024-02-22 01:56:22 +01:00
Henrique Moody
26e9fb9d59
Allow templates to have raw and translatable params
In some cases, a user would like to show the parameter just as it is,
and in other cases, they need to translate a specific parameter. This
change creates that capability by adding a template-style modifier to a
parameter in the template.

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
2024-02-14 18:08:13 +01:00