GithubHelp home page GithubHelp logo

ttskch / ray.di Goto Github PK

View Code? Open in Web Editor NEW

This project forked from ray-di/ray.di

0.0 3.0 0.0 1.35 MB

Guice style dependency injection framework for PHP

License: BSD 2-Clause "Simplified" License

PHP 100.00%

ray.di's Introduction

Ray.Di

Dependency Injection framework for PHP

Latest Stable Version Latest Unstable Version Scrutinizer Code Quality Code Coverage Build Status

Ray.Di was created in order to get Guice style dependency injection in PHP projects. It tries to mirror Guice's behavior and style. Guice is a Java dependency injection framework developed by Google.

Getting Stated

Linked Bindings

Here is a basic example of dependency injection using Ray.Di.

namespace MovieApp;

use Ray\Di\AbstractModule;
use Ray\Di\Di\Inject;
use Ray\Di\Injector;
use MovieApp\FinderInterface;
use MovieApp\Finder;

interface FinderInterface
{
}

interface ListerInterface
{
}

class Finder implements FinderInterface
{
}

class Lister implements ListerInterface
{
    public $finder;

    /**
     * @Inject
     */
    public function setFinder(FinderInterface $finder)
    {
        $this->finder = $finder;
    }
}

class ListerModule extends AbstractModule
{
    public function configure()
    {
        $this->bind(FinderInterface::class)->to(Finder::class);
        $this->bind(ListerInterface::class)->to(Lister::class);
    }
}

$injector = new Injector(new ListerModule);
$lister = $injector->getInstance(ListerInterface::class);
$works = ($lister->finder instanceof Finder::class);
echo(($works) ? 'It works!' : 'It DOES NOT work!');

// It works!

This is an example of Linked Bindings. Linked bindings map a type to its implementation, it can also be chained.

Provider Bindings

Provider bindings map a type to its provider.

$this->bind(TransactionLogInterface::class)->toProvider(DatabaseTransactionLogProvider::class);

The provider class implements Ray's Provider interface, which is a simple, general interface for supplying values:

namespace Ray\Di;

interface ProviderInterface
{
    public function get();
}

Our provider implementation class has dependencies of its own, which it receives via a contructor annotated with @Inject. It implements the Provider interface to define what's returned with complete type safety:

use Ray\Di\Di\Inject;
use Ray\Di\ProviderInterface;

class DatabaseTransactionLogProvider implements ProviderInterface
{
    private $connection;

    /**
     * @Inject
     */
    public function __construct(ConnectionInterface $connection)
    {
        $this->connection = $connection;
    }

    public function get()
    {
        $transactionLog = new DatabaseTransactionLog;
        $transactionLog->setConnection($this->connection);

        return $transactionLog;
    }
}

Finally we bind to the provider using the toProvider() method:

$this->bind(TransactionLogInterface::class)->toProvider(DatabaseTransactionLogProvider::class);

Named Binding

Ray comes with a built-in binding annotation @Named that takes a string.

use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;

/**
 *  @Inject
 *  @Named("processor=checkout")
 */
public RealBillingService(CreditCardProcessorInterface $processor)
{
...

To bind a specific name, pass that string using the annotatedWith() method.

protected function configure()
{
    $this->bind(CreditCardProcessorInterface::class)->annotatedWith('checkout')->to(CheckoutCreditCardProcessor::class);
}

You need to specify in case of multiple parameter.

use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;

/**
 *  @Inject
 *  @Named("processor=checkout,backup=subProcessor")
 */
public RealBillingService(CreditCardProcessorInterface $processor, CreditCardProcessorInterface $subProcessor)
{
...

Instance Bindings

protected function configure()
{
    $this->bind(UserInterface::class)->toInstance(new User);
}

You can bind a type to an instance of that type. This is usually only useful for objects that don't have dependencies of their own, such as value objects:

protected function configure()
{
    $this->bind()->annotatedWith("login_id")->toInstance('bear');
}

Untargeted Bindings

You may create bindings without specifying a target. This is most useful for concrete classes. An untargetted binding informs the injector about a type, so it may prepare dependencies eagerly. Untargetted bindings have no to clause, like so:

protected function configure()
{
    $this->bind(MyConcreteClass::class);
    $this->bind(AnotherConcreteClass::class)->in(Scope::SINGLETON);
}

note: annotations is not supported Untargeted Bindings

Constructor Bindings

Occasionally it's necessary to bind a type to an arbitrary constructor. This comes up when the @Inject annotation cannot be applied to the target constructor: either because it is a third party class, or because multiple constructors that participate in dependency injection. Provider Binding provide the solution to this problem. By calling your target constructor explicitly, you don't need reflection and its associated pitfalls. But there are limitations of that approach: manually constructed instances do not participate in AOP.

To address this, Ray.Di has toConstructor bindings.

<?php
class Car
{
    public function __construct(EngineInterface $engine, $carName)
    {
        // ...
<?php
protected function configure()
{
    $this->bind(EngineInterface::class)->annotatedWith('na')->to(NaturalAspirationEngine::class);
    $this->bind()->annotatedWith('car_name')->toInstance('Eunos Roadster');
    $this
        ->bind(CarInterface::class)
        ->toConstructor(
            Car::class,
            'engine=na,carName=car_name' // varName=BindName,...
        );
}

In this example, the Car have a constructor which name bound with engine=na,carName=car_name. That constructor does not need an @Inject annotation. Ray.Di will invoke that constructor to satisfy the binding.

Scopes

By default, Ray returns a new instance each time it supplies a value. This behaviour is configurable via scopes. You can also configure scopes with the @Scope annotation.

use Ray\Di\Scope;

protected function configure()
{
    $this->bind(TransactionLogInterface::class)->to(InMemoryTransactionLog::class)->in(Scope::SINGLETON);
}

Object life cycle

@PostConstruct is used on methods that need to get executed after dependency injection has finalized to perform any extra initialization.

use Ray\Di\Di\PostConstruct;

/**
 * @PostConstruct
 */
public function init()
{
    //....
}

Automatic Injection

Ray.Di automatically injects all of the following:

  • instances passed to toInstance() in a bind statement
  • provider instances passed to toProvider() in a bind statement

The objects will be injected while the injector itself is being created. If they're needed to satisfy other startup injections, Ray.Di will inject them before they're used.

Aspect Oriented Programing

To mark select methods as weekdays-only, we define an annotation .

/**
 * NotOnWeekends
 *
 * @Annotation
 * @Target("METHOD")
 */
final class NotOnWeekends
{
}

...and apply it to the methods that need to be intercepted:

class BillingService
{
    /**
     * @NotOnWeekends
     */
    chargeOrder(PizzaOrder $order, CreditCard $creditCard)
    {

Next, we define the interceptor by implementing the org.aopalliance.intercept.MethodInterceptor interface. When we need to call through to the underlying method, we do so by calling $invocation->proceed():

use Ray\Aop\MethodInterceptor;
use Ray\Aop\MethodInvocation;

class WeekendBlocker implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        $today = getdate();
        if ($today['weekday'][0] === 'S') {
            throw new \RuntimeException(
                $invocation->getMethod()->getName() . " not allowed on weekends!"
            );
        }
        return $invocation->proceed();
    }
}

Finally, we configure everything. In this case we match any class, but only the methods with our @NotOnWeekends annotation:

use Ray\Di\AbstractModule;

class WeekendModule extends AbstractModule
{
    protected function configure()
    {
        $this->bindInterceptor(
            $this->matcher->any(),
            $this->matcher->annotatedWith('NotOnWeekends'),
            [WeekendBlocker::class]
        );
    }
}

$injector = new Injector(new WeekendModule);
$billing = $injector->getInstance(BillingServiceInterface::class);
try {
    echo $billing->chargeOrder();
} catch (\RuntimeException $e) {
    echo $e->getMessage() . "\n";
    exit(1);
}

Putting it all together, (and waiting until Saturday), we see the method is intercepted and our order is rejected:

RuntimeException: chargeOrder not allowed on weekends! in /apps/pizza/WeekendBlocker.php on line 14

Call Stack:
    0.0022     228296   1. {main}() /apps/pizza/main.php:0
    0.0054     317424   2. Ray\Aop\Weaver->chargeOrder() /apps/pizza/main.php:14
    0.0054     317608   3. Ray\Aop\Weaver->__call() /libs/Ray.Aop/src/Weaver.php:14
    0.0055     318384   4. Ray\Aop\ReflectiveMethodInvocation->proceed() /libs/Ray.Aop/src/Weaver.php:68
    0.0056     318784   5. Ray\Aop\Sample\WeekendBlocker->invoke() /libs/Ray.Aop/src/ReflectiveMethodInvocation.php:65

You can bind interceptors in variouas ways as follows.

use Ray\Di\AbstractModule;

class TaxModule extends AbstractModule
{
    protected function configure()
    {
        $this->bindInterceptor(
            $this->matcher->annotatedWith('Tax'),
            $this->matcher->any(),
            [TaxCharger::class]
        );
    }
}
use Ray\Di\AbstractModule;

class AopMatcherModule extends AbstractModule
{
    protected function configure()
    {
        $this->bindInterceptor(
            $this->matcher->any(),                 // In any class and
            $this->matcher->startWith('delete'),   // ..the method start with "delete"
            [Logger::class]
        );
    }
}

Installation

A module can install other modules to configure more bindings.

  • Earlier bindings have priority even if the same binding is made later.
  • override bindings in that module have priority.
protected function configure()
{
    $this->install(new OtherModule);
    $this->override(new CustomiseModule);
}

Injection in the module

You can use a built-in injector in the module which uses existing bindings.

protected function configure()
{
    $this->bind(DbInterface::class)->to(Db::class);
    $dbLogger = $this->requestInjection(DbLogger::class);
}

Best practice

Your code should deal directly with the Injector as little as possible. Instead, you want to bootstrap your application by injecting one root object. The class of this object should use injection to obtain references to other objects on which it depends. The classes of those objects should do the same.

Performance boost

インジェクターオブジェクトをシリアライズすると、束縛の最適化が行われます。 unserializeして利用したインジェクターではパフォーマンスが向上します。

// save
$injector = new Injector(new ListerModule);
$cachedInjector = serialize($injector);

// load
$injector = unserialize($cachedInjector);
$lister = $injector->getInstance(ListerInterface::class);

Requirements

  • PHP 5.4+
  • hhvm

Installation

The recommended way to install Ray.Di is through Composer.

# Add Ray.Di as a dependency
$ composer require ray/di ~2.0@dev

Testing Ray.Di

Here's how to install Ray.Di from source and run the unit tests and demos.

$ composer create-project ray/di:~2.0@dev Ray.Di
$ cd Ray.Di
$ phpunit
$ php docs/demo/run.php

ray.di's People

Contributors

koriym avatar madapaja avatar mackstar avatar harikt avatar jamolkhon avatar craigjbass avatar lorenzo avatar mugeso avatar scrutinizer-auto-fixer avatar yuya-takeyama avatar holyshared avatar fivestar avatar vlakarados avatar nishigori avatar

Watchers

James Cloos avatar Takashi Kanemoto avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.