Laravel 4 IoC Container

1 篇文章 0 订阅
1 篇文章 0 订阅
IoC stands for Inversion of Control, and it is effectively just a fancy way of describing a concept in Object Oriented programming where the logic behind how you instantiate a class is abstracted into a class itself, often referred to as a "container". In languages like Java and C#, this means that creating instances of classes is moved from compile time to run time, as instead of using the language itself to directly create an instance of a class e.g. "object = new Class()", you are telling your IoC container to do it, e.g. "object = ioc.get('Class')". Of course, this compile time vs run time effect of IoC isn't all that relevant for PHP, but it is nonetheless just as useful a concept in PHP.

Why is this a benefit? After all, using a language's "new" keyword to create an instance seems technically shorter, and possibly more efficient. The benefit comes when you design your OO code around the principles of Dependency Injection. I won't go into a lengthy dialogue about DI here, so if you're not familiar with the concept of DI you may want to go do some Googling, but in its most simple form, DI means writing classes that depend on instances of other classes in a manner which means they are not tightly coupled to those other classes. In other words, instead of creating instances of other classes inside a class, you feed instances into the class, typically via its constructor, but also possibly through setter methods. This means classes can be unit tested far easier, and it's generally just a better design in the 'separation of concerns' world of OO programming.

Say we have a Photo controller and injected its related DB in rather than the static way:

class Photo {
    /**
     * @var PDO The connection to the database
     */
    protected $db ;
 
    public function __construct() {}
 
    /**
     * Sets the database connection
     * @param PDO $dbConn The connection to the database.
     */
    public function setDB( $dbConn )  {
       $this ->db = $dbConn ;
   }
}
 
$photo = new Photo; 
$photo ->setDB( $dbConn );



There's one problem with using DI in this way: it makes the class considerably more difficult to work with. The user now must be fully aware of the class's dependencies, and must remember to set them, accordingly. Consider, down the line, when our fictional class requires a couple more dependencies. Well, following the rules of the dependency injection pattern, we'd have to do:

$photo = new Photo;
$photo ->setDB( $dbConn );
$photo ->setConfig( $config );
$photo ->setResponse( $response );

Yuck. The class my be more modular, but we've also piled on confusion and complexity. Before, the user could simply create a new instance of Photo, but, now, he has to remember to set all of these dependencies. This is where a IoC container comes in - the object coupling is bound at run time by an assembler object and is typically not known at compile time using static analysis. This is much nicer.

An IoC could be like this:

class IoC {
    /**
     * @var PDO The connection to the database
     */
    protected static $registry = array ();
 
    /**
     * Add a new resolver to the registry array.
     * @param  string $name The id
     * @param  object $resolve Closure that creates instance
     * @return void
     */
    public static function register( $name , Closure $resolve ) {

       static :: $registry [ $name ] = $resolve ;
   }
 
    /**
     * Create the instance
     * @param  string $name The id
     * @return mixed
     */
    public static function resolve( $name ) {

       if ( static ::registered( $name ) ) {

          $name = static :: $registry [ $name ];
          return $name ();
      }
 
       throw new Exception( 'Nothing registered with that name, fool.' );
   }
 
    /**
     * Determine whether the id is registered
     * @param  string $name The id
     * @return bool Whether to id exists or not
     */
    public static function registered( $name ) {

       return array_key_exists ( $name , static :: $registry );
   }
}

When the user calls the IoC::register method, they're adding an id, such as photo, and its associated resolver, which is just a lambda that creates the instance and sets any necessary dependencies on the class.

Now, we can register and resolve dependencies through the IoC class, like this:

// Add `photo` to the registry array, along with a resolver
IoC::register( 'photo' , function () {
    $photo = new Photo;
    $photo ->setDB( '...' );
    $photo ->setConfig( '...' );
 
    return $photo ;
});
 
// Fetch new photo instance with dependencies set
 $photo = IoC::resolve( 'photo' );

So, we can observe that, with this pattern, we're not manually instantiating the class. Instead, we register it with the IoC container, and then request a specific instance. This reduces the complexity that we introduced, when we stripped the hardcoded dependencies out of the class.

// Before
$photo = new Photo();
 
// After
$photo = IoC::resolve( 'photo' );

Back to Laravel 4. Not only does "L4" as it is referred to by its faithful offer a IoC container out of the box, but it basically encompasses the entire framework. In fact, if you  check out the L4 code on Github , the Application class that the framework operates within extends from the Container class, so the ability to bind a class instance implementation is absolutely core to the Laravel 4 framework. So what's the typical usage of it ?

Laravel 4 introduces the ruby-on-rails-like resource controller for handling all of the http request. If a user hit /resource url, the controller would serve him the resources through index() method for example. So the resource controller is responsible for public user requests. 

What if we want to add some of the methods for handling the resource Model and do not want it to be public to the users ? Instead of defining these methods within the controller itself, it is suggested to use a Repository contains these internal methods and inject it into resource controller: 

1. we should define an interface that contains all of the internal methods we need: 
 
interface SomeRepositoryInterface {
    public function  method1( $arg );
    public function  method2( $arg1, $arg2 );
   public function  method3( $arg1, $arg2, $arg3 );
    public function  method4( $arg1, $arg2, $arg3, $arg4 );
    public function  method5( $arg1, $arg2, $arg3, $arg4, $arg5 );
  });

2. make the SomeRepository to implements all of these contracts:

class  SomeRepository implements SomeRepositoryInterface {

    public function  method1( $arg ) {
      // I promise to implement it. 
   }

    public function  method2( $arg1, $arg2 ) {
      // I promise to implement it. 
   }

   public function  method3( $arg1, $arg2, $arg3 ) {
      // I promise to implement it. 
   }

    public function  method4( $arg1, $arg2, $arg3, $arg4 ) {
      // I promise to implement it.
   }

    public function  method5( $arg1, $arg2, $arg3, $arg4, $arg5 ) {
      // I promise to implement it.
   }

});

3. inject it into the constructor of our resource controller:

class  SomeController implements BaseController {

    protected $thing ;

    public function  __construct( SomeRepositoryInterface $thing ) {
       $this ->thing = $thing ;
   }

    public function  index() {
       return $this ->thing->method1( /* arguments */ );
   }

   public function  create() {
       return $this ->thing->method2( /* arguments */ );  
   }

    public function  store() {
       return $this ->thing->method3( /* arguments */ ); 
   }

    public function  show( $id ) {
       return $this ->thing->method4( /* arguments */ ); 
   }

    /* some other resources methods here... */

});

Here the constructor is like to say, anyone who implements the SomeRepositoryInterface would be injected into myself. 

4. Within the routes.php, use the App IoC container to bind our SomeRepository to our SomeRepositoryInterface:

   App:: bind ( 'SomeRepositoryInterface' , 'SomeRepository' );

   And the IoC container would smartly know that every time we say to instantiate the SomeRepositoryInterface, we want instantiate the SomeRepository actually.


Now, we've got a more flexible codebase structure: 
1. separate the concern of internal methods from public APIs gracefully. Whenever we want some entirely new implementations of  SomeRepositoryInterface, we can just define SomeOther RepositoryInterface  and bind it to the  SomeRepositoryInterface

2. dependency injection make it easier for unit testing.


[References]:  http://net.tutsplus.com/tutorials/php/dependency-injection-huh/
                               http://www.nathandavison.com/posts/view/16/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值