Closed far-blue closed 5 years ago
If we want to override the default $di->params values for a specific lazy instance, we can pass a $params array as the second argument to lazyNew() to merge with the default values.
I'm finding it a little hard to follow the single letter meta variables, but here's an example of what I think you're saying:
<?php
// @codingStandardsIgnoreFile
// Default params
$di->params[A::class] = ['B' => $di->lazyNew(B::class)];
$di->params[B::class] = ['C' => $di->lazyNew(C::class)];
$di->params[C::class] = ['exchangeName' => 'foo'];
// Overridden for D::class
$c_for_d = $di->lazyNew(C::class, ['exchangeName' => 'bar']);
$b_for_d = $di->lazyNew(B::class, ['C' => $c_for_d]);
$di->params[D:class] = ['B' => $b_for_d];
Hello :)
Yes, I'm aware of the overriding but it means having to explicitly define all the dependencies down a chain which can get somewhat cumbersome - esp. when there's no sensible default value.
We ended up doing something like:
<?php
$di->params[A::class] = [
'B' => $di->lazyNew(
B::class,
[
'C' => $di->lazyNew(
C::class,
['exchangeName' => 'foo']
)
]
)
];
$di->params[D::class] = [
'B' => $di->lazyNew(
B::class,
[
'C' => $di->lazyNew(
C::class,
['exchangeName' => 'bar']
)
]
)
];
I was just thinking something like this might be neater:
<?php
$di->percolatedParams[A::class] = ['exchangeName' => 'foo'];
$di->percolatedParams[D::class] = ['exchangeName' => 'bar'];
Personally, I would prefer the explicit definition.
I'm not even sure how you're suggesting this new "feature" would work. Any dependency of A
that just happens to have a parameter named exchangeName
would get the value 'foo'?? That seems totally nuts to me.
The hyper abstracted example makes it hard to understand the specifics and suggest another solution. Other alternatives I would think would be to use some kind of creation pattern, like a factory or something that could encapsulate this conditional logic. Or maybe the B
that you pass to D
could actually be X extends B
or something like that.
Someone else should weigh in, but it sounds a little fishy to me.
I can understand if you aren't keen on my suggestion of implementation but basically I'm suggesting the equiv. of this: https://laravel.com/docs/5.5/container#contextual-binding
I had a class that handled publishing data to an exchange and I wanted to use 2 instances of this class in different places in my code but in each place I wanted to provide an 'exchange' object instance that was instantiated with a different exchange name string.
My first inclination is to create two different named services, with the different exchange strings.
Then inject the one named service in the one class, and the other named service in the other class.
EDIT:
On reading further, note that params are "percolated" -- that is, child class params are "inherited" from their parent class definitions. Perhaps that's what you're getting at?
I'm not sure there's a difference in the resulting code based on whether a named service or an instance with different parameters is used, really. Named services are a little clearer and more self-documenting in a way but you still have this repeated chain of explicit dependencies. Obviously it's just as possible to have an N-level stack as it is the example 3-level stack.
I'm not saying this is an every-day occurrence but we've hit it twice in 2 apps as we've started transitioning to developing around a Aura.Di.
I suppose I'd have to see the specific code in question; it's difficult for me to "see" the problem in the abstracted example above.
I came up with this after attempting to modify the Resolver. I haven't tested this when using the AutoResolver.
https://gist.github.com/dlundgren/5c2fd8670cf6334a1b56ea607739463b
@dlundgren that looks great and exactly the kind of behaviour I was interested in :)
@frederikbosch you may want to consider this issue which contains a solution by @dlundgren for v4 before we go for alpha or beta release.
@harikt Thought about this. The only problem I have is that the syntax and methodology is completely different compared to the rest of the library. I would rather see something like the example below. Then the user will remain calling $di
rather than the $resolver
directly which is at the moment hidden from the outside caller.
$di->params[D::class]['param'] = $di->lazyNew(B::class)->withContext(C::class, ['name' => 'second']);
@frederikbosch I agree with your points. Was mentioning, this issues needs to be addressed if possible.
The only reason I used the resolver was because it was the quickest way for such a feature to work in v3. I also didn't think it was the best way to do it, which is why I never submitted a PR.
In PR #181 you can see my suggestion how to solve this.
Fixed by #181, which is merged since today.
Laravel's container supports what they call "Contextual Binding" where you can specify different values for constructor parameter resolution on an instance depending on where the instance is being used.
I recently ran into a need for this when writing some RabbitMQ code. I had a class that handled publishing data to an exchange and I wanted to use 2 instances of this class in different places in my code but in each place I wanted to provide an 'exchange' object instance that was instantiated with a different exchange name string.
If there is no similar functionality already, I thought a possibly easy to use generic approach would be the ability to set named keys on a specified class, like we do for parameter defining, but which have the extra feature that they 'percolate down' until they are consumed.
For instance: class
A
needs an instance of classB
and this classB
then needs an instance of classC
. The constructor for classC
has a constructor parameter calledexchangeName
. You could defined classA
as having a key ofexchangeName
with a value of foo and when an instance ofA
is created, the instance of classC
receives the value of foo on it's constructor for the parameterexchangeName
.This way, if both class
A
and classD
need instances of classB
with different values ofexchangeName
for classC
this is easy to setup.