Add object creation filters, and provide one that injects annotated setter dependencies.
authorChuck Hagenbuch <chuck@horde.org>
Mon, 26 Apr 2010 00:26:40 +0000 (20:26 -0400)
committerChuck Hagenbuch <chuck@horde.org>
Mon, 26 Apr 2010 00:27:13 +0000 (20:27 -0400)
framework/Injector/lib/Horde/Injector.php
framework/Injector/lib/Horde/Injector/Binder/Implementation.php
framework/Injector/lib/Horde/Injector/Filter.php [new file with mode: 0644]
framework/Injector/lib/Horde/Injector/Filter/AnnotatedSetterInjector.php [new file with mode: 0644]
framework/Injector/package.xml
framework/Injector/test/Horde/Injector/Filter/AnnotatedSetterInjectorTest.php [new file with mode: 0644]
framework/Injector/test/Horde/Injector/InjectorTest.php

index c37bd27..4f5fce7 100644 (file)
@@ -16,6 +16,7 @@ class Horde_Injector implements Horde_Injector_Scope
 {
     private $_parentInjector;
     private $_bindings = array();
+    private $_filters = array();
     private $_instances;
 
     /**
@@ -197,7 +198,9 @@ class Horde_Injector implements Horde_Injector_Scope
      */
     public function createInstance($interface)
     {
-        return $this->getBinder($interface)->create($this);
+        $instance = $this->getBinder($interface)->create($this);
+        $this->runFilters($instance);
+        return $instance;
     }
 
     /**
@@ -235,4 +238,53 @@ class Horde_Injector implements Horde_Injector_Scope
         return $this->_instances[$interface];
     }
 
+    /**
+     */
+    public function getMethodDependencies(ReflectionMethod $method)
+    {
+        $dependencies = array();
+
+        try {
+            foreach ($method->getParameters() as $parameter) {
+                $dependencies[] = $this->_getParameterDependency($parameter);
+            }
+        } catch (Exception $e) {
+            throw new Horde_Injector_Exception("$method has unfulfilled dependencies ($parameter)", 0, $e);
+        }
+
+        return $dependencies;
+    }
+
+    /**
+     */
+    private function _getParameterDependency(ReflectionParameter $parameter)
+    {
+        if ($parameter->getClass()) {
+            return $this->getInstance($parameter->getClass()->getName());
+        } elseif ($parameter->isOptional()) {
+            return $parameter->getDefaultValue();
+        }
+
+        throw new Horde_Injector_Exception("Untyped parameter \$" . $parameter->getName() . "can't be fulfilled");
+    }
+
+    /**
+     * Add an object creation filter
+     */
+    public function addFilter(Horde_Injector_Filter $filter)
+    {
+        $this->_filters[] = $filter;
+    }
+
+    /**
+     * Run object creation filters on a new object
+     *
+     * @param object $instance  The new instance to filter.
+     */
+    public function runFilters($instance)
+    {
+        foreach ($this->_filters as $filter) {
+            $filter->filter($this, $instance);
+        }
+    }
 }
index 66928a3..a5c9e59 100644 (file)
@@ -79,56 +79,24 @@ class Horde_Injector_Binder_Implementation implements Horde_Injector_Binder
     /**
      * TODO
      */
-    private function _getInstance(Horde_Injector $injector,
-                                  ReflectionClass $class)
+    private function _getInstance(Horde_Injector $injector, ReflectionClass $class)
     {
         return $class->getConstructor()
-            ? $class->newInstanceArgs($this->_getMethodDependencies($injector, $class->getConstructor()))
+            ? $class->newInstanceArgs($injector->getMethodDependencies($class->getConstructor()))
             : $class->newInstance();
     }
 
     /**
      * TODO
      */
-    private function _getMethodDependencies(Horde_Injector $injector,
-                                            ReflectionMethod $method)
-    {
-        $dependencies = array();
-
-        foreach ($method->getParameters() as $parameter) {
-            $dependencies[] = $this->_getParameterDependency($injector, $parameter);
-        }
-
-        return $dependencies;
-    }
-
-    /**
-     * TODO
-     */
-    private function _getParameterDependency(Horde_Injector $injector,
-                                             ReflectionParameter $parameter)
-    {
-        if ($parameter->getClass()) {
-            return $injector->getInstance($parameter->getClass()->getName());
-        } elseif ($parameter->isOptional()) {
-            return $parameter->getDefaultValue();
-        }
-
-        throw new Horde_Injector_Exception('Unable to instantiate class "' . $this->_implementation . '" because a value could not be determined untyped parameter "$' . $parameter->getName() . '"');
-    }
-
-    /**
-     * TODO
-     */
     private function _callSetters(Horde_Injector $injector, $instance)
     {
         foreach ($this->_setters as $setter) {
             $reflectionMethod = new ReflectionMethod($instance, $setter);
             $reflectionMethod->invokeArgs(
                 $instance,
-                $this->_getMethodDependencies($injector, $reflectionMethod)
+                $injector->getMethodDependencies($reflectionMethod)
             );
         }
     }
-
 }
diff --git a/framework/Injector/lib/Horde/Injector/Filter.php b/framework/Injector/lib/Horde/Injector/Filter.php
new file mode 100644 (file)
index 0000000..267d483
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Interface for object post-creation filters.
+ *
+ * @author   Bob Mckee <bmckee@bywires.com>
+ * @author   James Pepin <james@jamespepin.com>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @category Horde
+ * @package  Horde_Injector
+ */
+interface Horde_Injector_Filter
+{
+    /**
+     * @param Horde_Injector $injector  The active Horde_Injector
+     * @param object $instance          The new instance to filter
+     *
+     * @return void
+     */
+    public function filter(Horde_Injector $injector, $instance);
+}
diff --git a/framework/Injector/lib/Horde/Injector/Filter/AnnotatedSetterInjector.php b/framework/Injector/lib/Horde/Injector/Filter/AnnotatedSetterInjector.php
new file mode 100644 (file)
index 0000000..4abb760
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Filter that finds variables marked with @inject and injects them into an
+ * object.
+ *
+ * @author   Bob Mckee <bmckee@bywires.com>
+ * @author   James Pepin <james@jamespepin.com>
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @category Horde
+ * @package  Horde_Injector
+ */
+class Horde_Injector_Filter_AnnotatedSetterInjector implements Horde_Injector_Filter
+{
+    /**
+     * @var Horde_Injector_DependencyFinder
+     */
+    protected $_dependencyFinder;
+
+    /**
+     * Inspect the object's class docblock for @inject annotations, and fill
+     * those objects in through their setter methods.
+     *
+     * @param object $instance  The object to do setter injection on.
+     */
+    public function filter(Horde_Injector $injector, $instance)
+    {
+        $reflectionClass = new ReflectionClass($instance);
+        $setters = $this->_findSetters($reflectionClass);
+        $this->_callSetters($injector, $instance, $setters);
+    }
+
+    /**
+     * Find annotated setters in the class docblock
+     *
+     * @param ReflectionClass $reflectionClass
+     *
+     * @return array
+     */
+    private function _findSetters(ReflectionClass $reflectionClass)
+    {
+        $setters = array();
+        $docBlock = $reflectionClass->getDocComment();
+        if ($docBlock) {
+            if (preg_match_all('/@inject (\w+)/', $docBlock, $matches)) {
+                foreach ($matches[1] as $setter) {
+                    $setters[] = $setter;
+                }
+            }
+        }
+
+        return $setters;
+    }
+
+    /**
+     * TODO
+     */
+    private function _callSetters(Horde_Injector $injector, $instance, $setters)
+    {
+        foreach ($setters as $setter) {
+            $reflectionMethod = new ReflectionMethod($instance, $setter);
+            $reflectionMethod->invokeArgs(
+                $instance,
+                $injector->getMethodDependencies($reflectionMethod)
+            );
+        }
+    }
+}
index 7b77ebc..5fc1d67 100644 (file)
        <file name="Factory.php" role="php" />
        <file name="Implementation.php" role="php" />
       </dir> <!-- /lib/Horde/Injector/Binder -->
+      <dir name="Filter">
+       <file name="AnnotatedSetterInjector.php" role="php" />
+      </dir> <!-- /lib/Horde/Injector/Filter -->
       <file name="Binder.php" role="php" />
       <file name="Exception.php" role="php" />
+      <file name="Filter.php" role="php" />
       <file name="Scope.php" role="php" />
       <file name="TopLevel.php" role="php" />
      </dir> <!-- /lib/Horde/Injector -->
    <install as="Horde/Injector.php" name="lib/Horde/Injector.php" />
    <install as="Horde/Injector/Binder.php" name="lib/Horde/Injector/Binder.php" />
    <install as="Horde/Injector/Exception.php" name="lib/Horde/Injector/Exception.php" />
+   <install as="Horde/Injector/Filter.php" name="lib/Horde/Injector/Filter.php" />
    <install as="Horde/Injector/Scope.php" name="lib/Horde/Injector/Scope.php" />
    <install as="Horde/Injector/TopLevel.php" name="lib/Horde/Injector/TopLevel.php" />
    <install as="Horde/Injector/Binder/Closure.php" name="lib/Horde/Injector/Binder/Closure.php" />
    <install as="Horde/Injector/Binder/Factory.php" name="lib/Horde/Injector/Binder/Factory.php" />
    <install as="Horde/Injector/Binder/Implementation.php" name="lib/Horde/Injector/Binder/Implementation.php" />
+   <install as="Horde/Injector/Filter/AnnotatedSetterInjector.php" name="lib/Horde/Injector/Filter/AnnotatedSetterInjector.php" />
    <install as="Horde/Injector/AllTests.php" name="test/Horde/Injector/AllTests.php" />
    <install as="Horde/Injector/BinderTest.php" name="test/Horde/Injector/BinderTest.php" />
    <install as="Horde/Injector/InjectorTest.php" name="test/Horde/Injector/InjectorTest.php" />
diff --git a/framework/Injector/test/Horde/Injector/Filter/AnnotatedSetterInjectorTest.php b/framework/Injector/test/Horde/Injector/Filter/AnnotatedSetterInjectorTest.php
new file mode 100644 (file)
index 0000000..aaaa859
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+class Horde_Injector_Filter_AnnotatedSetterInjectorTest extends PHPUnit_Framework_TestCase
+{
+    public function testAnnotatedSettersAreInjected()
+    {
+        $injector = new Horde_Injector(new Horde_Injector_TopLevel());
+        $foo1 = $injector->createInstance('Horde_Injector_TestFoo');
+        $this->assertNull($foo1->bar);
+
+        $injector->addFilter(new Horde_Injector_Filter_AnnotatedSetterInjector());
+        $foo2 = $injector->createInstance('Horde_Injector_TestFoo');
+        $this->assertType('Horde_Injector_TestBar', $foo2->bar);
+    }
+
+    public function testAnnotatedSettersAreThereWhenCallingGetInstanceAgain()
+    {
+        $injector = new Horde_Injector(new Horde_Injector_TopLevel());
+        $injector->addFilter(new Horde_Injector_Filter_AnnotatedSetterInjector());
+        $foo1 = $injector->getInstance('Horde_Injector_TestFoo');
+
+        $foo2 = $injector->getInstance('Horde_Injector_TestFoo');
+        $this->assertType('Horde_Injector_TestBar', $foo2->bar);
+    }
+}
+
+/**
+ * Used by the preceding tests
+ *
+ * @inject setBar
+ */
+class Horde_Injector_TestFoo
+{
+    public $bar;
+
+    public function setBar(Horde_Injector_TestBar $bar)
+    {
+        $this->bar = $bar;
+    }
+}
+
+class Horde_Injector_TestBar
+{
+}
index 7c0a07c..3ec68fb 100644 (file)
@@ -246,7 +246,7 @@ class Horde_Injector_InjectorTest extends PHPUnit_Framework_TestCase
 }
 
 /**
- * Used by preceeding tests!!!
+ * Used by preceding tests
  */
 class Horde_Injector_Binder_Mock implements Horde_Injector_Binder
 {