Yii Dependency Injection Container - Tutorial

Recenlty I was looking for a solution to integrate e dependency injection pattern into yii without modify the core. Today I just integrated a little class, well known and written by Fabien Potencier, Pimple.

I will explain here all the steps to add a simple dependency injection into your application that is also capable to retrieve Yii components and parameters.

1. Install Pimple

Here is the github page: Pimple, you can use composer or just download the single class and put that into your components (or whatever) directory.

2. Extend Pimple

Create a Class CContainer that extends Pimple and put it into components.


class CContainer extends \Pimple

{

    /**

     * Retrieve parameter/service.

     *

     * @param string $id id of parameter/service

     * to retrieve Yii specific param/service just use an id like "yii.core.paramName"

     *

     * @return mixed

     */

    public function get($id)

    {

        $element = null;

        if (strpos($id, 'yii.core.') !== false) {

            $id = str_replace('yii.core.', '', $id);

            $element = Yii::app()->{$id};

        } else {

            $element = $this[$id];

        }

        return $element;

    }


    /**

     * Set a new parameter/service

     * 

     * @param string $id    id of parameter/service

     * @param mixed  $value value or callable

     * 

     * @return void

     */

    public function set($id, $value)

    {

        $this[$id] = $value;

    }

}

3. Extends CWebApplication


class WebApplication extends CWebApplication

{

    /**

     * @var \Pimple dependency injection container

     */

    protected $_container = null;


    /**

     * Configure DiC.

     * 

     * @param array $config configuration parameters

     * it consists of two main parameters:

     * <pre>

     * array(

     *     'class' => 'CContainer',

     *     'services' => array(

     *         's1' => function($c) {

     *             return new S1();

     *         },

     *         's2' => function($c) {

     *             return new S2($c['s1');

     *         }

     *     ),

     * )

     * </pre>

     * @return void

     */

    public function setContainer(array $config)

    {

        $container = isset($config['class']) && !empty($config['class']) ? $config['class'] : 'CContainer';

        $services = isset($config['services']) && is_array($config['services']) && !empty($config['services'])

            ? $config['services']

            : array();

        $this->_container = new $container($services);

    }


    /**

     * Retrieve the DiC container.

     * 

     * @return CContainer

     */

    public function getContainer()

    {

        if ($this->_container === null) {

            $this->_container = new CContainer();

        }

        return $this->_container;

    }

}

4. Change the entry scripts

We need now to change the index.php, index-test.php and test/bootstrap.php to use our new WebApplication Class.

Just change this


Yii::createWebApplication($config)->run();

into




require_once('pathtoprotected/components/WebApplication.php');

Yii::createApplication('WebApplication', $config)->run();

5. Now its time for configuration!

The last step is set the configuration elements we need




// import your files here or use an auoloader (better)

Yii::import('application.components.container.Pimple', true);

Yii::import('application.components.container.CContainer', true);


return array(

    'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..',

    'name' => 'Yii Dependency Injection Integration',

    // application modules

    'modules' => array(

        // ...

    ),

    // application components

    'components' => array(

        // ...

    ),

    // dipendency injection configuration

    'container' => array(

        'class' => 'CContainer',

        'services' => array(

            // ... put here your services

        );

    ),

    // application-level parameters that can be accessed

    'params' => array(

        // ....

    ),

);



Now if everything is set up correctly you can access your dic in these ways:




// retrieve services1

$service_1 = Yii::app()->getContainer()->get('s1');

// retrieve yii urlManager

$url_manager = Yii::app()->getContainer()->get('yii.core.urlManager');



Hope you enjoy this tutorial!!! Feel free to ask for help!!! B)

Nice thought.

It would be great to have it in the core (maybe yii2) with:

Yii::app()->get(‘SERVICE’) or Yii::app()->{ID} as actual shortcuts to Container::get() method.

DICs sounds as a very natural asset for Yii.

Cool, i was looking around for Dependency Injection to my Yii project, it helped a lot

It is much easier if u use composer for loading the components, the two import line becomes unnecessary at the top of the config file

The get method is nice in PHP versions before array dereferencing

I use CWebApplication as Service container, if I have a complicated business logic I decompose it in small classes with CComponent and put everything together in a a Service class which is a CApplicationComponent and most of time this business logic can be accessed trough a module (I think CWebModule is a good facade).

If this yiipimple is used, then how we can configure bootstrap file for phpunit?

I’m still getting my feet wet with Yii v1.1 and attempting to follow this tutorial.

I’m having issues “initializing” the container

If I put the config snippet in the root of the config array then


Yii::app()->getContainer();

just returns the config value




array(

	'class' => 'CContainer',

	'services' => array(

	     // my services

	),

)



If I put the config snippet in the ‘components’ “subsection” of the config then


Yii::app()->getContainer();

returns a CContainer instance, but without any of the services I defined in the config

how do I define the services in the config?

is the WebApplication::setContainer method supposed to be called automatically?

Solved my issue by updating getContainer()




    /**

     * Retrieve the DiC container.

     *

     * @return CContainer

     */

    public function getContainer()

    {

        if (is_array($this->container)) {

            // Container hasn't been initiated yet

            $this->setContainer($this->container);

        }

        if ($this->container === null) {

            $this->container = new CContainer();

        }

        return $this->container;

    }