@@ -0,0 +1,140 @@
<?php
// Context: I'm trying to argue that DI (and DIC) are great, and DIC libs suck.
// Happy to be proven wrong!
final class Router {
private $ dependencies ;
public function __construct (Dependencies $ dependencies ) {
$ this ->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 ()) {
// 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 ) {}
}