package.xml 0000664 0001750 0001750 00000026761 12160052005 011302 0 ustar jan jan
Horde_Injectorpear.horde.orgHorde dependency injection containerA depedency injection container for Horde.Chuck Hagenbuchchuckchuck@horde.orgyes2013-06-182.0.21.0.0stablestableBSD-2-Clause
* [jan] Re-throw ReflectionExceptions as Horde_Injector_Exceptions.
5.3.01.7.0Horde_Exceptionpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Testpear.horde.org2.1.03.0.0alpha13.0.0alpha11.0.0alpha11.0.0alphaalpha2011-03-08BSD-2-Clause
* First alpha release for Horde 4.
* Add Horde_Injector#hasInstance().
* Initial release, contributed by Blue State Digital
1.0.0beta11.0.0betabeta2011-03-16BSD-2-Clause
* First beta release for Horde 4.
1.0.0RC11.0.0betabeta2011-03-22BSD-2-Clause
* First release candidate for Horde 4.
1.0.0RC21.0.0betabeta2011-03-29BSD-2-Clause
* Second release candidate for Horde 4.
1.0.01.0.0stablestable2011-04-06BSD-2-Clause
* First stable release for Horde 4.
1.0.11.0.0stablestable2011-11-22BSD-2-Clause
* [jan] Fix tests to work with PHPUnit 3.6.
2.0.0alpha11.0.0alphastable2012-07-05BSD-2-Clause
* First alpha release for Horde 5.
2.0.0beta11.0.0betastable2012-07-19BSD-2-Clause
* First beta release for Horde 5.
2.0.01.0.0stablestable2012-10-30BSD-2-Clause
* First stable release for Horde 5.
2.0.11.0.0stablestable2012-11-19BSD-2-Clause
* [mms] Use new Horde_Test layout.
2.0.21.0.0stablestable2013-06-18BSD-2-Clause
* [jan] Re-throw ReflectionExceptions as Horde_Injector_Exceptions.
Horde_Injector-2.0.2/doc/Horde/Injector/examples/annotatedsetters.php 0000644 0001750 0001750 00000001271 12160052005 024004 0 ustar jan jan
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
require 'Horde/Autoloader.php';
class Worker
{
public $helper;
/**
* @inject
*/
public function setHelper(Helper $h)
{
$this->helper = $h;
}
}
class Helper
{
public function __toString()
{
return 'helper';
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$b = $a->getInstance('Worker');
echo "$b->helper\n";
Horde_Injector-2.0.2/doc/Horde/Injector/examples/binder.php 0000644 0001750 0001750 00000002733 12160052005 021664 0 ustar jan jan
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
require 'Horde/Autoloader.php';
/**
* A dummy binder.
*
* @category Horde
* @package Injector
* @author Gunnar Wrobel
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
class Binder implements Horde_Injector_Binder
{
/**
* Create an instance.
*
* @param Horde_Injector $injector The injector should provide all required
* dependencies for creating the instance.
*
* @return mixed The concrete instance.
*/
public function create(Horde_Injector $injector)
{
return 'constructed';
}
/**
* Determine if one binder equals another binder
*
* @param Horde_Injector_Binder $binder The binder to compare against $this
*
* @return bool true if they are equal, or false if they are not equal
*/
public function equals(Horde_Injector_Binder $binder)
{
return false;
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->addBinder('constructed', new Binder());
var_dump($a->getInstance('constructed')); Horde_Injector-2.0.2/doc/Horde/Injector/examples/closure.php 0000664 0001750 0001750 00000001452 12160052005 022074 0 ustar jan jan
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
require 'Horde/Autoloader.php';
class ClosureCreated
{
public function __construct($msg)
{
$this->msg = $msg;
}
public function __toString()
{
return 'Foo: ' . $this->msg;
}
}
$closure = function(Horde_Injector $i) {
return new ClosureCreated('created by closure');
};
$binder = new Horde_Injector_Binder_Closure($closure);
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->bindClosure('CC', $closure);
$b = $a->getInstance('CC');
echo "$b\n";
Horde_Injector-2.0.2/doc/Horde/Injector/examples/factory.php 0000644 0001750 0001750 00000001544 12160052005 022067 0 ustar jan jan
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
require 'Horde/Autoloader.php';
class Greet
{
public function __construct($somebody)
{
$this->somebody = $somebody;
}
public function greet()
{
print 'Hello ' . $this->somebody;
}
}
class Factory
{
static public function getGreeter(Horde_Injector $injector)
{
return new Greet($injector->getInstance('Person'));
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->setInstance('Person', 'Bob');
$a->bindFactory('Greet', 'Factory', 'getGreeter');
$a->getInstance('Greet')->greet(); Horde_Injector-2.0.2/doc/Horde/Injector/examples/implementation.php 0000644 0001750 0001750 00000001702 12160052005 023441 0 ustar jan jan
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
require 'Horde/Autoloader.php';
interface Person
{
public function __toString();
}
class World implements Person
{
public function __toString()
{
return 'World';
}
}
interface Greeter
{
public function greet();
}
class Hello implements Greeter
{
public function __construct(Person $somebody)
{
$this->somebody = $somebody;
}
public function greet()
{
print 'Hello ' . $this->somebody;
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->bindImplementation('Person', 'World');
$a->bindImplementation('Greeter', 'Hello');
$a->getInstance('Greeter')->greet(); Horde_Injector-2.0.2/doc/Horde/Injector/examples/setget.php 0000644 0001750 0001750 00000000667 12160052005 021720 0 ustar jan jan
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
require 'Horde/Autoloader.php';
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->setInstance('a', 'a');
var_dump($a->getInstance('a'));
Horde_Injector-2.0.2/doc/Horde/Injector/COPYING 0000664 0001750 0001750 00000002430 12160052005 017121 0 ustar jan jan Copyright 1999-2013 Horde LLC. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HORDE PROJECT
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Horde_Injector-2.0.2/doc/Horde/Injector/Readme.html 0000644 0001750 0001750 00000134630 12160052005 020157 0 ustar jan jan
The dependency injection pattern (http://en.wikipedia.org/wiki/Dependency_injection) is a useful approach that can help to avoid using global variables or state. If a class depends on a connection to a database then this connection is often pulled into the class using a singleton pattern or by using a global variable.
Instead of providing the class with knowledge about the global state it is often preferable to "inject" the dependency into the class from the outside. This usually happens within the class constructor. To get hold of a database connection a class constructor could for example require an object that implements a database interface instead of using a singleton pattern.
This way the dependencies of a class are immediately visible in the constructor. It is not necessary to search the code of the class for references to the global scope. This usually also helps to decouple
dependencies between different code modules. Another major benefit of dependency injection is the fact that it facilitates unit testing of complex systems.
Horde_Injector
Horde_Injector provides a "Dependency Injection" framework. For PHP there exist several dependency injection frameworks (e.g. http://stubbles.net, http://components.symfony-project.org/dependency-injection) with extensive feature lists. So there is hardly any need for another framework with similar capabilities.
The essential part of dependency injection is the structure of classes with dependencies. They need to be amenable for an external management of their dependencies. If that is the case for a given class then most dependency injection frameworks should have no problem handling this class within the framework. The choice of the actual framework should not matter anymore.
Horde_Injector provides only a minimal version of dependency injection. It is somewhere in between the frameworks mentioned above and Twittee (http://twittee.org/). The primary goal is to drive refactoring of classes with complex dependencies so that their dependencies can be uncoupled and they can be used with a dependency injection framework.
Making classes amenable to dependency injection
As trivial as it may sound: a class can be managed by a dependency injection framework if the class allows the framework to inject its dependencies from the outside. That means that the class may not
pull in a dependency using global state via the singleton pattern:
External_Class::singleton();
create new objects with dependencies:
$db = new DB();
$b = new User($db);
use global variables:
global $conf;
$db = new DB($conf['sql');
In most cases the class should receive dependencies and required parameters within the constructor.
Using Horde_Injector
The Horde_Injector class is a simple container that allows you to fill it with a number of elements that can be retrieved later:
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->setInstance('a', 'a');
echo $a->getInstance('a');
string(1) "a"
Here we assigned a concrete instance to the injector. In fact not even an instance but a simple type: a string. Usually you would register an object.
But there might be situations - and in fact these are what dependency injection is about - where you do not want to register a concrete instance. You might not already have all the dependencies for creating an instance in place. So all you want to do is to register the required build instruction for generating an instance.
This is something that you can do by registering a wrapper object that implements the Horde_Injector_Binder interface. This wrapper object needs to be capable of creating the concrete instance:
class Binder implements Horde_Injector_Binder
{
public function create(Horde_Injector $injector)
{
return 'constructed';
}
public function equals(Horde_Injector_Binder $binder)
{
return false;
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->addBinder('constructed', new Binder());
var_dump($a->getInstance('constructed'));
string(11) "constructed"
The example above demonstrates this approach by using the dummy Binder class which implements Horde_Injector_Binder. Once getInstance('constructed') is called on the injector object it will
determine that there is no concrete instance for 'constructed' yet. It then looks for any binders that might be capable of creating 'constructed' and calls the create() function of such a binder.
Here the binder is simple again and does not even return an object but a simple string. It also makes no use of the Horde_Injector instance delivered as argument to the create() function. Usually the provided injector will be used to retrieve any missing dependencies for the instance to be created.
Default Binders
Horde_Injector comes with two default Binder implementations so that you don't have to define your own binders.
Lets look at the factory binder first:
class Greet
{
public function __construct($somebody)
{
$this->somebody = $somebody;
}
public function greet()
{
print 'Hello ' . $this->somebody;
}
}
class Factory
{
static public function getGreeter(Horde_Injector $injector)
{
return new Greet($injector->getInstance('Person'));
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->setInstance('Person', 'Bob');
$a->bindFactory('Greet', 'Factory', 'getGreeter');
$a->getInstance('Greet')->greet();
Hello Bob
This time the Factory in the example above really pulls a dependency: a person. We explicitly registered the string "Bob" with the injector and associated it with the interface name "Person".
The Horde_Injector_Binder_Factory binder can be registered with the injector using the "bindFactory()" shortcut. It takes the interface name (here it is "Greet") and requires a class and a method name. This is assumed to be the factory creating the concrete instance.
Once getInstance('Greet') is called the injector refers to the binder (as no concrete instance has been created yet). The binder delegates to the factory to actually create the object.
The whole thing is also possible with a little bit more magic. The second approach implemented by Horde_Injector_Binder_Implementation requires type hinting to work:
interface Person
{
public function __toString();
}
class World implements Person
{
public function __toString()
{
return 'World';
}
}
interface Greeter
{
public function greet();
}
class Hello implements Greeter
{
public function __construct(Person $somebody)
{
$this->somebody = $somebody;
}
public function greet()
{
print 'Hello ' . $this->somebody;
}
}
$a = new Horde_Injector(new Horde_Injector_TopLevel());
$a->bindImplementation('Person', 'World');
$a->bindImplementation('Greeter', 'Hello');
$a->getInstance('Greeter')->greet();
Hello World
The crucial part here is that the "Hello" class indicates in its constructor that it requires an object implementing the interface "Person". Horde_Injector is capable of detecting this via reflection. It will automatically search for the dependency and try to create an instance implementing this interface.
In order for this to work we bind two classes to two interfaces: "World" to "Person" and "Hello" to "Greeter". Once the injector tries to create the "Greeter"-instance it will be able to fetch the required "Person" dependency by creating a "World" object.
In case you remember that printing the little resulting string can be slightly easier while even using far less code: Dependency injection is meant for complex situations.
Nevertheless the example hopefully demonstrates how to handle different implementation options using dependency injection: You may have different drivers that all fulfill a given interface. The Horde_Injector gives you an easy method to define which drivers you actually want to use without actually instantiating them. The concrete setup will only be build once you really need a concrete instance.
Preparing a class for Horde_Injector
Assume you have the following simple class that represents a common structure found in many of the Horde packages:
class Horde_X
{
/**
* Instance object.
*
* @var Horde_X
*/
static protected $_instance;
/**
* Pointer to a DB instance.
*
* @var DB
*/
protected $_db;
/**
* Attempts to return a reference to a concrete Horde_X instance.
*
* @return Horde_X The concrete Horde_X reference.
* @throws Horde_Exception
*/
static public function singleton()
{
if (!isset(self::$_instance)) {
self::$_instance = new Horde_X();
}
return self::$_instance;
}
/**
* Constructor.
*/
public function __construct()
{
global $conf;
$this->_db = DB::connect($conf['sql']);
}
}
The class obviously depends on a database connection. The constructor above does not allow for dependency injection as it constructs the database connection itself. It uses the global variable $conf in order to get the settings for this connection. A constructor allowing dependency injection would look like this:
/**
* Constructor.
*
* @param DB $db A database connection.
*/
public function __construct(DB $db)
{
$this->_db = $db;
}
Of course this connection must be provided from somewhere. The application using Horde_X might simply provide it when creating the Horde_X instance. If the application is however using a dependency injection framework then this framework would be required to provide the required database connection.
Getting rid of singletons?
From the viewpoint of dependency injection Horde_X can be used now as it allows external injection of its dependencies. We could throw away the singleton now. However there might be some reasons why we would like to keep the singleton() method. One of the reasons might be backward compatibility as some other classes or applications are bound to use the method. Another reason might be that we want to clarify how to get a functional instance of the class to somebody just looking at the Horde_X class.
We could keep the following singleton method:
static public function singleton()
{
if (!isset(self::$_instance)) {
global $conf;
$db = DB::connect($conf['sql']);
self::$_instance = Horde_X($db);
}
return self::$_instance;
}
The final result that can be used with a dependency injection framework and still provides a backward compatible singleton method:
class Horde_X
{
/**
* Instance object.
*
* @var Horde_X
*/
static protected $_instance;
/**
* Pointer to a DB instance.
*
* @var DB
*/
protected $_db;
/**
* Attempts to return a reference to a concrete Horde_X instance.
*
* @return Horde_X The concrete Horde_X reference.
*/
static public function singleton()
{
if (!isset(self::$_instance)) {
global $conf;
$db = DB::connect($conf['sql']);
self::$_instance = Horde_X($db);
}
return self::$_instance;
}
/**
* Constructor.
*
* @param DB $db A database connection.
*/
public function __construct(DB $db)
{
$this->_db = $db;
}
}
Dependency Injection Container FAQ
Where can Horde_Injector be used?
In the application layer only. If you use this in your business models I will find you and beat you to death with a shoe.
How do I provide Horde_Config values to my business models?
Factories.
$injector->bindFactory('InterfaceX', 'FactoryX', 'create');
class FactoryX {
public function create(Horde_Injector $injector) {
$setting = $injector->getInstance('Horde_Config')->get('main', 'setting');
return new X($setting);
}
}
I have an array-typed parameter in my constructor, do I have to use a factory to provide the array of values?
Maybe. It depends on which you believe is easier. Consider this example where a more specific ArrayObject is used. Does this array get reused? If so it may be worth creating a special extension of ArrayObject.
$injector->bindFactory('Dictionary_Sources', 'Dictionary_Sources_Factory');
$dictionary = $injector->createInstance('Dictionary');
class Dictionary {
public function __construct(Dictionary_Sources $sources) {
...
}
}
class Dictionary_Sources extends ArrayObject{}
class Dictionary_Sources_Factory {
public function create(Horde_Injector $injector) {
return new Dictionary_Sources(array(
$injector->getInstance('Dictionary_Source_Cache'),
$injector->getInstance('Dictionary_Source_Db'),
$injector->getInstance('Dictionary_Source_Json')
));
}
}
You're probably thinking that you could just create a factory to build your Dictionary object since you need to write a factory anyways. The real benefit is when you decide that Dictionary needs a new collaborator, say a Logger. If you have defined a factory for your Dictionary object, then you must edit that factory and create a Logger and pass it to your Dictionary. With the method above, you would simply need to edit your constructor, and the Logger will be provided for you. This gives you much greater flexibility, especially if you have objects that can operate on the same array of objects, but need slightly different configuration.
Dependency Injection Container Spec
Terms
Dependency Injection - DI
Dependency Injection Container - DIC
Requirements
Switching business layer implementations across an application is expensive.
DI decouples our business layer classes from other business layer classes but doesn't solve the problem of decoupling our application layer classes from our business layer classes. Without functionality to tackle this issue, business layer class configuration is duplicated throughout the application.
As expense is the primary concern, any solution to the problem must not simply move the expense to initial application layer implementation.
Functional Spec
Goals
Decouple application layer (controller) from business layer (model)
Allow easy use of models which are decoupled from their dependencies using DI
Favor code over configuration
Simple, testable design
Features
A DIC allows you to make typically difficult configuration changes, like changing an entire application from using one service implementation to another, with simple modifications in one localized place.
A DIC manages the way your objects are wired together. It makes the wiring entirely configurable per-application, per-module, and all the way down to per single object instantiation. Needing configuration to make lots of individual objects is inefficient and hard to maintain which is why its reserved for only special cases. In reality most classes rely on only a few service objects (configuration, database connection, cache, etc.) and these object most often will have identical configurations in the entire application with a few per-module exceptions.
DICs don't require configuration for every object you want them to create. In most cases class dependencies will be detectable and the DIC will be able to provide those dependencies automatically.
Using a DIC for all application-level object creation makes your application unit-testable. Dependencies can easily be swapped out with mock objects because all dependencies are provided to the application using a DIC.
Technical Spec
The name of our DIC implementation is Horde_Injector. Horde_Injector can be told how to create objects using factories or it can try to create them itself using reflection. It can also be told to return an already-instantiated object when an interface is requested. Finally, if the requested interface has not been bound to a factory, implementation, or instance the injector will attempt to create the object using an implementation binder. For this to be successful the interface must actually be a concrete class.
Binders
The way we tell Horde_Injector how to create objects is using "binders" (which implement Horde_Injector_Binder). Horde_Injector maintains references to binders. References can be added in two ways:
$binder = new Horde_Injector_Binder_X($param1);
$injector->addBinder($interface, $binder)
$injector->bindX($interface, $param1);
Factory binders
[Factories|http://en.wikipedia.org/wiki/Factory_method_pattern] are classes with methods which instantiate and return objects. In the following example an interface is bound to a factory. If DataSourceX had dependencies it could instantiate them itself or ask $injector for those dependencies.
$injector->bindFactory('DataSourceInteface', 'DataSourceFactory', 'create');
class DataSourceFactory {
public function create(Horde_Injector $injector) {
return new DataSourceX();
}
}
Factory method names can be whatever you want but they must only require one parameter and it must be a Horde_Injector object.
Implementation binders
Reflection allows us to programmatically inspect the structure of the class that is to be instantiated. By reading the interface types of the class constructor's parameters and then asking the injector to create those objects as well we try to provide the requested class's constructor with all its dependencies.
$injector->bindImplementation('DataSourceInteface', 'DataSourceX');
class DataSourceX {
public function __construct(DependencyY $dependencyY) {
...
}
}
Choosing a binder
Use a factory binder if:
The class you are instantiating has any untyped parameters
You wish to create an instance of a class, that needs to have 2 objects of the same interface, but configured differently. [See FAQ|Dependency Injection Container FAQ]
Use an implementation binder if:
The class you are instantiating has only typed parameters
Instances
Horde_Injector maintains an array of object instances which are bound to interfaces. Instance binding happens two different ways: setting the instance binding manually, or by asking the injector to give you an instance (if it doesn't have one, then one is created and a reference to the internal instances array is added.)
$instance = new X();
$injector->setInstance('X', $instance);
Getting objects
For requested objects to be returned the injector must already have all the information present it needs to create the object.
Creating a new instance
To get a guaranteed new object use createInstance. References to instances retrieved in this manner are not stored. They will not be available to other objects unless you use setInstance to store the instance on the Injector.
$injector->createInstance('X');
Although the returned object will be new, its dependencies may not be. The injector will search its internal instances array for an instance matching the dependency's interface and if a match if found it will be used. If for some reason you need to guarantee that all dependencies are new, then you should consider using a factory binder.
Getting an instance
As previously mentioned instances are pooled by the injector, so getInstance() gives developers the opportunity to reuse objects. If an instance exists for the requested interface it will be returned, otherwise it will be created, added to the injectors internal instances array, and returned.
$injector->getInstance('X');
Scoping with child injectors
Horde_Injector implements the [Chain of Responsibility|http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern] design pattern with bindings and instances. What this means is that injectors will try to give you a binder or instance but if it doesn't have it it will ask its parent injector for them and try returning that to you.
Child Injectors allow you to configure sub-modules of code differently, without leaking any state into the global scope.
Horde_Injector-2.0.2/lib/Horde/Injector/Binder/AnnotatedSetters.php 0000664 0001750 0001750 00000007500 12160052005 023275 0 ustar jan jan
* @author James Pepin
* @author Chuck Hagenbuch
* @category Horde
* @package Injector
*/
class Horde_Injector_Binder_AnnotatedSetters implements Horde_Injector_Binder
{
/**
* @var Horde_Injector_Binder
*/
private $_binder;
/**
* @var Horde_Injector_DependencyFinder
*/
private $_dependencyFinder;
/**
* Constructor.
*
* @param Horde_Injector_Binder $binder TODO
* @param Horde_Injector_DependencyFinder $finder TODO
*
*/
public function __construct(Horde_Injector_Binder $binder,
Horde_Injector_DependencyFinder $finder = null)
{
$this->_binder = $binder;
$this->_dependencyFinder = is_null($finder)
? new Horde_Injector_DependencyFinder()
: $finder;
}
/**
* TODO
*
* @param Horde_Injector_Binder $binder TODO
*
* @return boolean Equality.
*/
public function equals(Horde_Injector_Binder $otherBinder)
{
return ($otherBinder instanceof Horde_Injector_Binder_AnnotatedSetters) &&
$this->getBinder()->equals($otherBinder->getBinder());
}
/**
* TODO
*
* @return Horde_Injector_Binder TODO
*/
public function getBinder()
{
return $this->_binder;
}
/**
* TODO
*/
public function create(Horde_Injector $injector)
{
$instance = $this->_binder->create($injector);
try {
$reflectionClass = new ReflectionClass(get_class($instance));
} catch (ReflectionException $e) {
throw new Horde_Injector_Exception($e);
}
$setters = $this->_findAnnotatedSetters($reflectionClass);
$this->_callSetters($setters, $injector, $instance);
return $instance;
}
/**
* Find all public methods in $reflectionClass that are annotated with
* @inject.
*
* @param ReflectionClass $reflectionClass TODO
*
* @return array TODO
*/
private function _findAnnotatedSetters(ReflectionClass $reflectionClass)
{
$setters = array();
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
if ($this->_isSetterMethod($reflectionMethod)) {
$setters[] = $reflectionMethod;
}
}
return $setters;
}
/**
* Is a method a setter method, by the criteria we define (has a doc
* comment that includes @inject).
*
* @param ReflectionMethod $reflectionMethod TODO
*/
private function _isSetterMethod(ReflectionMethod $reflectionMethod)
{
$docBlock = $reflectionMethod->getDocComment();
if ($docBlock) {
if (strpos($docBlock, '@inject') !== false) {
return true;
}
}
return false;
}
/**
* Call each ReflectionMethod in the $setters array, filling in its
* dependencies with the $injector.
*
* @param array $setters Array of ReflectionMethods to call.
* @param Horde_Injector $injector The injector to get dependencies from.
* @param object $instance The object to call setters on.
*/
private function _callSetters(array $setters, Horde_Injector $injector,
$instance)
{
foreach ($setters as $setterMethod) {
$setterMethod->invokeArgs(
$instance,
$this->_dependencyFinder->getMethodDependencies($injector, $setterMethod)
);
}
}
}
Horde_Injector-2.0.2/lib/Horde/Injector/Binder/Closure.php 0000644 0001750 0001750 00000004526 12160052005 021425 0 ustar jan jan
* $injector->bindClosure('database', function($injector) { return new my_mysql(); });
*
*
* @author Bob Mckee
* @author James Pepin
* @author Chuck Hagenbuch
* @category Horde
* @package Injector
*/
class Horde_Injector_Binder_Closure implements Horde_Injector_Binder
{
/**
* TODO
*
* @var Horde_Injector_Binder_Closure
*/
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
*
* @param Horde_Injector_Binder $otherBinder TODO
*
* @return boolean Equality.
*/
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);
}
}
Horde_Injector-2.0.2/lib/Horde/Injector/Binder/Factory.php 0000644 0001750 0001750 00000006237 12160052005 021421 0 ustar jan jan
* class MyFactory {
* ...
* public function create(Horde_Injector $injector)
* {
* return new MyClass($injector->getInstance('Collaborator'), new MyOtherClass(17));
* }
* ...
* }
*
*
* @author Bob Mckee
* @author James Pepin
* @category Horde
* @package Injector
*/
class Horde_Injector_Binder_Factory implements Horde_Injector_Binder
{
/**
* TODO
*
* @var string
*/
private $_factory;
/**
* TODO
*
* @var string
*/
private $_method;
/**
* Create a new Horde_Injector_Binder_Factory instance.
*
* @param string $factory The factory class to use for creating objects.
* @param string $method The method on that factory to use for creating
* objects.
*/
public function __construct($factory, $method)
{
$this->_factory = $factory;
$this->_method = $method;
}
/**
* TODO
*
* @param Horde_Injector_Binder $otherBinder TODO
*
* @return boolean Equality.
*/
public function equals(Horde_Injector_Binder $otherBinder)
{
return (($otherBinder instanceof Horde_Injector_Binder_Factory) &&
($otherBinder->getFactory() == $this->_factory) &&
($otherBinder->getMethod() == $this->_method));
}
/**
* Get the factory classname that this binder was bound to.
*
* @return string The factory classname this binder is bound to.
*/
public function getFactory()
{
return $this->_factory;
}
/**
* Get the method that this binder was bound to.
*
* @return string The method this binder is bound to.
*/
public function getMethod()
{
return $this->_method;
}
/**
* Create instance using a factory method
*
* If the factory depends on a Horde_Injector we want to limit its scope
* so it cannot change anything that effects any higher-level scope. A
* factory 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 factory 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();
/* We use getInstance() here because we don't want to have to create
* this factory more than one time to create more objects of this
* type. */
return $childInjector->getInstance($this->_factory)->{$this->_method}($childInjector);
}
}
Horde_Injector-2.0.2/lib/Horde/Injector/Binder/Implementation.php 0000664 0001750 0001750 00000004421 12160052005 022772 0 ustar jan jan
* @author James Pepin
* @category Horde
* @package Injector
*/
class Horde_Injector_Binder_Implementation implements Horde_Injector_Binder
{
/**
* TODO
*/
private $_implementation;
/**
* @var Horde_Injector_DependencyFinder
*/
private $_dependencyFinder;
/**
* TODO
*/
public function __construct($implementation,
Horde_Injector_DependencyFinder $finder = null)
{
$this->_implementation = $implementation;
$this->_dependencyFinder = is_null($finder)
? new Horde_Injector_DependencyFinder()
: $finder;
}
/**
* TODO
*
* @return TODO
*/
public function getImplementation()
{
return $this->_implementation;
}
/**
* TODO
*
* @return boolean Equality.
*/
public function equals(Horde_Injector_Binder $otherBinder)
{
return (($otherBinder instanceof Horde_Injector_Binder_Implementation) &&
($otherBinder->getImplementation() == $this->_implementation));
}
/**
* TODO
*/
public function create(Horde_Injector $injector)
{
try {
$reflectionClass = new ReflectionClass($this->_implementation);
} catch (ReflectionException $e) {
throw new Horde_Injector_Exception($e);
}
$this->_validateImplementation($reflectionClass);
return $this->_getInstance($injector, $reflectionClass);
}
/**
* TODO
*/
protected function _validateImplementation(ReflectionClass $reflectionClass)
{
if ($reflectionClass->isAbstract() || $reflectionClass->isInterface()) {
throw new Horde_Injector_Exception('Cannot bind interface or abstract class "' . $this->_implementation . '" to an interface.');
}
}
/**
* TODO
*/
protected function _getInstance(Horde_Injector $injector,
ReflectionClass $class)
{
return $class->getConstructor()
? $class->newInstanceArgs($this->_dependencyFinder->getMethodDependencies($injector, $class->getConstructor()))
: $class->newInstance();
}
}
Horde_Injector-2.0.2/lib/Horde/Injector/Binder.php 0000644 0001750 0001750 00000002536 12160052005 020010 0 ustar jan jan
* @author James Pepin
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
/**
* Describes a binding class that is able to create concrete object instances.
*
* @category Horde
* @package Injector
* @author Bob Mckee
* @author James Pepin
* @license http://www.horde.org/licenses/bsd BSD
* @link http://pear.horde.org/index.php?package=Injector
*/
interface Horde_Injector_Binder
{
/**
* Create an instance.
*
* @param Horde_Injector $injector The injector should provide all
* required dependencies for creating the
* instance.
*
* @return mixed The concrete instance.
*/
public function create(Horde_Injector $injector);
/**
* Determine if one binder equals another binder
*
* @param Horde_Injector_Binder $binder The binder to compare against
* $this.
*
* @return boolean True if equal, false if not equal.
*/
public function equals(Horde_Injector_Binder $binder);
}
Horde_Injector-2.0.2/lib/Horde/Injector/DependencyFinder.php 0000644 0001750 0001750 00000003467 12160052005 022017 0 ustar jan jan
* @author James Pepin
* @author Chuck Hagenbuch
* @category Horde
* @package Injector
*/
class Horde_Injector_DependencyFinder
{
/**
* TODO
*
* @param Horde_Injector $injector TODO
* @param ReflectionMethod $method TODO
*
* @return array TODO
* @throws Horde_Injector_Exception
*/
public function getMethodDependencies(Horde_Injector $injector,
ReflectionMethod $method)
{
$dependencies = array();
try {
foreach ($method->getParameters() as $parameter) {
$dependencies[] = $this->getParameterDependency($injector, $parameter);
}
} catch (Horde_Injector_Exception $e) {
throw new Horde_Injector_Exception("$method has unfulfilled dependencies ($parameter)", 0, $e);
}
return $dependencies;
}
/**
* TODO
*
* @param Horde_Injector $injector TODO
* @param ReflectionParameter $method TODO
*
* @return mixed TODO
* @throws Horde_Injector_Exception
*/
public 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("Untyped parameter \$" . $parameter->getName() . "can't be fulfilled");
}
}
Horde_Injector-2.0.2/lib/Horde/Injector/Exception.php 0000644 0001750 0001750 00000000316 12160052005 020535 0 ustar jan jan
* @author James Pepin
* @category Horde
* @package Injector
*/
class Horde_Injector_Exception extends Horde_Exception
{
}
Horde_Injector-2.0.2/lib/Horde/Injector/Scope.php 0000644 0001750 0001750 00000001706 12160052005 017654 0 ustar jan jan
* @author James Pepin
* @category Horde
* @package Injector
*/
class Horde_Injector_TopLevel implements Horde_Injector_Scope
{
/**
* Get an Implementation Binder that maps the $interface to itself
*
* @param string $interface The interface to retrieve binding information
* for.
*
* @return Horde_Injector_Binder_ImplementationWithSetters
* A new binding object that maps the interface to itself, with
* setter injection.
*/
public function getBinder($interface)
{
$dependencyFinder = new Horde_Injector_DependencyFinder();
$implementationBinder = new Horde_Injector_Binder_Implementation($interface, $dependencyFinder);
return new Horde_Injector_Binder_AnnotatedSetters($implementationBinder, $dependencyFinder);
}
/**
* Always return null. Object doesn't keep instance references
*
* Method is necessary because this object is the default parent Injector.
* The child of this injector will ask it for instances in the case where
* no bindings are set on the child. This should always return null.
*
* @param string $interface The interface in question
* @return null
*/
public function getInstance($interface)
{
return null;
}
}
Horde_Injector-2.0.2/lib/Horde/Injector.php 0000664 0001750 0001750 00000021433 12160052005 016604 0 ustar jan jan
* @author James Pepin
* @category Horde
* @package Injector
*/
class Horde_Injector implements Horde_Injector_Scope
{
/**
* @var Horde_Injector_Scope
*/
private $_parentInjector;
/**
* @var array
*/
private $_bindings = array();
/**
* @var array
*/
private $_instances;
/**
* Create a new injector object.
*
* Every injector object has a parent scope. For the very first
* Horde_Injector, you should pass it a Horde_Injector_TopLevel object.
*
* @param Horde_Injector_Scope $injector The parent scope.
*/
public function __construct(Horde_Injector_Scope $injector)
{
$this->_parentInjector = $injector;
$this->_instances = array(__CLASS__ => $this);
}
/**
* Create a child injector that inherits this injector's scope.
*
* All child injectors inherit the parent scope. Any objects that were
* created using getInstance, will be available to the child container.
* The child container can set bindings to override the parent, and none
* of those bindings will leak to the parent.
*
* @return Horde_Injector A child injector with $this as its parent.
*/
public function createChildInjector()
{
return new self($this);
}
/**
* Method overloader. Handles $this->bind[BinderType] type calls.
*
* @return Horde_Injector_Binder See _bind().
* @throws BadMethodCallException
*/
public function __call($name, $args)
{
if (substr($name, 0, 4) == 'bind') {
return $this->_bind(substr($name, 4), $args);
}
throw new BadMethodCallException('Call to undefined method ' . __CLASS__ . '::' . $name . '()');
}
/**
* Method that creates binders to send to addBinder(). This is called by
* the magic method __call() whenever a function is called that starts
* with bind.
*
* @param string $type The type of Horde_Injector_Binder_ to be created.
* Matches /^Horde_Injector_Binder_(\w+)$/.
* @param array $args The constructor arguments for the binder object.
*
* @return Horde_Injector_Binder The binder object created. Useful for
* method chaining.
* @throws BadMethodCallException
*/
private function _bind($type, $args)
{
$interface = array_shift($args);
if (!$interface) {
throw new BadMethodCallException('First parameter for "bind' . $type . '" must be the name of an interface or class');
}
$reflectionClass = new ReflectionClass('Horde_Injector_Binder_' . $type);
$this->_addBinder(
$interface,
$reflectionClass->getConstructor()
? $reflectionClass->newInstanceArgs($args)
: $reflectionClass->newInstance()
);
return $this->_getBinder($interface);
}
/**
* Add a Horde_Injector_Binder to an interface
*
* This is the method by which we bind an interface to a concrete
* implentation or factory. For convenience, binders may be added by
* $this->bind[BinderType].
*
* bindFactory - Creates a Horde_Injector_Binder_Factory
* bindImplementation - Creates a Horde_Injector_Binder_Implementation
*
* All subsequent arguments are passed to the constructor of the
* Horde_Injector_Binder object.
*
* @param string $interface The interface to bind to.
* @param Horde_Injector_Binder $binder The binder to be bound to the
* specified $interface.
*
* @return Horde_Injector A reference to itself for method chaining.
*/
public function addBinder($interface, Horde_Injector_Binder $binder)
{
$this->_addBinder($interface, $binder);
return $this;
}
/**
* @see self::addBinder()
*/
private function _addBinder($interface, Horde_Injector_Binder $binder)
{
// First we check to see if our parent already has an equal binder set.
// if so we don't need to do anything
if (!$binder->equals($this->_parentInjector->getBinder($interface))) {
$this->_bindings[$interface] = $binder;
}
}
/**
* Get the Binder associated with the specified instance.
*
* Binders are objects responsible for binding a particular interface
* with a class. If no binding is set for this object, the parent scope is
* consulted.
*
* @param string $interface The interface to retrieve binding information
* for.
*
* @return Horde_Injector_Binder The binding set for the specified
* interface.
*/
public function getBinder($interface)
{
return isset($this->_bindings[$interface])
? $this->_bindings[$interface]
: $this->_parentInjector->getBinder($interface);
}
/**
* Get the Binder associated with the specified instance.
*
* @param string $interface The interface to retrieve binding information
* for.
*
* @return Horde_Injector_Binder The binder object created. Useful for
* method chaining.
*/
private function _getBinder($interface)
{
return $this->_bindings[$interface];
}
/**
* Set the object instance to be retrieved by getInstance the next time
* the specified interface is requested.
*
* This method allows you to set the cached object instance so that all
* subsequent getInstance() calls return the object you have specified.
*
* @param string $interface The interface to bind the instance to.
* @param mixed $instance The object instance to be bound to the
* specified instance.
*
* @return Horde_Injector A reference to itself for method chaining.
*/
public function setInstance($interface, $instance)
{
$this->_instances[$interface] = $instance;
return $this;
}
/**
* Create a new instance of the specified object/interface.
*
* This method creates a new instance of the specified object/interface.
* NOTE: it does not save that instance for later retrieval. If your
* object should be re-used elsewhere, you should be using getInstance().
*
* @param string $interface The interface name, or object class to be
* created.
*
* @return mixed A new object that implements $interface.
*/
public function createInstance($interface)
{
return $this->getBinder($interface)->create($this);
}
/**
* Retrieve an instance of the specified object/interface.
*
* This method gets you an instance, and saves a reference to that
* instance for later requests.
*
* Interfaces must be bound to a concrete class to be created this way.
* Concrete instances may be created through reflection.
*
* It does not gaurantee that it is a new instance of the object. For a
* new instance see createInstance().
*
* @param string $interface The interface name, or object class to be
* created.
*
* @return mixed An object that implements $interface, but not
* necessarily a new one.
*/
public function getInstance($interface)
{
// Do we have an instance?
if (!$this->hasInstance($interface)) {
// Do we have a binding for this interface? If so then we don't
// ask our parent.
if (!isset($this->_bindings[$interface]) &&
// Does our parent have an instance?
($instance = $this->_parentInjector->getInstance($interface))) {
return $instance;
}
// We have to make our own instance
$this->setInstance($interface, $this->createInstance($interface));
}
return $this->_instances[$interface];
}
/**
* Has the interface for the specified object/interface been created yet?
*
* @param string $interface The interface name or object class.
*
* @return boolean True if the instance already has been created.
*/
public function hasInstance($interface)
{
return isset($this->_instances[$interface]);
}
}
Horde_Injector-2.0.2/test/Horde/Injector/Binder/AnnotatedSettersTest.php 0000664 0001750 0001750 00000003015 12160052005 024343 0 ustar jan jan assertNull($instance->dep);
$newInstance = $annotatedSettersBinder->create($injector);
$this->assertInstanceOf('Horde_Injector_Binder_AnnotatedSettersTest__NoDependencies', $newInstance->dep);
}
}
/**
* Used by preceeding tests!!!
*/
class Horde_Injector_Binder_AnnotatedSettersTest__EmptyBinder implements Horde_Injector_Binder
{
public $instance;
public function __construct($instance)
{
$this->instance = $instance;
}
public function create(Horde_Injector $injector)
{
return $this->instance;
}
public function equals(Horde_Injector_Binder $otherBinder)
{
return false;
}
}
class Horde_Injector_Binder_AnnotatedSettersTest__NoDependencies
{
}
class Horde_Injector_Binder_AnnotatedSettersTest__TypedSetterDependency
{
public $dep;
/**
* @inject
*/
public function setDep(Horde_Injector_Binder_AnnotatedSettersTest__NoDependencies $dep)
{
$this->dep = $dep;
}
}
Horde_Injector-2.0.2/test/Horde/Injector/Binder/ClosureTest.php 0000644 0001750 0001750 00000004064 12160052005 022473 0 ustar jan jan 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;
}
}
Horde_Injector-2.0.2/test/Horde/Injector/Binder/FactoryTest.php 0000644 0001750 0001750 00000006723 12160052005 022472 0 ustar jan jan getMockSkipConstructor('Horde_Injector_Binder_Factory', array('create'));
$factory->expects($this->once())
->method('create')
->with()
->will($this->returnValue('INSTANCE'));
$factoryClassName = get_class($factory);
$childInjector = $this->getMockSkipConstructor('Horde_Injector', array('createInstance', 'getInstance'));
$childInjector->expects($this->once())
->method('getInstance')
->with($this->equalTo($factoryClassName))
->will($this->returnValue($factory));
$injector = $this->getMockSkipConstructor('Horde_Injector', array('createChildInjector'));
$injector->expects($this->once())
->method('createChildInjector')
->with()
->will($this->returnValue($childInjector));
$factoryBinder = new Horde_Injector_Binder_Factory(
$factoryClassName,
'create'
);
$this->assertEquals('INSTANCE', $factoryBinder->create($injector));
}
/**
* the factory binder should pass a child injector object to the factory, so that
* any configuration that happens in the factory will not bleed into global scope
*/
public function testShouldPassChildInjectorToFactoryMethod()
{
$factory = new InjectorFactoryTestMockFactory();
$binder = new Horde_Injector_Binder_Factory('InjectorFactoryTestMockFactory', 'create');
$injector = new InjectorMockTestAccess(new Horde_Injector_TopLevel());
$injector->TEST_ID = "PARENTINJECTOR";
// set the instance so we know we'll get our factory object from the injector
$injector->setInstance('InjectorFactoryTestMockFactory', $factory);
// calling create should pass a child injector to the factory
$binder->create($injector);
// now the factory should have a reference to a child injector
$this->assertEquals($injector->TEST_ID . "->CHILD", $factory->getInjector()->TEST_ID, "Incorrect Injector passed to factory method");
}
/**
* this test guarantees that our mock factory stores the injector that was given to it,
* so that we may inspect it later and prove what injector is actually given to it
*/
public function testMockFactoryStoresPassedInjector()
{
$factory = new InjectorFactoryTestMockFactory();
$injector = new InjectorMockTestAccess(new Horde_Injector_TopLevel());
$injector->TEST_ID = "INJECTOR";
$factory->create($injector);
$this->assertEquals($injector, $factory->getInjector());
}
public function testShouldReturnBindingDetails()
{
$factoryBinder = new Horde_Injector_Binder_Factory(
'FACTORY',
'METHOD'
);
$this->assertEquals('FACTORY', $factoryBinder->getFactory());
$this->assertEquals('METHOD', $factoryBinder->getMethod());
}
}
class InjectorFactoryTestMockFactory
{
public function getInjector()
{
return $this->_injector;
}
public function create(Horde_Injector $injector)
{
$this->_injector = $injector;
}
}
class InjectorMockTestAccess extends Horde_Injector
{
public function createChildInjector()
{
$child = new self($this);
$child->TEST_ID = $this->TEST_ID . "->CHILD";
return $child;
}
}
Horde_Injector-2.0.2/test/Horde/Injector/Binder/ImplementationTest.php 0000664 0001750 0001750 00000012536 12160052005 024051 0 ustar jan jan df = new Horde_Injector_DependencyFinder();
}
public function testShouldReturnBindingDetails()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'IMPLEMENTATION',
$this->df
);
$this->assertEquals('IMPLEMENTATION', $implBinder->getImplementation());
}
public function testShouldCreateInstanceOfClassWithNoDependencies()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'Horde_Injector_Binder_ImplementationTest__NoDependencies',
$this->df
);
$this->assertInstanceOf(
'Horde_Injector_Binder_ImplementationTest__NoDependencies',
$implBinder->create($this->_getInjectorNeverCallMock())
);
}
public function testShouldCreateInstanceOfClassWithTypedDependencies()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'Horde_Injector_Binder_ImplementationTest__TypedDependency',
$this->df
);
$createdInstance = $implBinder->create($this->_getInjectorReturnsNoDependencyObject());
$this->assertInstanceOf(
'Horde_Injector_Binder_ImplementationTest__TypedDependency',
$createdInstance
);
$this->assertInstanceOf(
'Horde_Injector_Binder_ImplementationTest__NoDependencies',
$createdInstance->dep
);
}
/**
* @expectedException Horde_Injector_Exception
*/
public function testShouldThrowExceptionWhenTryingToCreateInstanceOfClassWithUntypedDependencies()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'Horde_Injector_Binder_ImplementationTest__UntypedDependency',
$this->df
);
$implBinder->create($this->_getInjectorNeverCallMock());
}
public function testShouldUseDefaultValuesFromUntypedOptionalParameters()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'Horde_Injector_Binder_ImplementationTest__UntypedOptionalDependency',
$this->df
);
$createdInstance = $implBinder->create($this->_getInjectorNeverCallMock());
$this->assertEquals('DEPENDENCY', $createdInstance->dep);
}
/**
* @expectedException Horde_Injector_Exception
*/
public function testShouldThrowExceptionIfRequestedClassIsNotDefined()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'CLASS_DOES_NOT_EXIST',
$this->df
);
$implBinder->create($this->_getInjectorNeverCallMock());
}
/**
* @expectedException Horde_Injector_Exception
*/
public function testShouldThrowExceptionIfImplementationIsAnInterface()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'Horde_Injector_Binder_ImplementationTest__Interface',
$this->df
);
$implBinder->create($this->_getInjectorNeverCallMock());
}
/**
* @expectedException Horde_Injector_Exception
*/
public function testShouldThrowExceptionIfImplementationIsAnAbstractClass()
{
$implBinder = new Horde_Injector_Binder_Implementation(
'Horde_Injector_Binder_ImplementationTest__AbstractClass',
$this->df
);
$implBinder->create($this->_getInjectorNeverCallMock());
}
private function _getInjectorNeverCallMock()
{
$injector = $this->getMockSkipConstructor('Horde_Injector', array('getInstance'));
$injector->expects($this->never())
->method('getInstance');
return $injector;
}
private function _getInjectorReturnsNoDependencyObject()
{
$injector = $this->getMockSkipConstructor('Horde_Injector', array('getInstance'));
$injector->expects($this->once())
->method('getInstance')
->with($this->equalTo('Horde_Injector_Binder_ImplementationTest__NoDependencies'))
->will($this->returnValue(new Horde_Injector_Binder_ImplementationTest__NoDependencies()));
return $injector;
}
}
/**
* Used by preceeding tests!!!
*/
class Horde_Injector_Binder_ImplementationTest__NoDependencies
{
}
class Horde_Injector_Binder_ImplementationTest__TypedDependency
{
public $dep;
public function __construct(Horde_Injector_Binder_ImplementationTest__NoDependencies $dep)
{
$this->dep = $dep;
}
}
class Horde_Injector_Binder_ImplementationTest__UntypedDependency
{
public function __construct($dep)
{
}
}
class Horde_Injector_Binder_ImplementationTest__UntypedOptionalDependency
{
public $dep;
public function __construct($dep = 'DEPENDENCY')
{
$this->dep = $dep;
}
}
interface Horde_Injector_Binder_ImplementationTest__Interface
{
}
abstract class Horde_Injector_Binder_ImplementationTest__AbstractClass
{
}
class Horde_Injector_Binder_ImplementationTest__SetterNoDependencies
{
public $setterDep;
public function setDependency()
{
$this->setterDep = 'CALLED';
}
}
class Horde_Injector_Binder_ImplementationTest__SetterHasDependencies
{
public $setterDep;
public function setDependency(Horde_Injector_Binder_ImplementationTest__NoDependencies $setterDep)
{
$this->setterDep = $setterDep;
}
}
Horde_Injector-2.0.2/test/Horde/Injector/AllTests.php 0000664 0001750 0001750 00000000132 12160052005 020541 0 ustar jan jan run();
Horde_Injector-2.0.2/test/Horde/Injector/BinderTest.php 0000644 0001750 0001750 00000004557 12160052005 021066 0 ustar jan jan assertEquals($shouldEqual, $binderA->equals($binderB), $message);
}
}
Horde_Injector-2.0.2/test/Horde/Injector/bootstrap.php 0000664 0001750 0001750 00000000143 12160052005 021025 0 ustar jan jan getMock('Horde_Injector_TopLevel', array('getBinder'));
$topLevel->expects($this->once())
->method('getBinder')
->with($this->equalTo('UNBOUND_INTERFACE'))
->will($this->returnValue('RETURNED_BINDING'));
$injector = new Horde_Injector($topLevel);
$this->assertEquals('RETURNED_BINDING', $injector->getBinder('UNBOUND_INTERFACE'));
}
public function testShouldGetManuallyBoundBinder()
{
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
$binder = new Horde_Injector_Binder_Mock();
$injector->addBinder('BOUND_INTERFACE', $binder);
$this->assertSame($binder, $injector->getBinder('BOUND_INTERFACE'));
}
public function testShouldProvideMagicFactoryMethodForBinderAddition()
{
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
// binds a Horde_Injector_Binder_Mock object
$this->assertInstanceOf('Horde_Injector_Binder_Mock', $injector->bindMock('BOUND_INTERFACE'));
$this->assertInstanceOf('Horde_Injector_Binder_Mock', $injector->getBinder('BOUND_INTERFACE'));
}
public function testShouldProvideMagicFactoryMethodForBinderAdditionWhereBinderHasDependencies()
{
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
// binds a Horde_Injector_Binder_Mock object
$this->assertInstanceOf('Horde_Injector_Binder_MockWithDependencies',
$injector->bindMockWithDependencies('BOUND_INTERFACE', 'PARAMETER1'));
$this->assertInstanceOf('Horde_Injector_Binder_MockWithDependencies',
$injector->getBinder('BOUND_INTERFACE'));
}
/**
* @expectedException BadMethodCallException
*/
public function testShouldThrowExceptionIfInterfaceNameIsNotPassedToMagicFactoryMethodForBinderAddition()
{
$injector = new Horde_Injector($this->_getTopLevelNeverCalledMock());
$injector->bindMock();
}
/**
* @expectedException BadMethodCallException
*/
public function testShouldThrowExceptionIfMethodNameIsInvalid()
{
$injector = new Horde_Injector($this->_getTopLevelNeverCalledMock());
$injector->invalid();
}
public function testShouldReturnItselfWhenInjectorRequested()
{
$injector = new Horde_Injector($this->_getTopLevelNeverCalledMock());
$this->assertSame($injector, $injector->getInstance('Horde_Injector'));
}
/**
* Would love to use PHPUnit's mock object here istead of Horde_Injector_Binder_Mock but you
* can't be sure the expected resulting object is the same object you told the mock to return.
* This is because Mock clone objects passed to mocked methods.
*
* http://www.phpunit.de/ticket/120
*
* @author Bob McKee
*/
public function testCreateInstancePassesCurrentInjectorScopeToBinderForCreation()
{
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
$injector->addBinder('BOUND_INTERFACE', new Horde_Injector_Binder_Mock());
// normally you wouldn't get an injector back; the binder would create something and return
// it to you. here we are just confirming that the proper injector was passed to the
// binder's create method.
$this->assertEquals($injector, $injector->createInstance('BOUND_INTERFACE'));
}
public function testShouldNotReturnSharedObjectOnCreate()
{
$injector = $this->_createInjector();
//this call will cache this class on the injector
$stdclass = $injector->getInstance('StdClass');
$this->assertNotSame($stdclass, $injector->createInstance('StdClass'));
}
public function testShouldNotShareObjectCreatedUsingCreate()
{
$injector = $this->_createInjector();
// this call should not store the instance on the injector
$stdclass = $injector->createInstance('StdClass');
$this->assertNotSame($stdclass, $injector->getInstance('StdClass'));
}
public function testChildSharesInstancesOfParent()
{
$injector = $this->_createInjector();
//this call will store the created instance on $injector
$stdclass = $injector->getInstance('StdClass');
// create a child injector and ensure that the stdclass returned is the same
$child = $injector->createChildInjector();
$this->assertSame($stdclass, $child->getInstance('StdClass'));
}
private function _createInjector()
{
return new Horde_Injector(new Horde_Injector_TopLevel());
}
public function testShouldReturnSharedInstanceIfRequested()
{
$injector = new Horde_Injector($this->_getTopLevelNeverCalledMock());
$instance = new StdClass();
$injector->setInstance('INSTANCE_INTERFACE', $instance);
$this->assertSame($instance, $injector->getInstance('INSTANCE_INTERFACE'));
}
/**
* this test should test that when you override a binding in a child injector,
* that the child does not create a new version of the object if the binding has not changed
*/
public function testChildInjectorDoNotSaveBindingLocallyWhenBinderIsSameAsParent()
{
// we need to set a class for an instance on the parent
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
$df = new Horde_Injector_DependencyFinder();
$injector->addBinder('FooBarInterface', new Horde_Injector_Binder_Implementation('StdClass', $df));
// getInstance will save $returnedObject and return it again later when FooBarInterface is requested
$returnedObject = $injector->getInstance('FooBarInterface');
$childInjector = $injector->createChildInjector();
// add same binding again to child
$childInjector->addBinder('FooBarInterface', new Horde_Injector_Binder_Implementation('StdClass', $df));
$this->assertSame($returnedObject, $childInjector->getInstance('FooBarInterface'),
"Child should have returned object reference from parent because added binder was identical to the parent binder");
}
/**
* this test should test that when you override a binding in a child injector,
* that the child creates a new version of the object, and not the parent's cached version
* if the binding is changed
*/
public function testChildInjectorsDoNotAskParentForInstanceIfBindingIsSet()
{
$mockTopLevel = $this->getMock('Horde_Injector_TopLevel', array('getInstance'));
$mockTopLevel->expects($this->never())->method('getInstance');
$injector = new Horde_Injector($mockTopLevel);
$injector->addBinder('StdClass', new Horde_Injector_Binder_Mock());
$injector->getInstance('StdClass');
}
public function testChildInjectorAsksParentForInstance()
{
$topLevelMock = $this->getMock('Horde_Injector_TopLevel', array('getInstance'));
$topLevelMock->expects($this->once())
->method('getInstance')
->with('StdClass');
$injector = new Horde_Injector($topLevelMock);
$injector->getInstance('StdClass');
}
/**
* Would love to use PHPUnit's mock object here istead of Horde_Injector_Binder_Mock but you
* can't be sure the expected resulting object is the same object you told the mock to return.
* This is because Mock clone objects passed to mocked methods.
*
* http://www.phpunit.de/ticket/120
*
* @author Bob McKee
*/
public function testShouldCreateAndStoreSharedObjectIfOneDoesNotAlreadyExist()
{
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
$injector->addBinder('BOUND_INTERFACE', new Horde_Injector_Binder_Mock());
// should call "createInstance" and then "setInstance" on the result
// normally you wouldn't get an injector back; the binder would create something and return
// it to you. here we are just confirming that the proper injector was passed to the
// binder's create method.
$this->assertSame($injector, $injector->getInstance('BOUND_INTERFACE'));
// should just return stored instance
// the injector sent to the "create" method noted above should also be returned here.
$this->assertSame($injector, $injector->getInstance('BOUND_INTERFACE'));
}
public function testShouldCreateAndStoreSharedObjectInstanceIfDefaultTopLevelBinderIsUsed()
{
$injector = new Horde_Injector(new Horde_Injector_TopLevel());
$class = $injector->getInstance('StdClass');
$class2 = $injector->getInstance('StdClass');
$this->assertSame($class, $class2, "Injector did not return same object on consecutive getInstance calls");
}
public function testCreateChildInjectorReturnsDifferentInjector()
{
$injector = new Horde_Injector($this->_getTopLevelNeverCalledMock());
$childInjector = $injector->createChildInjector();
$this->assertInstanceOf('Horde_Injector', $childInjector);
$this->assertNotSame($injector, $childInjector);
}
public function testShouldAllowChildInjectorsAccessToParentInjectorBindings()
{
$mockInjector = $this->getMock('Horde_Injector_TopLevel', array('getBinder'));
$mockInjector->expects($this->any()) // this gets called once in addBinder
->method('getBinder')
->with('BOUND_INTERFACE')
->will($this->returnValue(new Horde_Injector_Binder_Mock()));
$injector = new Horde_Injector($mockInjector);
$binder = new Horde_Injector_Binder_Mock();
$injector->addBinder('BOUND_INTERFACE', $binder);
$childInjector = $injector->createChildInjector();
$this->assertSame($binder, $childInjector->getBinder('BOUND_INTERFACE'));
}
private function _getTopLevelNeverCalledMock()
{
$topLevel = $this->getMock('Horde_Injector_TopLevel', array('getBinder', 'getInstance'));
$topLevel->expects($this->never())->method('getBinder');
return $topLevel;
}
}
/**
* Used by preceding tests
*/
class Horde_Injector_Binder_Mock implements Horde_Injector_Binder
{
private $_interface;
public function create(Horde_Injector $injector)
{
return $injector;
}
public function equals(Horde_Injector_Binder $otherBinder)
{
return $otherBinder === $this;
}
}
class Horde_Injector_Binder_MockWithDependencies implements Horde_Injector_Binder
{
private $_interface;
public function __construct($parameter1)
{
}
public function create(Horde_Injector $injector)
{
return $injector;
}
public function equals(Horde_Injector_Binder $otherBinder)
{
return $otherBinder === $this;
}
}
Horde_Injector-2.0.2/test/Horde/Injector/phpunit.xml 0000664 0001750 0001750 00000000306 12160052005 020511 0 ustar jan jan
../../../lib