dependencies = $dependencies; // You might say that this is Service Locator, but it's not. This router is toplevel, // and toplevel must have access to dependencies. After that it can all just bubble nicely using proper DI. } public function route(Command $cmd) { switch(true) { case $cmd instanceof RubPlumbus: $this->dependencies->PlumbusRubber()->handle($cmd); break; case $cmd instanceof SpitOnPlumbus: // $this->dependencies->SomeOtherService()->handle($cmd); //.. etc // So what it's just a dumb list of case and instanceof instead of clever autowiring. This takes no time to // write and no time to understand and nobody will ever need to stackoverflow it to get it. // This is all lazy by default, Dependencies only makes stuff we ask for and we only ask for stuff when we need // it. // For content based routing: case $cmd instanceof PaintPlumbus: if($cmd->isBlue()) { echo "it's blue"; // do specific blue stuff } else { // ... } break; } } } abstract class Dependencies { private $environment; function __construct (Environment $environment) { // No dependency on a global mutable env, this is a value object. $this->environment = $environment; } public function PlumbusRubber (): PlumbusRubber { // Public, because this service is toplevel return new PlumbusRubber($this->PlumbusRepository()); } // Only the methods above this line are public, because I expose as few of them as possible to the outside. I'm not // just letting you get any dep from anywhere. Symfony has this feature, but it's public by default there, which // annoys me. I'm easily annoyed by public things that shouldn't be public. protected function PlumbusRepository (): PlumbusRepository { return $this->MySQLPlumbusRepository(); } protected function MySQLPlumbusRepository (): MySQLPlumbusRepository { // NOTE: Private! Never exposed outside return new MySQLPlumbusRepository($this->DBConnection()); } protected function DBConnection (): DBConnection { // The others deps didn't sharing because they're stateless and fast and often only used once in a request. // Here there is some side effect on instantiation so it makes sense to reuse $environment = $this->environment; return $this->share( __METHOD__, // used as key function () use ($environment) { return new DBConnection( $environment->db_host(), $environment->db_username() /* ... */ ); } ); } private $shared = []; protected function share ($key, callable $factory) { // I sure wish PHP had generics :`-( if ( ! array_key_exists($key, $this->shared)) { $this->shared[$key] = $factory(); } return $this->shared[$key]; } } final class ProductionDependencies extends Dependencies { } final class StagingDependencies extends Dependencies { // A typical use case would be to have a mock Mailer so we can't send email from tests } final class TestingDependencies extends Dependencies { // Overriding stuff we want to differently in unit tests protected function PlumbusRepository (): PlumbusRepository { return $this->InMemoryPlumbusRepository(); // Or use your favourite mocking lib if you must } } // Benefits: // * No XML! No YAML! No frameworks! Just plain old POPOs and tautologies! // * No black magic autowiring // * All in glorious typehinting // * I can even write unit tests for the Dependencies class itself. // * There is no naming convention you have to learn like "app.thing.some_underscord_thing". Methods are named // after the interface or the class of the thing they return. Such easy, much wow. // * I don't need IDE plugins to navigate the xml shit and detect missing services because it's all POPO! // * If you think any of this involves work: it's less work than any DIC lib I've used, because it's the same amount // of information as you'd have to write anyway with whatever configuration format they use. The only exception is // autowiring, but you'll pay that back with itnerest in debugging time anyway. // ignore below, just making all the GLORIOUS TYPEHINTING WORK FOR ME LIKE BOSS interface Command {}; class RubPlumbus implements Command {} class SpitOnPlumbus implements Command {} class PaintPlumbus implements Command { public function isBlue(): bool {return true;} } class PlumbusRubber { function __construct (PlumbusRepository $plumbusRepository) {} public function handle ($cmd) {} }; interface Environment { public function db_host () : string; public function db_username () : string; } interface PlumbusRepository {} final class MySQLPlumbusRepository implements PlumbusRepository { function __construct (DBConnection $dbConnection) {} } class DBConnection { function __construct (string $host, string $username) {} } // ====================================================== // Run $environment = new class implements Environment { public function db_host () : string { return 'foo'; } public function db_username () : string { return 'bar'; } }; $dependencies = new class($environment) extends Dependencies { }; // $command = new RubPlumbus(); $command = new PaintPlumbus(); $router = new Router($dependencies); $router->route($command);