spiffy/spiffy-inject

This package is abandoned and no longer maintained. No replacement package was suggested.

Spiffy\Inject is a light-weight, HHVM compatible, and dependency lite DI container.

1.0.0-alpha 2014-07-17 01:58 UTC

This package is not auto-updated.

Last update: 2022-02-01 12:33:17 UTC


README

Build Status Code Coverage Scrutinizer Code Quality

Installation

Spiffy\Inject can be installed using composer which will setup any autoloading for you.

composer require spiffy/spiffy-inject

Additionally, you can download or clone the repository and setup your own autoloading.

Introduction

Spiffy\Inject is a light-weight, HHVM compatible, and dependency lite dependency injection (DI) container. You can read more about DI on Wikipedia or Martin Fowler's website. Spiffy\Inject aims to help you manage your parameters and services.

Parameters

use Spiffy\Inject\Injector;

$i = new Injector();

// assign a parameter is as easy as using ArrayAccess
$i['foo'] = 'bar';

// output is 'bar'
echo $i['foo'];

Services

The primary purpose of Spiffy\Inject is for managing your services. You can create services in one of three ways:

  • Setting using a string class name
  • Setting the service directly
  • Creating the service through a factory closure
  • Using the array configuration
  • Using an object that implements ServiceFactory
  • Using annotations combined with a generator.

All services are set through the nject method regardless of which style you choose. Each style has it's own advantages and disadvantages. It's you to you to decide which is the best approach to take for your application.

Setting Services

$i = new Injector();

// setting using the string class name
$i->nject('foo', 'StdClass');

// setting the service directly
$i->nject('foo', new \StdClass());

// setting the service using a closure factory
$i->nject('foo', function() {
  return new \StdClass();
});

// setting the service using array configuration
$i->nject('foo', ['StdClass']);

// setting the service using an object that implements ServiceFactory
class StdClassFactory implements ServiceFactory
{
    public function createService(Injector $i)
    {
        return new \StdClass();
    }
}
$i->nject('foo', new StdClassFactory());

// each method listed above is identical

Testing if a service exists

// false
$i->has('foo');

$i->nject('foo', new \StdClass());

// true
$i->has('foo');

Getting Services

// assuming the configuration from 'Setting Services' above
// the following retrieves the 'foo' service
$foo = $i->nvoke('foo');

Array Configuration

The array configuration has some additional options available to make it extremely flexible.

Constructor injection

$i = new Injector();

// you can pass constructor parameters to the service
class Foo
{
    public function __construct($string, $int)
    {
        $this->string = $string;
        $this->int = $int;
    }
}

// the resulting object will have 'string set to 'I am a string'
// and 'int' set to '1'
$i->nject('foo', ['Foo', ['I am a string', 1]]);

Setter injection

$i = new Injector();

// you can pass constructor parameters to the service
class Foo
{
    public function __construct($int)
    {
        $this->int = $int;
    }

    public function setString($string)
    {
        $this->string = $string;
    }
}

// the resulting object will have 'string set to 'I am a string'
// and 'int' set to '1'
$i->nject('foo', ['Foo',[1],['setString' => 'string']]);

Referencing other services and parameters

Spiffy\Inject's array configuration includes the ability to reference other services when the string is prepended with special characters. By default you reference services with the @ symbol and parameters with the $ symbol. These can be modified using the setServiceIdentifier and setParamIdentifier methods respectively.

$i = new Injector();

class Bar
{
}

class Foo
{
    public function __construct(Bar $bar)
    {
        $this->bar = $bar;
    }

    public function setBaz($baz)
    {
        $this->baz = $baz;
    }
}

// set the 'baz' parameter to 'boogly'
$i['baz'] = 'boogly';

// set the 'bar' service to an instance of \Bar
$i->nject('bar', new \Bar());

// create the foo service using array configuration and parameter/service references
$i->nject('foo', ['Foo',['@bar'],['setBaz' => '$baz']]);

// the resulting Foo service would have '$this->bar' set to '\Bar' and '$this->baz' set to 'boogly'

ServiceFactories

If you return an instance of Spiffy\Inject\ServiceFactory from an array configuration it will automatically create the instance for you and return that instead. This let's you inject parameters into Service Factories easily and reuse factories while still returning the instance you want.

class ArrayObjectFactory implements ServiceFactory
{
    private $defaults;

    public function __construct(array $defaults)
    {
        $this->defaults = $defaults;
    }

    public function createService(Injector $i)
    {
        return new \ArrayObject($this->defaults);
    }
}

$i = new Injector();

// Result is an ArrayObject and *not* an ArrayObjectFactory
$i->nject('ArrayObject', ['ArrayObjectFactory', [['foo' => 'bar']]);

Annotations combined with a generator

SpiffyInject provides annotations that you can use to assist in creating configurations for services.

namespace Spiffy\Inject\TestAsset;

use Spiffy\Inject\Annotation as Injector;

/**
 * @Injector\Component("inject.test-asset.annotated-component")
 */
class AnnotatedComponent
{
    /** @var \StdClass */
    private $foo;
    /** @var array */
    private $params;
    /** @var array */
    private $setter;
    
    /**
     * @Injector\Method({@Injector\Inject("foo"), @Injector\Param("params")})
     */
    public function __construct(\StdClass $foo, array $params)
    {
        $this->foo = $foo;
        $this->params = $params;
    }

    /**
     * @Injector\Method({@Injector\Param("setter")})
     * 
     * @param array $setter
     */
    public function setSetter($setter)
    {
        $this->setter = $setter;
    }

    /**
     * @return array
     */
    public function getSetter()
    {
        return $this->setter;
    }

    /**
     * @return \StdClass
     */
    public function getFoo()
    {
        return $this->foo;
    }

    /**
     * @return array
     */
    public function getParams()
    {
        return $this->params;
    }
}

Now that you have an annotated class use the metadata factory to create metadata for the class and a generator to generate the Injector configuration.

use Spiffy\Inject\Generator;
use Spiffy\Inject\Metadata;

$mdf = new Metadata\MetadataFactory();
$md = $mdf->getMetadataForClass('Spiffy\Inject\TestAsset\AnnotatedComponent');

$generator = new Generator\ArrayGenerator();

$i = new Injector();
$i->nject($md->getName(), $generator->generate($md));

$i->nvoke($md->getName()); // instanceof Spiffy\Inject\TestAsset\AnnotatedComponent

Reading files, creating metadata, and generating configuration is a heavy process and is not intended for production. You should cache the results of the generation and use the cache in production.

Decorating your services

Sometimes you want to over-ride the services set your DI container without modifying the original configuration. Spiffy\Inject handles this by providing you with two types of decorators.

Decorate

The decorate method allows you to take the service created and apply any modifications to it prior to having it returned. The decorate closure receives the injector and service as arguments.

$i = new Injector();
$i->nject('foo', new \StdClass());
$i->decorate('foo', function(Injector $i, \StdClass $foo) {
    $foo->bar = 'bar';
    $foo->baz = 'baz';
});

$foo = $i->nvoke('foo');

// output is 'barbaz';
echo $foo->bar;
echo $foo->baz;

Alternatively, you can provide an instance of a class implementing the Spiffy\Inject\ServiceDecorator interface:

namespace My;

use Spiffy\Inject\Injector;
use Spiffy\Inject\ServiceDecorator;

class FooDecorator implements ServiceDecorator
{
    public function decorateService(Injector $i, $instance)
    {
        $instance->bar = 'bar';
        $instance->baz = 'baz';
    }
}
use Spiffy\Inject\Injector;

$i = new Injector();
$i->nject('foo', new \StdClass());
$i->decorate('foo', new \My\FooDecorator());

$foo = $i->nvoke('foo');

// output is 'barbaz';
echo $foo->bar;
echo $foo->baz;

Wrap

The wrap method is much more powerful than decorate. Wrapping let's you completely change the object that's created or completely bypass the original configuration. The wrap closure receives three arguments: the injector, the name of the service, and the callable that creates the service.

$i = new Injector();
$i->nject('foo', new \StdClass());

// if we use the $callable available to the closure we receive an instance of the original service
// the \StdClass object would have two properties: 'bar' and 'name'
// the values would be 'bar' and 'foo' respectively
$i->wrap('foo', function(Injector $i, $name, $callable) {
    $foo = $callable();
    $foo->bar = 'bar';
    $foo->name = $name;

    return $foo;
});

// we can completely override the original service configuration by skipping the callable
$i->wrap('foo', function(Injector $i, $name, $callable) {
    return new \ArrayObject();
});

// output is 'ArrayObject'
echo get_class($i->nvoke('foo'));

Alternatively, you can provide an instance of a class implementing the Spiffy\Inject\ServiceWrapper interface:

namespace My;

use Spiffy\Inject\Injector;
use Spiffy\Inject\ServiceWrapper;

class FooWrapper implements ServiceWrapper
{
    public function wrapService(Injector $i, $name, $callable)
    {
        $foo = $callable();
        $foo->bar = 'bar';
        $foo->name = $name;

        return $foo;
    }
}
use Spiffy\Inject\Injector;

$i = new Injector();
$i->nject('foo', new \StdClass());
$i->wrap('foo', new \My\FooWrapper());

$foo = $i->nvoke('foo');

echo $foo->bar; // outputs 'bar'
echo $foo->name; // outputs 'foo'

Why nvoke and nject?

Because it's damn cute, that's why! If you prefer, though, you can use set() instead as nject() and get() instead of nvoke().