Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
We don't need no DIC libs / we don't need no deps control
<?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) {}
}
@prigazzi

This comment has been minimized.

Copy link

commented Jun 7, 2018

I do like the mix of a developer, PHP, and 18yo Whiskey.

@hollodotme

This comment has been minimized.

Copy link

commented Oct 15, 2018

Thanks for sharing.

@GDmac

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.