njasm/container

Dependency Container for PHP

Installs: 4 542

Dependents: 1

Stars: 12

Watchers: 2

Forks: 2

Open Issues: 5

Language: PHP

1.2.11 2015-08-18 02:13 UTC

README

Build Status Code Coverage Code Climate Scrutinizer Code Quality Latest Stable Version License HHVM Status

Features

  • Alias Service Keys support
  • Circular Dependency guard
  • Primitive data-types registration
  • Automatic Constructor Dependency Resolution and Injection for non-registered Services
  • Lazy and Eager instantiation approaches
  • Lazy and Eager Instantiation Singleton services registration
  • Support for public Setter injection/Method calls after service instantiation
  • Support for public Properties/Attributes Injection after Service instantiation
  • Ability to override existing dependency (Properties & Setters) declarations by supplying new ones when call to Container::get
  • Nested Providers/Containers support
  • Comply with Container-Interop interfaces

Requirements

  • PHP 5.3 or higher.

Installation

Include njasm\container in your project, by adding it to your composer.json file.

{
    "require": {
        "njasm/container": "~1.0"
    }
}

Usage

To create a container, simply instantiate the Container class.

use Njasm\Container\Container;

$container = new Container();

Using Alias

There are time that your key are too long to be convenient for your client code, one example for instance, is when binding an interface to an implementation or when using for your key the FQCN of your classes.

namespace Very\Long\Name\Space;

interface SomeInterface {}

class SomeImplementation implements SomeInterface
{
    // code here
}

$container = new Njasm\Container\Container();
$container->set('Very\Long\Name\Space\SomeInterface', new SomeImplementation());
$container->alias('Some', 'Very\Long\Name\Space\SomeInterface');

$some = $container->get('Some');

Defining Services

Services are defined with two params. A key and a value. The order you define your services is irrelevant.

Defining Services - Primitive data-types
$container->set("Username", "John");

echo $container->get("Username");
Defining Services - Binding Services (Lazy Loading)

You can bind a key to a instantiable FCQN value.

 $container->bind("MyKey", "\My\Namespace\SomeClass");

If you want to bind a Service, and register that Service as a Singleton Service.

 $container->bindSingleton("MyKey", "\My\Namespace\SomeClass");

Both Container::bind and Container::bindSingleton uses Lazy Loading approach, so that \My\Namespace\SomeClass will only be evaluated/instantiated when MyKey is requested.

When binding a service, constructor dependencies can be declared, public attributes be set and methods called with arguments, so they are injected/setted when instantiating the service.

namespace \App\Actors;

class Person {
    protected $name;
    protected $age = 24;
    public genre = 'Male';

    public function __construct($name = 'John') {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    public function getAge() {
        return $this->age;
    }

    public function setAge($age) {
        $this->age = (int) $age;
    }
}

$container->bind(
    "Person",                       // key
    "\App\Actors\Person",           // FQCN
    array("Jane"),                  // constructor dependencies
    array("genre" => "Female"),     // attributes injection
    array("setAge" => array(33))    // call methods
);

// binding with chaining methods 
$container->bind("Person", '\App\Actors\Person')
    ->setConstructorArguments(array("Jane"))        // setConstructorArgument($index, $argument)
    ->setProperty("genre" => "Female")              // setProperties(array("genre" => "Female", ...) also work
    ->callMethod("setAge", array(33));              // callMethods(array('methodName' => 'methodValue', ...));

// retrieving the object    
$person = $container->get("Person");
echo $person->getName(); // Jane
echo $person->getAge();  // 33
echo $person->genre      // Female

// calling services and overriding declared dependencies 
$person2 = $container->get(
    "Person", 
    array("Mark"), 
    array("genre" => "Male"), 
    array("setAge" => array(55))
);
echo $person2->getName(); // Mark
echo $person2->getAge();  // 55
echo $person2->genre      // Male
Defining Services - Objects (Eager Loading)
$mailer = new \Namespace\For\My\MailTransport(
    "smtp.example.com", 
    "username", 
    "password", 
    25
); 

$container->set(
    "Mail.Transport", 
    $mailer, 
    array(), // constructor args 
    array(), // public properties injection
    array("withSSL" => array(false)) // calling methods
);

$mailerTransport = $container->get("Mail.Transport");

Overwriting existent declared dependencies is also possible for set definitions.

// calling methods and injecting attributes is also possible
$mailerTransportSsl = $container->get(
    "Mail.Transport", 
    array(), 
    array(), 
    array("withSSL" => array(true))
);
Defining Services - Complex builds (Lazy Loading)

There are time when you'll want to instantiate an object, but the build process is reall complex and you want to control that process. You use anonymous functions for that.

$container->set(
    "Complex",
    function($firstName = "John", $lastName = "Doe") {
        // complex logic here
        // ...
        $theComplexObject = new Complex($firstName, $lastName);

        return $theComplexObject;
    }
);

$complex = $container->get("Complex");

// injecting closure dependencies is also possible
$complexJane = $container->get("Complex", array("Jane", "Fonda")); 
Defining Services - Complex builds With Nested Dependencies (Lazy Loading)

Creation of nested dependencies is also possible. You just need to pass the container to the closure.

$container->set(
    "Mail.Transport",
    function() use (&$container) {
        return new \Namespace\For\My\MailTransport(
            $container->get("Mail.Transport.Config")
        );
    }
);

$container->set(
    "Mail.Transport.Config",
    function() {
        return new \Namespace\For\My\MailTransportConfig(
            "smtp.example.com", 
            "username", 
            "password", 
            25
        );
    }
);

$mailer = $container->get("Mail.Transport");
Defining Singleton Services

For registering singleton services, you use the singleton method invocation.

$container->singleton(
    "Database.Connection",
    function() {
        return new \Namespace\For\My\Database(
            "mysql:host=example.com;port=3306;dbname=your_db", 
            "username", 
            "password"
        );
    }
);

// MyDatabase is instantiated and stored, for future requests to this service, 
// and then returned.
$db = $container->get("Database.Connection");
$db2 = $container->get("Database.Connection");

// $db === $db2 TRUE

Defining Sub/Nested Containers

Nesting container is possible as long as you use an existing Container Adapter for your application existing container. The Adapter class must implement the ServicesProviderInterface for more examples please see the Adapterfolder.

$pimple; // is your instantiated pimple container
$pimple["Name"] = $pimple->factory(function() {
   return "John";
}

$pimpleAdapter = new \Njasm\Container\Adapter\PimpleAdapter($pimple);
$mainContainer = new \Njasm\Container\Container();
$mainContainer->provider($pimpleAdapter);

$mainContainer->has("Name"); // TRUE
echo $mainContainer->get("Name"); // John

Automatic Resolution of Services

When the Container is requested for a service that is not registered, it will try to find the class, and will automatically try to resolve your class's constructor dependencies.

namespace My\Name\Space;

class Something
{
    // code
}

// without registering the Something class in the container you can...
$container = new Njasm\Container\Container();
$something = $container->get('My\Name\Space\Something');

//$something instanceof 'My\Name\Space\Something' == true

//once again you can also inject dependencies when calling get method.
$something = $container->get(
    "My\Name\Space\Something", 
    array("constructor value 1", "constructor value 2"),
    array("attributeName" => "value 1"), // attributes
    array("methodName" => array("value 1", "value 2"))
);

Roadmap

In no Particular order - check Milestones for a more organized picture.

  • Load definitions from configuration files
  • Optimizations

Contributing

Do you wanna help on feature development/improving existing code through refactoring, etc? Or wanna discuss a feature/bug/idea? Issues and Pull Requests are welcome as long as you follow some guidelines for PRs:

Pull Requests must:

  • Be PSR-2 compliant.
  • Submit tests with your pull request to your own changes / new code introduction.
  • having fun.