Giter VIP home page Giter VIP logo

dependency-pocket's Introduction

DependencyPocket

Build Status Latest Stable Version Latest Unstable Version License

A new form of "Dependency Injection" in PHP

Not to be confused with Dependency Injection Container or Ioc Container like Symfony DI Container, Pimple or Laravel Service Container,

DependencyPocket is not a DI Container in that way, It's rather a new form of Dependency Injection. Essentially, there're two ways of dependency injection : Constructor Injection and Setter Injection, I'd like to think of this technique as the third type, the Pocket Injection.

Installation

Using composer

composer require mmghv/dependency-pocket "~0.2"

usage

We use this pocket inside our classes to hold the dependencies, we first create a new pocket :

use mmghv\DependencyPocket;

// ...

$this->pocket = new DependencyPocket();

Then we add the dependencies, We first define them (defining the Name and the Type of each dependency) :

$this->pocket->define([
    'dep1',  // allow any type
    'dep2' => 'string',  // primitive type
    'dep3' => 'array',  // primitive type
    'dep4' => 'App\Model\Article'  // class or interface
]);

Then we set our dependencies (set dependencies values) :

$this->pocket->set([
    'dep1' => true,
    'dep2' => 'some value',
    'dep3' => [1, 2],
    'dep4' => $myArticle
]);

Then when we want to get a dependency we simply do :

$dep = $this->pocket->get('myDep');
// or
$dep = $this->pocket->myDep;

Or we can use Property Overloading to easily access our dependencies from our class (or subclasses) :

public function __get($name)
{
    if ($this->pocket->has($name)) {
        return $this->pocket->get($name);
    } else {
        throw new \Exception("Undefined property: [$name]");
    }
}

When it's useful and the technique to use it

Basically Its useful when your class has many dependencies which not all of them are required so you don't want your constructor to have all these dependencies but still need a way to easily change them in the subclasses and tests.

Imagine you have a class with 5 dependencies but only 2 of them are essential and the other 3 can be set to some default values, Then you extend this class and the subclass can resolve one of the two essential dependencies to a default value and needs to replace one of the optional dependencies of the parent class, You need to be able to do that and want your final class to have only one dependency in the constructor but still be able to change any default ones from any future subclasses as well as the ability to mock any of these dependencies in the tests.

Using DependencyPocket you can achieve that like the following without using a setter for each dependency and also mocking dependencies for tests is easier than using Setter Injection method :

// Manager.php

namespace App\Managers;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use mmghv\DependencyPocket;

class Manager
{
    protected $pocket;

    /**
     * Define class dependencies.
     */
    protected function defineDependencies()
    {
        if ($this->pocket) {
            return true;
        }

        $this->pocket = new DependencyPocket();

        $this->pocket->define([
            'app'           => 'Laravel\Lumen\Application',
            'model'         => 'Illuminate\Database\Eloquent\Model',
            'validator'     => 'Illuminate\Contracts\Validation\Factory',
            'request'       => 'Illuminate\Http\Request',
            'redirectUrl'   => 'string',
        ]);
    }

    /**
     * Create new manager.
     *
     * @param  Application $app
     * @param  Model       $model
     * @param  array       $dependencyPocket
     */
    public function __construct(Application $app, Model $model, array $dependencyPocket = [])
    {
        $this->defineDependencies();

        $this->pocket->set($dependencyPocket += [
            'app'           => $app,
            'model'         => $model,
            'validator'     => $app->make('validator'),  // default value
            'request'       => $app->make('request'),  // default value
            'redirectUrl'   => $app->make('session')->previousUrl(),  // default value
        ]);
    }
}
// ArticleManager.php

namespace App\Managers;

use Illuminate\Contracts\Foundation\Application;
use App\Models\Article;

class ArticleManager extends Manager
{

    /**
     * Define any additional class dependencies, Declare this function
     * only when you need to define new dependencies.
     */
    protected function defineDependencies()
    {
        if (parent::defineDependencies()) {
            return true;
        }

        $this->pocket->define([
            // define any new dependencies for this class
        ]);
    }

    /**
     * Create new article-manager.
     *
     * @param  Application $app
     * @param  array       $dependencyPocket
     */
    public function __construct(Application $app, array $dependencyPocket = [])
    {
        // always call this first
        $this->defineDependencies();

        // default value for $model dependency
        $model = new Article();

        // call parent construct and pass dependencies
        parent::__construct($app, $model, $dependencyPocket += [
            'validator'     => new CustomValidator(), // replace default dependency value
            // add any new dependencies for this calss, needs to be defined first in 'defineDependencies()'
        ]);
    }
}

Then when we instantiate the ArticleManager class we only need to pass one dependency like this :

$manager = new ArticleManager($app);

But also we have the ability to easily replace any default dependencies when we want like this :

$manager = new ArticleManager($app, [
    'model' => $anotherModel,
    'validator' => $anotherValidator
]);

Contributing

This is a relatively new technique so any contributions (suggestions, enhancements to the technique used) are welcome. PSR-2 standards are used and tests should cover any changes in case of PRs.

License & Copyright

Copyright © 2016, Mohamed Gharib. Released under the MIT license.

dependency-pocket's People

Contributors

mmghv avatar

Stargazers

 avatar

Watchers

James Cloos 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.