Skip to content

Instantly share code, notes, and snippets.

@AnrDaemon
Forked from GDmac/rollyourown.php
Created June 28, 2019 21:44
Show Gist options
  • Save AnrDaemon/4d1a7b7dd9d9355c3576a88826f1821b to your computer and use it in GitHub Desktop.
Save AnrDaemon/4d1a7b7dd9d9355c3576a88826f1821b to your computer and use it in GitHub Desktop.

Revisions

  1. @GDmac GDmac revised this gist Mar 27, 2019. 1 changed file with 16 additions and 1 deletion.
    17 changes: 16 additions & 1 deletion rollyourown.php
    Original file line number Diff line number Diff line change
    @@ -24,6 +24,7 @@ public function route(Command $cmd) {
    // For content based routing:
    case $cmd instanceof PaintPlumbus:
    if($cmd->isBlue()) {
    echo "it's blue";
    // do specific blue stuff
    } else {
    // ...
    @@ -137,4 +138,18 @@ 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);
  2. @mathiasverraes mathiasverraes created this gist May 30, 2018.
    140 changes: 140 additions & 0 deletions rollyourown.php
    Original file line number Diff line number Diff line change
    @@ -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) {}
    }