{"action":"created","issue":{"url":"https://api.github.com/repos/thephpleague/tactician/issues/10","labels_url":"https://api.github.com/repos/thephpleague/tactician/issues/10/labels{/name}","comments_url":"https://api.github.com/repos/thephpleague/tactician/issues/10/comments","events_url":"https://api.github.com/repos/thephpleague/tactician/issues/10/events","html_url":"https://github.com/thephpleague/tactician/pull/10","id":55521340,"number":10,"title":"Command Middleware","user":{"login":"rosstuck","id":146766,"avatar_url":"https://avatars.githubusercontent.com/u/146766?v=3","gravatar_id":"","url":"https://api.github.com/users/rosstuck","html_url":"https://github.com/rosstuck","followers_url":"https://api.github.com/users/rosstuck/followers","following_url":"https://api.github.com/users/rosstuck/following{/other_user}","gists_url":"https://api.github.com/users/rosstuck/gists{/gist_id}","starred_url":"https://api.github.com/users/rosstuck/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rosstuck/subscriptions","organizations_url":"https://api.github.com/users/rosstuck/orgs","repos_url":"https://api.github.com/users/rosstuck/repos","events_url":"https://api.github.com/users/rosstuck/events{/privacy}","received_events_url":"https://api.github.com/users/rosstuck/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":8,"created_at":"2015-01-26T18:51:25Z","updated_at":"2015-01-26T21:21:26Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/thephpleague/tactician/pulls/10","html_url":"https://github.com/thephpleague/tactician/pull/10","diff_url":"https://github.com/thephpleague/tactician/pull/10.diff","patch_url":"https://github.com/thephpleague/tactician/pull/10.patch"},"body":"So, Command Middleware is something we've shelved previously but I want to reopen the issue after some discussions at PHPBenelux. I've also updated the core package with a sample implementation in this PR.\r\n\r\nFor those of you not familar with it, Command Middleware is a Chain-of-Responsibility version of extension rather than Decorators. @matthiasnoback's SimpleBus was the first to use a CoR approach rather than Decorators, but it was originally mutable which is something I really didn't like. During some private discussions, I suggested a PSR-7 style middleware approach and sketched out how it could work. That ultimately made its way to SimpleBus 2.0 and I'm suggesting it here as well.\r\n\r\nThere are two main reasons to use this approach:\r\n- The big feature is that we can progmatically attach plugins. There's no more custom factories or bulky decorator chain setup in your DI container, we can provide a single consistent interface for attaching plugins. More below.\r\n- It means all plugins are on equal ground. @sagikazarmark and I had this discussion with the RemoteCommandBus and HandlerCommandBus, about which should be the end of their decorator chains or should they all pass through. Depending on how we solve an open point below, this could be resolved everywhere.\r\n\r\nWhat changed my mind? I implemented a logging and doctrine transaction decorator at PHPBenelux, but when I started assembling them in a simple script, it became extremely bulky. Also, while talking to different users, I realized the current model is....okay-ish...for some imperative DI containers (ZF2, Laravel) but is downright awful for declarative DI containers (like Symfony).\r\n\r\nMiddleware means you can attach them with a common interface but the next hurdle is making certain the middleware priority is correct. I wanted to avoid priority numbers ala ZF1 at all costs, so the other insight I had was that plugin extension can be done not with tags, but as a master app config that lists which services to load from the DI container. That's simply an array, which is easier to implement, easy for end users and has order. It also means that we're sideloading the dependencies, which might make it possible to have framework integration bits ship with optional, pre-made config for plugin packages (That all might be a bit of a Symfony specific detail but it's what I'm used to and it means that it works in both imperative and declarative DI containers).\r\n\r\nThe main changes are:\r\n\r\n- CommandBus: https://github.com/thephpleague/tactician/blob/8a8df0695fe7816d0e38b52698747c475b064c2c/src/CommandBus.php \r\n- Middleware: https://github.com/thephpleague/tactician/blob/8a8df0695fe7816d0e38b52698747c475b064c2c/src/Middleware.php\r\n- HandlerMiddleware: https://github.com/thephpleague/tactician/blob/8a8df0695fe7816d0e38b52698747c475b064c2c/src/HandlerMiddleware.php\r\n- LockingMiddleware: https://github.com/thephpleague/tactician/blob/8a8df0695fe7816d0e38b52698747c475b064c2c/src/LockingMiddleware.php\r\n\r\nOpen points:\r\n\r\n- Do we like the current implementation?\r\n- If no middleware breaks the chain, should we throw an \"UnhandledException\" or simply assume no-op? An exception might clarify bad configuration to users but it means some implementations are \"terminal\" again.\r\n- Right now, the next closures passed in don't require the Command be passed to them. We could make the Command a parameter (optional or mandatory) on these callables to allow Middleware to change or replace the Command given further down the chain. Should we allow that? It might also allow us to only generate the ExecutionChain once.\r\n- Should we use a \"callable\" typehint everywhere or are we better served with Closure (which can be relied upon now I think)?\r\n- Samples, docs and other packages need to be updated, I'd be glad to take the lead on it. Porting from decorator to Middleware is pretty trivial."},"comment":{"url":"https://api.github.com/repos/thephpleague/tactician/issues/comments/71540705","html_url":"https://github.com/thephpleague/tactician/pull/10#issuecomment-71540705","issue_url":"https://api.github.com/repos/thephpleague/tactician/issues/10","id":71540705,"user":{"login":"sagikazarmark","id":1226384,"avatar_url":"https://avatars.githubusercontent.com/u/1226384?v=3","gravatar_id":"","url":"https://api.github.com/users/sagikazarmark","html_url":"https://github.com/sagikazarmark","followers_url":"https://api.github.com/users/sagikazarmark/followers","following_url":"https://api.github.com/users/sagikazarmark/following{/other_user}","gists_url":"https://api.github.com/users/sagikazarmark/gists{/gist_id}","starred_url":"https://api.github.com/users/sagikazarmark/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sagikazarmark/subscriptions","organizations_url":"https://api.github.com/users/sagikazarmark/orgs","repos_url":"https://api.github.com/users/sagikazarmark/repos","events_url":"https://api.github.com/users/sagikazarmark/events{/privacy}","received_events_url":"https://api.github.com/users/sagikazarmark/received_events","type":"User","site_admin":false},"created_at":"2015-01-26T21:21:26Z","updated_at":"2015-01-26T21:21:26Z","body":"interface: CommandBus\r\nclass: [CommandBus]Stack?"}}