Closed ndench closed 1 year ago
Interesting use-case and solution! I'm all for a PR.
Add a flag to the existing lazy value object or new object?
A new object means we either have to make LazyValue
non-final, or copy paste it's internals.
But a bool flag on the LazyValue
is not very readable IMO.
$owner = new LazyValue(fn () => UserFactory::createOne(), true);
I'd probably lean towards making LazyValue
non-final. This would then allow users to extend it themselves if they have other use-cases they'd like to handle.
However, I'm unsure if it was made final for a specific reason originally?
what about using a named constructor?
$owner = LazyValue::singleValue(fn () => UserFactory::createOne()); // or maybe a better name 😅
Perfect! Thanks for the idea @nikophil! I'll get a PR up soon.
If we are trying to allow other projects to implement their own, what about using an empty interface and replace $value instaceof self
checks with $value instanceof LazyValueInterface
. Would look something like the following:
interface LazyValueInterface { }
final class LazyValue implements LazyValueInterface
{
...
public function __invoke(): mixed
{
$value = ($this->factory)();
if ($value instanceof LazyValueInterface) {
return ($value)();
}
if (\is_array($value)) {
return self::normalizeArray($value);
}
return $value;
}
/**
* @internal
*/
public static function normalizeArray(array $value): array
{
\array_walk_recursive($value, static function(mixed &$v): void {
if ($v instanceof LazyValueInterface) {
$v = $v();
}
});
return $value;
}
}
Then projects can implement something invokable for their own needs
final class MyLazyValue implements LazyValueInterface
{
public function __invoke(): mixed
{
}
}
indeed, if we decide to allow users to define custom lazy values, we'd introduce an interface, but I'm really wondering if this would actually be used? I mean: besides the current point, do you see any other valid use case?
What about using the term memoize? A bit computer science-y but perfectly describes the intent.
I like it, it express well what it does, and it will be documented anyway
Agreed LazyValue::memoize()
it is.
RE
I mean: besides the current point, do you see any other valid use case?
Only other use case I can think of would be a more complicate construction than creating a single entity. However, the $factory
is a callable, which should allow you to be as complicated as you like. You could even use a service with dependency injection and an __invoke()
method defined.
So I don't think it's necessary to allow extension at this point. It can always be added later if a use-case arises.
First of all, thanks for the great bundle! It's been a massive improvement to our DX over the last few months ♥️
I have an interesting use case with regards to my entity model which has multiple relations between entities which need to be setup for the entity to be "valid".
For example, say we have a
Project
which has both a list ofTasks
and a list ofUsers
. ATask
has a singleUser
as the owner, but that owner must exist in the list ofProject::users
for thatTask
:At the moment, my
TaskFactory
looks like this:However, the UserFactory will actually create two different instances of a User, when in fact, I need them to be the same instance. There are currently two ways to work around this (that I know of):
UserFactory::createOne()
ingetDefaults()
, however this is not recommendedThat being said, I think there is a very simple feature we could add to enable this use case by building off the existing LazyValue.
Which the
TaskFactory
would use like this:I'm more than happy to submit a PR for this if you agree it's valuable. I'd also like some feedback on this approach (it does require either making
LazyValue
non-final, or duplicating it). Or potentially there's another approach I haven't considered?