-
Notifications
You must be signed in to change notification settings - Fork 88
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
Add Locator to retrieve Handlers from a Container #66
Conversation
The DocBlock mentions in its example that the getHandlerForCommandMethod accepts an object (the Command) but in reality it accepts a string. I have altered the DocBlock to match this behaviour.
In this Commit I have added a Locator that is capable of retrieving a handler from a Container implementing the Container Interop standard / shared interface. The Locator's default behaviour is to append the suffix 'Handler' to the given Command Name (assuming the Command Name is an FQCN) and uses that to find a handler in the Container that is provided in the constructor. A secondary behaviour for the Locator is that you can pass a map with which you can map command names to specific identifiers with which a handler can be returned. This is especially convenient when you are using a container that operates based on identifiers and not class names. This specific locator depends on the `container-interop/container-interop` package but I have explicitly mentioned this in the suggest section of the composer because it is not a hard requirement for the usage of Tactician, only when you want to use this new Locator. Using unit tests and by manually performing tests using dummy data I have been able to verify that no errors occur due to omitting this dependency by default.
This looks great! Normally, we'd split the container-interop part out to a separate plugin package but since it's just interfaces (and this is an oft-requested feature), I think the soft dep is pretty justifiable. The one thing I think might be worth refactoring here is the behavior around command name. Technically, that ought to be in a So, if we split that behavior out into two different CommandNameExtractors, this would be perfect and useful to a broader audience; they'd just need to pick which one to use. If you're really counting on the fallback from one to another there, check out #55, we could do something similar there. |
I am sorry but I am having trouble understanding which behaviour surrounding the command name that you'd like to extract to a CommandNameExtractor. Do you mean that a CommandNameExtractor should also be used for a CommandHandlerName? To me the strategy determining the Command Handler's name is a responsibility of the Locator; or at least delegated by the Locator. This would mean that the constructor of the ContainerLoader would feature another dependency for the Command Handler Name Extractor. And do I get it right that you identify the following patterns that you'd like to have extracted into CommandNameExtractors:
The reason for the fallback behaviour is because I can imagine three scenarios:
Since these two differ only slightly I thought I'd combine them into one as it feels like overkill to separate them. Though if we were to use CommandHandlerNameExtractors as described above then this could be used as a strategy and there is no need for separate versions |
@rosstuck I have pushed a proof of concept based on your remarks; is this what you mean? A CommandNameExtractor however receives a Command object; meaning that these differ in what they accept. |
Also: if this is what you mean I will fix the tests; didn't bother right now (also due to time ;) ) |
In Tactician, we don't really have a separation between the CommandName and the HandlerName (there isn't really the concept of a HandlerName at all). The CommandNameExtractor just figures out what the identifier should be and the HandlerLocator returns the one for that identifier. So, in this case, we wouldn't have a CommandHandlerNameExtractor, we'd just use the CommandNameExtractor. Because there's no other real use case for CommandNameExtractors anyways. Anyways, that'd be the quick solution for getting this merged. :) On a future oriented note: That's the way it works now, since it lets folks change their naming strategy separate from their container, which was a real issue when we had the two coupled. I admit it's a bit suboptimal, this was a later refactoring and we probably need to address this better in the next release or 2.0. You can see the issues it's caused with Cascading Locators in #55. At this point, my thought is making the HandlerLocator itself implement container-interop (been pondering this for a while and would solve a few issues) or passing the Extractor as a parameter somehow so the two can evolve separately. If you've got any thoughts about that, lemme know. :) |
@rosstuck There is just one problem with the CommandNameExtractor: The rest of the application passes a Command object while the Locators pass a Command FQCN (hence: string). This means that the current implementation of the Locators cannot consume normal CommandNameExtractors |
Sorry, to clarify: the locators aren't meant to purely work based on FQCN. They're meant to just receive any string name. The extractors can return anything: Symfony service or tag name or the result of a In the original version, the CommandLocators just took the entire command object, then implemented their own location logic. But that caused coupling issues when people had different styles of service naming and then had to reimplement the entire Locator just to change that part. At the time, we also considered passing the Extractor as a dependency but for wiring together in frameworks, that meant either required constructor position (no go) or making it a parameter in the |
@rosstuck the problem is not de output or its meaning of the Locator; the problem is the input of the CommandNameExtractor's The CommandNameExtractor interface requires that the |
Indeed, I think we're in agreement but need to sync up a bit. I've got to start the work day here, maybe catch up tonight on IRC or Slack? 😸 |
I can be a bit busy this evening but I will definitely try to get a hold of you ;) |
@@ -15,7 +15,7 @@ | |||
* $inMemoryLocator->addHandler($myHandler, 'My\TaskAddedCommand'); | |||
* | |||
* // Returns $myHandler | |||
* $inMemoryLocator->getHandlerForCommand(new My\TaskAddedCommand()); | |||
* $inMemoryLocator->getHandlerForCommand('My\TaskAddedCommand'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be My\TaskAddedCommand::class
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an allowed notation; I have explicitly used the pre-php 5.5 notation because this library its minimum version is not 5.5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naw, Tactician is only >= 5.5 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Holy smokes! I have totally had that wrong in my head! :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final class ContainerLocator implements HandlerLocator | ||
{ | ||
/** @var ContainerInterface */ | ||
private $container; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency, should we turn this into a multi-line docblock, rather than single line comment
/**
* @var ContainerInterface
*/
?
Closing this before @localheinz gets carried away :) The container-interop work here is now fulfilled by the new version of https://github.com/thephpleague/tactician-container. The other part involving a command name extractor probably needs to be picked up separately. If this is something we'd like to carry forward with perhaps it's a good idea to restart this one from scratch sometime :) |
Ha, nice! Thanks for the heads-up! |
In this PR I have added a Locator that is capable of retrieving
a handler from a Container implementing the Container Interop
standard / shared interface.
The Locator's default behaviour is to append the suffix 'Handler' to
the given Command Name (assuming the Command Name is an FQCN)
and uses that to find a handler in the Container that is provided
in the constructor.
A secondary behaviour for the Locator is that you can pass a map with
which you can map command names to specific identifiers with which
a handler can be returned. This is especially convenient when you are
using a container that operates based on identifiers and not class
names.
This specific locator depends on the
container-interop/container-interop
package but I have explicitly mentioned this in the suggest section
of the composer because it is not a hard requirement for the usage of
Tactician, only when you want to use this new Locator.
Using unit tests and by manually performing tests using dummy data
I have been able to verify that no errors occur due to omitting this
dependency by default.