Add closure Binder
authorChuck Hagenbuch <chuck@horde.org>
Sun, 14 Mar 2010 18:53:42 +0000 (14:53 -0400)
committerChuck Hagenbuch <chuck@horde.org>
Sun, 14 Mar 2010 18:53:42 +0000 (14:53 -0400)
framework/Injector/lib/Horde/Injector/Binder/Closure.php [new file with mode: 0644]
framework/Injector/package.xml
framework/Injector/test/Horde/Injector/Binder/ClosureTest.php [new file with mode: 0644]

diff --git a/framework/Injector/lib/Horde/Injector/Binder/Closure.php b/framework/Injector/lib/Horde/Injector/Binder/Closure.php
new file mode 100644 (file)
index 0000000..29fa10d
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * A binder object for binding an interface to a closure.
+ *
+ * An interface may be bound to a closure.  That closure must accept a
+ * Horde_Injector and return an object that satisfies the instance
+ * requirement. For example:
+ *
+ * $injector->bindClosure('database', function($injector) { return new my_mysql(); });
+ *
+ * @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_Binder_Closure implements Horde_Injector_Binder
+{
+    /**
+     * TODO
+     */
+    private $_closure;
+
+    /**
+     * Create a new Horde_Injector_Binder_Closure instance.
+     *
+     * @param string $closure  The closure to use for creating objects.
+     */
+    public function __construct($closure)
+    {
+        $this->_closure = $closure;
+    }
+
+    /**
+     * TODO
+     */
+    public function equals(Horde_Injector_Binder $otherBinder)
+    {
+        return (($otherBinder instanceof Horde_Injector_Binder_Closure) &&
+                ($otherBinder->getClosure() == $this->_closure));
+    }
+
+    /**
+     * Get the closure that this binder was bound to.
+     *
+     * @return callable  The closure this binder is bound to.
+     */
+    public function getClosure()
+    {
+        return $this->_closure;
+    }
+
+    /**
+     * Create instance using a closure
+     *
+     * If the closure depends on a Horde_Injector we want to limit its scope
+     * so it cannot change anything that effects any higher-level scope.  A
+     * closure should not have the responsibility of making a higher-level
+     * scope change.
+     * To enforce this we create a new child Horde_Injector.  When a
+     * Horde_Injector is requested from a Horde_Injector it will return
+     * itself. This means that the closure will only ever be able to work on
+     * the child Horde_Injector we give it now.
+     *
+     * @param Horde_Injector $injector  Injector object.
+     *
+     * @return TODO
+     */
+    public function create(Horde_Injector $injector)
+    {
+
+        $childInjector = $injector->createChildInjector();
+        $closure = $this->_closure;
+        return $closure($childInjector);
+    }
+}
index 06337f4..7b77ebc 100644 (file)
@@ -59,6 +59,7 @@
     <dir name="Horde">
      <dir name="Injector">
       <dir name="Binder">
+       <file name="Closure.php" role="php" />
        <file name="Factory.php" role="php" />
        <file name="Implementation.php" role="php" />
       </dir> <!-- /lib/Horde/Injector/Binder -->
@@ -74,6 +75,7 @@
     <dir name="Horde">
      <dir name="Injector">
       <dir name="Binder">
+       <file name="ClosureTest.php" role="test" />
        <file name="FactoryTest.php" role="test" />
        <file name="ImplementationTest.php" role="test" />
       </dir> <!-- /test/Horde/Injector/Binder -->
    <install as="Horde/Injector/Exception.php" name="lib/Horde/Injector/Exception.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/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" />
    <install as="Horde/Injector/phpunit.xml" name="test/Horde/Injector/phpunit.xml" />
+   <install as="Horde/Injector/Binder/ClosureTest.php" name="test/Horde/Injector/Binder/ClosureTest.php" />
    <install as="Horde/Injector/Binder/FactoryTest.php" name="test/Horde/Injector/Binder/FactoryTest.php" />
    <install as="Horde/Injector/Binder/ImplementationTest.php" name="test/Horde/Injector/Binder/ImplementationTest.php" />
   </filelist>
diff --git a/framework/Injector/test/Horde/Injector/Binder/ClosureTest.php b/framework/Injector/test/Horde/Injector/Binder/ClosureTest.php
new file mode 100644 (file)
index 0000000..5b71644
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+class Horde_Injector_Binder_ClosureTest extends Horde_Test_Case
+{
+    public function testShouldCallClosure()
+    {
+        $childInjector = $this->getMockSkipConstructor('Horde_Injector', array('createInstance', 'getInstance'));
+        $injector = $this->getMockSkipConstructor('Horde_Injector', array('createChildInjector'));
+        $injector->expects($this->once())
+            ->method('createChildInjector')
+            ->with()
+            ->will($this->returnValue($childInjector));
+
+        $closureBinder = new Horde_Injector_Binder_Closure(
+            function (Horde_Injector $injector) { return 'INSTANCE'; }
+        );
+
+        $this->assertEquals('INSTANCE', $closureBinder->create($injector));
+    }
+
+    /**
+     * The closure binder should pass a child injector object to the closure, so that
+     * any configuration that happens in the closure will not bleed into global scope
+     */
+    public function testShouldPassChildInjectorToClosure()
+    {
+        $closure = function (Horde_Injector $injector) { return $injector; };
+
+        $binder = new Horde_Injector_Binder_Closure($closure);
+
+        $injector = new ClosureInjectorMockTestAccess(new Horde_Injector_TopLevel());
+        $injector->TEST_ID = "PARENTINJECTOR";
+
+        // calling create should pass a child injector to the factory
+        $childInjector = $binder->create($injector);
+
+        // now the factory should have a reference to a child injector
+        $this->assertEquals($injector->TEST_ID . "->CHILD", $childInjector->TEST_ID, "Incorrect Injector passed to closure");
+    }
+
+    public function testShouldReturnBindingDetails()
+    {
+        $closure = function (Horde_Injector $injector) {};
+        $closureBinder = new Horde_Injector_Binder_Closure(
+            $closure
+        );
+
+        $this->assertEquals($closure, $closureBinder->getClosure());
+    }
+}
+
+class ClosureInjectorMockTestAccess extends Horde_Injector
+{
+    public function createChildInjector()
+    {
+        $child = new self($this);
+        $child->TEST_ID = $this->TEST_ID . "->CHILD";
+        return $child;
+    }
+}