-
-
Notifications
You must be signed in to change notification settings - Fork 320
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
Extend and decorate definitions (add new items to arrays, inherit definitions, decorate objects or factories…) #214
Comments
This is a really cool idea. I have been wanting to take our plugin architecture this direction for some time now... Iiuc, these are all currently equivalent, assuming CachedProductRepository type hints ProductRepositoryInterface in its constructor. ProductRepositoryInterface::class => object(CachedProductRepository::class)
ProductRepositoryInterface::class => extend()->object(CachedProductRepository::class)
ProductRepositoryInterface::class => extend()->object(CachedProductRepository::class)->constructor(decorated()) Is that right? With the empty link option, this would just be...
That could work. I'd also see value in having more symmetry between extend/decorated. I.e. use decorator and decoratee. decorated is also just more explicit than empty link, so that's nice.
This also keeps that first key word as a noun (object, link, factory, decorator), instead of a verb (object, link, factory, extend??) so I like that symmetry. But if object already did this then maybe factory should just do this automatically too, assuming the same key is injected. You're right that it's really not obvious how this should be done... Should everything be a decorator automatically or should we make people get explicit? It seems like if you're injecting the same key that you're defining, then it's obvious your intent is to decorate, which would leave us with:
Do my ramblings spark any new thoughts? |
Hi @ewinslow thanks for the feedback!
Yes in the future v5 discussed here, that would be equivalent indeed. Thanks to your suggestions I see something more clear: there's a strong difference between extending an So:
Decorating
Yes good point! And taking the same thinking for return [
Service::class => DI\factory(function (Service $decorated) {
return CachedService($decorated);
}),
]; That could work without any particular syntax. But that wouldn't work if we can't type-hint… In that case maybe it's just a problem of #197 and we could have something similar to AngularJS: return [
'foo' => DI\factory(function ($decorated) {
return Bar($decorated);
}, [
'decorated' => DI\link('foo'),
]),
// Or maybe
'foo' => DI\factory(function ($decorated) {
return Bar($decorated);
}, DI\link('foo')),
]; So maybe problem solved without any syntax addition? ExtendingOne problem I see is that we want to add decoration and definition extension, but that already exist for class Foo {
public function __construct(Bar $bar, $string) …
}
return [
'Foo' => DI\object()
// I only have to define the non type-hinted parameter
->constructorParameter('string', 'Hello world'),
]; What I'm wondering is, if we add definition extension and decoration as a main feature with explicit syntax addition (e.g. adding items to arrays, etc.), should we make it also explicit for objects: that would break BC. return [
'Foo' => DI\object()
->extend()
->constructorParameter('string', 'Hello world'),
]; Without To me that's better because:
So do we want to break BC here? By the way, alternative name: Ideal worldLet's say we break BC and imagine the best solution possible, options are:
'log.handlers' => extend()->array()
->add(FileHandler::class),
FileHandler::class => extend()->object()
->constructorParameter('filename', 'app.log'), Nah, now that we have only Something like this looks much clearer: 'log.handlers' => DI\array()
->add(FileHandler::class),
FileHandler::class => object()
->extend()
->constructorParameter('filename', 'app.log'),
'foo' => DI\array('bar')
->add('hello'), Here it's clear that we take an other array and add an entry for Also, I really like how explicit So we are closing in on a solution :) But do we want to break BC? |
Couple more issue with decorators -- how would I register more than one decorator at a time? Library might want to provide several decorators that apps can mix/match. There's also the issue of control over the order (or do we say in principle that this shouldn't matter?) Finally, what if a later plugin wants to remove one of the decorators? Is that a bad approach or is there some use case for such a thing? Not totally clear to me... |
You are right, the way definitions exist right now you can only write a definition once in each file (since it's an indexed array. But I don't personally see that as a problem. Let's say a plugin wants to decorate twice the logger: LoggerInterface::class => DI\factory(function (LoggerInterface $logger) {
$logger = new Decorator1($logger);
$logger = new Decorator2($logger);
return $logger;
}), It would be possible only with factory, but it's such a rare use case that I think it's OK to not be able to do it with (by the way of course in my mind each plugin/module has a separate config file else everything falls apart)
Inside a config file, there is no order. Between config file, the order is the one which you add the config files. So in a plugin system I would register the config file of each plugin in the order they are registered in the plugin system (i.e. usually base/core plugins are registered first, then optional plugins and here the order is a problem for the plugin managing system…). So basically the order should be handled by the user of PHP-DI, not PHP-DI. By the way this was recently a problem in Piwik (matomo-org/matomo#6529) even though we do not use PHP-DI for plugins yet.
I don't see a use case too but maybe it exist. Anyway there is no standard way to decorate an object (constructor parameter, call a setter, …?) so that's why you have to write definitions manually. Same goes for removing a decorator, do it manually, e.g.: LoggerInterface::class => DI\factory(function (LoggerInterface $logger) {
return $logger->getDecorated();
}), But I doubt it's a real use case honestly. |
Damn forgot that |
- refactored definition sources to a simpler design - class definitions do not extend previous definition anymore - `DI\extend()` allows to create a new "class definition extension" that do extend the previous (or any other) definition - reworked how the cache is used
I'm closing this: all has been implemented except:
|
Modular systems (aka plugins/bundles/extensions) usually work like this: where there is a base config and modules can override or extend it.
Overriding a definition is supported (just set a definition for the same name). However extending is not.
We could extend several kind of definitions:
The big question is the syntax: how to make it simple and explicit?
The other question is performances… Should definitions be compiled to PHP code to guarantee minimum overhead? I guess that can be decided once we have a working solution depending on whether performance is a problem or not.
Symfony tags
Symfony's approach is to use tags: you add tag to a container entry. Then you write a compiler pass that collects all instances having a specific tag, and you do something with them.
Pros:
Cons:
Spring collections
For reference, Spring allows to define collections (list/map…).
How to extend a list in spring config?
Guice multibindings
Guice allows to define lists too, and let it possible for module (in a modular system) add items. They call that Multibindings.
By the way Guice has a "module" system that is pretty similar to what we want to achieve here (e.g. this article). A stripped down version (but understandable for PHP devs) can be found in Ray.Di which is inspired from Guice.
What follows is an attempt at a syntax. This is just a suggestion, I've been thinking about that for months now and there doesn't seem to be an obvious solution…
Arrays
Now that #207 is implemented, we can define collections/arrays (in v5):
To add entries in other config files:
We could go with a simpler
DI\add()
function:but maybe being consistent with what follows is better?
Factories
Note: with a factory, we can extend any other definitions (that's cool!). E.g. here we'll extend an object definition:
To extend or decorate the previous one:
Alternative syntax:
Objects
Note that currently for
DI\object()
it already extends previous definitions instead of overriding them. The reason for this is that we sometimes want to extend the Autowiring or Annotation definition (just set a parameter that is not type-hinted for example).To extend another definition explicitly:
Or with
DI\extend()
:or
DI\link()
without service ID could be used to reference the previous definition of the current service?The text was updated successfully, but these errors were encountered: