phpdependency-injectionpimple

How do I best use an object factory inside another class using Pimple for Dependency Injection?


I am still trying to wrap my head around some of the aspects of the Dependency Injection design pattern using Pimple. I completely get the concept of using a constructor or setter function belonging to class Foo to establish it's dependency on class Bar.

The part I don't quite get is how to properly instantiate multiple new instances of class Bar from inside a method belonging to Foo while using a Pimple factory.

Basically I want to accomplish the equivalent of this:

Block.php

class Block {

     private $filepath;

     public function setFilePath($filepath) {
          $this->filepath = $filepath;
     }

}

Page.php

class Page {

     private function newBlock() {
          //do something here to get $filepath
          $block = new Block();
          $block->setFilePath($filepath);
          return $block;
     }

}

I am using Pimple for my container like so:

bootstrap.php

$container = new Container();

$container['page'] = $container->factory(function ($c) {
    return new Page();
});

$container['block'] = $container->factory(function ($c) {
     return new Block();
});

The idea is that there can be multiple pages defined, and each page is potentially comprised of multiple blocks. The properties for each block are defined by methods within Page. And it needs to happen using entirely decoupled code.

It is my understanding that injecting the entire container into Page as a dependency is actually the Service Locator anti-pattern. So the following is BAD code:

class Page {

     private $container;

     public function __construct(Container $container) {
          $this->container = $container;
     }

     private function newBlock() {
          //do something here to get $filepath
          $block = $this->container['block'];
          $block->setFilePath($filepath);
          return $block;
     }

}

How do I go about giving Page the ability to use the Block factory that is defined in the DIC?


Solution

  • So, in your DI container setup, you can pass around container references. So if you have the need to use the Block class a lot in your page class, you could do this:

    $container = new Container();
    
    $container['page'] = $container->factory(function ($c) {
        return new Page( $c['block'] );
    });
    
    $container['block'] = $container->factory(function ($c) {
         return new Block();
    });
    

    But this means you must add a parameter to your Page class's constructor:

    class Page {
    
        private $block = NULL;
    
        public function __construct( $block )
        {
          $this->block = $block;
        }
    
         private function newBlock() {
              //do something here to get $filepath
              $this->block->setFilePath($filepath);
              return $this->block;
         }
    
    }
    

    So, we're not actually passing the entire container into each class, but rather allowing the DI container to pass objects as needed.