xp-forge / inject

Dependency injection for the XP Framework
0 stars 0 forks source link

Composable injection #17

Closed thekid closed 4 months ago

thekid commented 7 years ago

Basic pattern

trait Database {
  private $db;

  #[@$dsn: inject(name= 'db-dsn')]
  public function __construct($dsn) {
    $this->db= DriverManager::getConnection($dsn);
  }
}

#[@webservice]
class RestApi {
  use Database;

  #[@webmethod(verb= 'GET')]
  public function allVotes() {
     return $this->db->query('...');
  }
}

Problem

This works fine as long as we only want to inject one thing. If, for example, we'd also want to have a message queue injected, we couldn't simply create another trait like this; although I'd like to be able to write this:

class RestApi {
  use Database, MessageQueue, Templates;

  ...
}

With method injection, this would still be possible! What other method can we find here, though? /cc @mikey179

thekid commented 7 years ago

First implementation

The API class uses Database and Templates traits, as well as an Injection trait:

class Api {
  use Injection, Database, Templates;

  public function toString(): string {
    return nameof($this).'(db= '.$this->conn.', templates= '.$this->templates.')';
  }
}

trait Database {
  private $conn;

  #[@inject(name= 'db-dsn')]
  private function database(string $dsn): void {
    $this->conn= $dsn;
  }
}

trait Templates {
  private $templates;

  #[@inject(name= 'templates-path')]
  private function templates(string $templates): void {
    $this->templates= $templates;
  }
}

The Injection trait glues it all together:

use inject\Injector;

trait Injection {

  public function __construct(Injector $inject) {
    $type= typeof($this);
    foreach ($type->getTraits() as $trait) {
      foreach ($trait->getMethods() as $m) {
        $m->hasAnnotation('inject') && $this->{$m->getName()}(...$inject->args($m));
      }
    }
  }
}

Needs this small patch to work:

diff --git a/src/main/php/inject/Injector.class.php b/src/main/php/inject/Injector.class.php
index 0165f00..7694d6c 100755
--- a/src/main/php/inject/Injector.class.php
+++ b/src/main/php/inject/Injector.class.php
@@ -128,7 +128,7 @@ class Injector {
    * @return var
    * @throws inject.ProvisionException
    */
-  protected function args($routine, $named) {
+  public function args($routine, $named= []) {
     $args= [];
     foreach ($routine->getParameters() as $i => $param) {
       $name= $param->getName();
thekid commented 5 years ago

Maybe use Traits as name instead of Injection?

use inject\Traits;

class Api {
  use Traits, Database, Templates;

  public function toString(): string {
    return nameof($this).'(db= '.$this->conn.', templates= '.$this->templates.')';
  }
}