-
Notifications
You must be signed in to change notification settings - Fork 2
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
Flexible service dependencies #14
Comments
Thank you for the enjoyable writeup. You can imagine that I would much appreciate this change. And yes I also ran into the situation where I considered (or even ended up?) creating otherwise unneeded services just to be able to use the So apart from laying the groundwork for stacking more flexible definitions, this already has tangible benefits. I don't see a reason NOT to implement this, but I am of course biased. |
I see. Initially, I ithought you'd want to pass the value directly, and just decide that anything that's not a string gets treated verbatim. But this is even beter without big changes, and makes more use of existing implementations. I would love to see a PoC PR to tackle this! 🙏 |
I see it now. |
Use Case
Scenario: You need to create multiple services that are class instances.
Example class:
Class instance services can be easily created using the
Constructor
class. However, this class only accepts service IDs as dependencies, leading to a lot of boilerplate code:Each
Link
service needs 3 accompanying services to be declared, for thestyle
,url
andnewTab
dependencies. The above example omits styles to contrast them against the simpler case: when some dependencies (styles) have a valid reason for being declared as services, while others (URL and new-tab) are too simple to justify.It may be argued that declaring everything as a service should still be desired, as it leads to more flexibility by allowing the
url
andnewTab
services to be extended without needing to modify the correspondingLink
service. I would normally be inclined to agree with such an argument.But consider the simplistic nature of the above use case. It doesn't take much effort to imagine an application that isn't interested in this level of flexibility and would rather enjoy the brevity of not having to declare the services for the
newTab
dependency.Such an application would rather be able to pass the dependency values directly:
The above is currently not supported. The alternative is to use
Factory
instead ofConstructor
, which is still quite cumbersome for large lists:The Challenge
Allowing arbitrary values to be passed as dependencies is not trivial, as this raises the obvious challenge:
How does the underlying service helper class know if a string is a service ID or just a plain string?
You may consider doing a simple
$container->has()
check with the string. If it returnstrue
, it's a service ID. If it returnsfalse
, it's a value. But it could also be a typo in the service ID by the developer, or a misconfigured application or container. The implicit nature of this approach is not very ergonomic.The natural "OOP" response is to turn dependencies or values into an abstraction. There are two approaches to consider:
1. Abstracting Dependencies
This would involve instantiating some objects to represent dependencies. For instance:
This could dramatically increase the amount of boilerplate code since it's likely that dependencies will be more common than direct values. Of course, this can be shortened by providing a proxy function:
... but it's still not an ideal solution.
That said, it could lead to an interesting abstraction for how dependencies get resolved, by pushing the responsibility of the resolution to the
Dependency
itself. Imagine:There may be something interesting to consider here.
2. Abstracting Values
This would be similar to dependency abstraction, but instead of abstracting dependencies, we'd abstract values.
And used in a similar way:
The main obstacle here is that we already have a
Value
class in this package. But that may actually be more of a blessing than a curse. Perhaps we can reuse theValue
class for this purpose.But if we're going to do that, then we might as well open it up to any
Service
implementation. Or better yet, any callable.The Proposal
I propose a solution that is built around the latter option. Service helper classes would accept dependencies in one of two forms: a service, or an ID that resolves to one.
In my opinion, this substitutability is pretty cool and quite intuitive. But if you need further convincing, I've prepared a full list of advantages:
Value
service helper would serve 90% of inlining cases, but the other service helpers can be used in the same way.__invoke()
signature of the service helpers, maintaining compatibility with PSR-11 containers that are agnostic of this package.These are the changes that would need to be made:
string|callable
ResolveKeysCapableTrait::resolveDeps()
to resolve strings normally, and callables as factories.Thoughts?
The text was updated successfully, but these errors were encountered: