-
Notifications
You must be signed in to change notification settings - Fork 64
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
Auto-wiring dependencies for setter injection #1
Comments
I'm not @rdlowrey but I'm personally against this behavior. Every time I have personally wanted a setter method for a dependency it been because of poor program design. My $0.02 |
@morrisonlevi Hey levi, I'm actually thinking controller-wise, if my url matches controller/action, and say my login controller requires a user service for the login action, and another service for a different action - why instantiate both dependencies when only one is used for each method? Is that poor program design by mapping url to controller / action? |
If it is a different action that requires different dependencies, then I would say it is bad design. The alternative option is to pass the dependency needed for just that action at method call time: $controller->action($id, $param, $extraDependencyForAction) I'd like to clarify that this is just based off my experience. I could be wrong here, but I wanted to voice my opinion on the matter. |
I generally prefer constructor injection over setter injection as well. However, I have in the past used Auryn in conjunction with a custom router class to (1) provision a "controller" class AND (2) subsequently provision the parameters for the controller's "action" method using a combination of arguments parsed from a URI and definitions for parameter typehints. While it's not the same thing as setter injection, it's not too far off. I think the better course of action may be to expand the class MyRouter {
private $injector;
private $reflStorage;
function __construct(Auryn\Injector $injector, Auryn\ReflectionStorage $reflStorage) {
$this->injector = $injector;
$this->reflStorage = $reflStorage;
}
function route($method, $uri) {
// ... do some routing to get $className, $methodName and $uriArgs parsed from $uri ... //
$controller = $this->injector->make($className);
$reflMethod = $this->reflStorage->getMethod($className, $methodName); // hypothetical, not implemented
$reflParams = $this->reflStorage->getMethodParams($reflMethod); // hypothetical, not implemented
$mergedArgs = [];
foreach ($reflParams as $reflParam) {
$paramName = $reflParam->name;
if (array_key_exists($paramName, $uriArgs)) {
$mergedArgs[$paramName] = $uriArgs[$paramName];
} elseif ($typehint = $this->reflStorage->getParamTypeHint($reflMethod, $reflParam)) {
$mergedArgs[$paramName] = $this->injector->make($typehint);
} elseif ($reflParam->isDefaultValueAvailable()) {
$mergedArgs[$paramName] = $reflParam->getDefaultValue();
} else {
$mergedArgs[$paramName] = NULL;
}
}
$reflMethod->invokeArgs($controller, $mergedArgs);
}
}
|
I know that setter injection is used for optional dependencies in the class - and I thought it was pretty common. Auryn already uses I was just hoping for it to also instantiate dependencies depending on the Running |
Note that you can implement something like the code above right now. You just can't use the reflection storage instance to cache the method and parameter typehints with the current code. You'd simply need to replace the two hypothetical calls to |
@J7mbo Is this still something you're interested in? I'm kicking around the idea of adding an class HelloDependency {
function saySomething() { return 'Hello'; }
}
class Greeting {
function greet(HelloDependency $hello, $name) {
echo $hello->saySomething() . ', ' . $name;
}
}
// Will echo "Hello, world"
$provider->execute(['Greeting', 'greet'], $args = [':name' => 'world']); This would accomplish what you're asking and would eliminate some steps when the ultimate goal is to actually do something with the objects you instantiate. |
@rdlowrey Definitely still interested in having this added: can the constructor injection still happen automatically for class-based dependencies and then, when running your |
Yeah that's the plan. Alternatively you could pass in a preexisting instance and only the relevant method's parameters would be provisioned: $provider->execute([$existingGreetingObj, 'greet'], $args = [':name', 'world']); It would also work for any other valid callable. For example: $myGreeter = function(HelloDependency $dep, $name) {
echo $dep->saySomething() . ', '. $name;
};
$provider->execute($myGreeter, $args = [':name' => 'world']); I'll put this on the menu to work on. I may get around to it today but we'll just see how things go. Regardless I'm likely to implement it soon. |
Looks awesome! Looking forward to it :) |
@J7mbo I've just committed the new $injector->execute(function(){});
$injector->execute([$objectInstance, 'methodName']);
$injector->execute('globalFunctionName');
$injector->execute('MyStaticClass::myStaticMethod');
$injector->execute(['MyStaticClass', 'myStaticMethod']);
$injector->execute(['MyChildStaticClass', 'parent::myStaticMethod']);
$injector->execute('ClassThatHasMagicInvoke');
$injector->execute($instanceOfClassThatHasMagicInvoke); Additionally, you can pass in the name of a class for a non-static method and the injector will automatically provision an instance of the class (subject to any definitions or shared instances already stored by the injector) before provisioning and invoking the specified method: class Dependency {}
class AnotherDependency {}
class Example {
function __construct(Dependency $dep){}
function myMethod(AnotherDependency $adep, $arg) {
return $arg;
}
}
$injector = new Auryn\Provider;
var_dump($injector->execute(['Example', 'myMethod'], $args = [':arg' => 42]));
// outputs: int(42) In conclusion, please test this and let me know if you have any questions/comments/feedback. It's very well unit-tested already so it should be bug-free but you never know. If everything seems cool in a couple of days I'll go ahead and tag a new release. |
Woops -- looks like I forgot to add support for classes with a magic |
I've been playing around with the new code this evening, and so far it's totally brilliant! Can't fault it, method injection for Controller / Action works perfectly. :D |
Cool. Thanks for the feedback. And thanks or the initial suggestion -- it took me awhile to get over my initial misgivings but I think it's a nice feature at this point. |
This is really cool. That is all. :) |
@ascii-soup Thanks; hopefully people find the feature useful. |
Just stumbled upon this problem here: Beforehand I had an instance of a controller a
and called it like
That worked great (I could pass any number of any params as Now I tried to make that possible using So how may I pass all arguments plus injecting the ones typehinted? I think that way controller method would look like this:
That looks possible only with the 'raw named parameter', but not unnamed. Forcing all controller actions to use named params like $arg1, $arg2 etc. is definitely not worth it. I hope you understand my issue, maybe it's a poor design, but I don't think this is the case |
Are the |
Exactly, so they're not constant, there may be one, more or none |
I see that I may do that like (IIRC) it was in codeigniter using something like |
Get your
This is how it works for my in Symfony, but you can customise for yourself. See how I'm making the controller, then executing the action with the given
|
@J7mbo, thank you, works great, the only downside is that I should name the arguments the same way as they're named in |
As the title explains, would it be possible to include this capability in here, so that when Auryn performs it's reflection to read constructor signatures, could the reflection also be performed for methods within the class?
Obviously this would increase overheard significantly, but if everything's cached - that shouldn't be an issue. I'm going to take a look at the source over the course of this week but, what are your opinions on this @rdlowrey?
The text was updated successfully, but these errors were encountered: