-
Notifications
You must be signed in to change notification settings - Fork 2
Add option attribute #59
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: main
Are you sure you want to change the base?
Conversation
6b3e0c1 to
66589c4
Compare
c4c1d1f to
9f95631
Compare
|
Just added another commit, introducing a cache for Oh, performance is also increased by 66.67% for a single usage of |
It's about 40% faster, measured using phpbench.
5aa59dc to
a844252
Compare
lippserd
left a comment
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.
I would like to propose some major changes for discussion, as this functionality is generally nothing new, but becomes much more elegant with attributes. Let me first give a general description of how I think the feature should be described, using well-known and established terminology for this functionality, namely hydration:
Implementation of attribute-based object hydration using PHP 8's native attributes for mapping key-value data to object properties and methods. This approach uses the Reflection API in combination with attributes to create a clean, type-safe, and maintainable data mapping system.
Let's now proceed with the classes necessary to provide such functionality, where separation of concerns is crucial. In my opinion, an attribute should not contain any "business logic" but only denote additional "configurations". In line with the topic, I would introduce the components as follows:
AttributeHydrator::hydrate($object, $values), which performs hydration and removes the business logic from Option and the introduced interfaces. As a bonus, this makes it clear that
- It uses PHP 8+ attributes for configuration
- It performs hydration
- It differs from other hydration approaches
Hydrate for the attribute class to
clearly indicate its purpose, i.e., "this property/method is involved in hydration", and its relationship to the AttributeHydrator.
As for the current implementation, I wonder why we should support multiple names and pass values by reference. Do you have a specific use case in mind for this? Why is it necessary? To me, it seems like a "dangerous" combination.
I would also consider introducing mapping strategies for properties/methods to array/data keys, e.g., converting KebapCase/camelCase to snake_case.
When switching to this proposal, most functionality will remain unchanged and are simply transferred to other "owners", where they can then be easily expanded in the future, for example, by enforcing type safety, converting values, etc.
|
If, by business logic, you mean that I don't have a problem with making the function a static method, that indeed won't change anything. But I don't see how removing the business logic from Yes, I have not described the intention here, but Icinga/ipl-html#157 should cover that. (Except required) But anyway. Here's a breakdown:
Stripping down Who are the other owners you speak of? Where do they fit in your proposal? How will they be able to do what |
No, I meant that the attribute
Separation of concerns is a must, because otherwise I cannot properly extend, adapt, or reuse the logic, as it would be set in stone. Sure, the code could be rewritten so that
Exactly that would be the goal: the attribute describes the metadata and another class uses it. Just compare this approach with the current implementation, where code cluttering interfaces are introduced and the logic is placed in a function that cannot be extended.
I meant that the code in this PR remains largely unchanged and is simply transferred to other classes, i.e., to the actual hydrator. However, since you are asking for examples of how this fits into the big picture, consider the proposed |
Okay, let's play that through for a moment:
So, to summarize: Instead of:
We'd have:
or alternatively:
I left out the actual implementation of the mappers. That can be dealt with after this. |
|
I think we should discuss this in person. My impression is that you are thinking the situation is more complicated than it actually is: <?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)]
readonly class Hydrate
{
public function __construct(
public ?string $name = null,
public bool $required = false
) {}
}
interface MapperFunc
{
public function __invoke($tbd): string;
}
/**
* @template T of object
*/
class AttributeHydrator
{
/**
* @param T|class-string<T> $target
* @param class-string<Hydrate> $attribute
* @param ?MapperFunc $nameMapper Mapper for name if Hydrate::$name is null.
* Defaults to SomeDefaultMapper (camelCase to snake_case), pass NoopMapper to explicity disable mapping.
* Alternatively, let us just require a name in Hydrate. Explicit is better than implicit.
*/
public function __construct(
protected readonly object|string $target,
protected readonly string $attribute = Hydrate::class,
protected readonly ?MapperFunc $mapper = null
)
{
if (! is_a($attribute, Hydrate::class, true)) {
throw new ValueError(...);
}
// Build cache if necessary. When building the cache,
// assert that the same name is not used twice.
// Account mapper in cache.
}
/**
* @return T
*/
public function hydrate(iterable $values): object
{
// If $target is a string, create object dynamically via reflection.
// If $target is an object, use it directly.
}
} |
|
Maybe, but your pseudo code isn't too far away from what I've described.
But since
should work. Or at least in which way it'd be different than the current proposal. I don't see a need for a custom mapper implementation as well anymore, That's what I've tried to avoid. If we'd implement the |
My first prototype contained everything in class
Option. Since there may be plans to make more extensive use of attributes, I thought how to generalize their resolution somewhat. Which is why the new interfaces plus the functionresolve_attributeare now proposed here as well.I did not implement the remaining targets (Classes, Parameters, Constants) as they're not needed right now, but the function can be easily extended in the future.