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.
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.
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.
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.
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.
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.
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.
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
Renamed from tests/unit/Message/StandardRendererTest.php (Browse further)