Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using dependency injection in a PHP web application

I have been reading a lot about dependency injection recently, and in theory it has made a lot of sense. The idea of having easy to test, single responsibility classes just sounds smart. However, I'm struggling with how far to take it.

I'm working on a PHP web application right now that has a CMS component and is heavily database driven. Within the CMS, you can create pages, and the pages themselves are built using various widgets. The widgets can be straight HTML (from a WYSIWYG editor), or can be a little more complex, like a photo gallery. And this is the important part, the photo gallery widget depends on other db models, like the PhotoGallery and PhotoGalleryImages in order to function properly.

According to Miško Hevery, I should not be passing references to dependencies through the different layers of my application. Instead, each class should simply "ask for" whatever dependencies it requires. Using his example, this is how this looks:

$db = new Database()
$repo = new UserRepository($db);
$page = new LoginPage($repo);

While I can see this working fine for the service layers of my application, I really don't understand how this would work with my value objects/entities. It seems to me that this pattern requires me to instantiate all my models in the highest layer of my application. However, how I can I instantiate all my widgets at that level, when I need my page class to tell me what widgets I need to instantiate? And without knowing what widgets I'm creating, how can I know what widget dependencies need to be created and injected in?

In theory DI makes a lot of sense to me. However, actually implementing this pattern in my new web application is proving to be more difficult. Is there still something I'm missing with respect to DI and IoC? Thanks!

Update: Response to Tandu

Thanks a lot for the response Tandu. I think that DI may not actually be my problem here, because I think I have a very clear understand of what it is, whether it be constructor or setter injection, or whether it be done using a container or the poor man's way. What I do think is happening is that DI is challenging me to rethink how I'm currently programming. I think my OOP design is the problem. To illustrate this, here is how I would have normally programmed this problem:

class Page
{
    public $id;
    public $name;

    public function widgets()
    {
        // Perform SQL query to select all widgets
        // for this page from the database, and then
        // return them as Widget objects.
    }
}

interface Widget
{
    public $id;
    public $page_id;
    public $type;

    public function widgetize();
}

class PhotoGallery_Widget implements Widget
{
    public $id;
    public $page_id;
    public $type;

    public function widgetize()
    {
        $gallery = new Photogallery();

        foreach($gallery->images())
        {
            // Create gallery HTML code
        }
    }
}

// Finally, to create the page, I would:
$page = new Page(1);
foreach($page->widgets() as $widget)
{
    $widget->widgetize();
}

Clearly there are some issues here. The Page model depends on the Widget models. The PhotoGallery_Widget model depends on the PhotoGallery model, and even further the PhotoGallery model depends on the PhotoGalleryImages model. The thing is, developing this way has worked well for me. As I get deeper and deeper into the layers of my application, each layer takes on the responsibility of creating the objects it requires. Introducing DI throws a major wrench into my way of thinking. However, I want to learn how to do this properly. Just because this works for me right now, doesn't mean I should continue doing it this way.

You mention using factories. But don't factories actually break DI because they are instantiating objects?

You may also notice that my models have the responsibility of interacting with the database. I suspect that this is also a problem with my design. Should I be using data mapping of some sort to remove this responsibility from my models?

Given this more specific example of how I'm currently implementing my code, do you have any other recommendations or can you further illustrate how I should be doing it differently?

like image 202
Jonathan Avatar asked Oct 27 '11 02:10

Jonathan


People also ask

What is the use of dependency injection PHP?

Dependency injection is a procedure where one object supplies the dependencies of another object. Dependency Injection is a software design approach that allows avoiding hard-coding dependencies and makes it possible to change the dependencies both at runtime and compile time.

What is dependency injection in Web?

“ - In software development, dependency injection is a technique where one object supplies the needs, or dependencies, of another object.

When should dependency injection be used?

More specifically, dependency injection is effective in these situations: You need to inject configuration data into one or more components. You need to inject the same dependency into multiple components. You need to inject different implementations of the same dependency.

Does dependency injection improve performance?

The dependency injection (DI) has become an increasingly popular tool in Android development, and for good reason. Injections reduce the amount you have to code (and hence, debug), facilitating the creation of better apps and a smoother development process.


1 Answers

Without looking at specific implementations, it's hard to give specific advice, so instead I'll provide the general advice that I can (I'm not very good, actually). It sounds like you could use a "dependency injection container" (google it .. find one you like or roll your own for your purposes) that manages the large interlocking dependencies of classes.

One thing you should keep in mind is that your code should gently fall around the design, which will hold it up very strongly. You shouldn't have to work hard to get your code to follow the design you've created. If what you are trying to do isn't working according to your understanding of the design, it's probably time for a rewrite.

You also only use examples of constructor injection. I'm sure you are aware that it's possible to use other kinds of injection such as setter injection when appropriate.

As I said, I haven't seen your code, but the problem with your design is that it's not the Page that should know about what widgets it needs, it's the service. If you are tying specific widget implementations to a page, this violates dependency inversion (and makes dependency injection harder). Instead your code should be more along the lines of:

interface Widget {
   function widgetize();
}

class ConcretePage {
   private $widgets = array();

   public function addWidget(Widget $widget) {
      $this->widgets[] = $widget;
   }

   public function run() {
      foreach ($this->widgets as $widget) {
         $widget->widgetize();
      }
   }
}

The service will create a page and add the widgets it requires. The page shouldn't care about those widgets. You may also have to implement a bridge between the page and the widgets. The service can handle that too.

As the very article writer you mention states here, you should have code that is devoted to building your entire object graph (this is where all of your new operators go). Imagine how difficult it would be to test your Page that expects five specific widgets without the class definitions of those widgets! You need to depend on abstractions.

This might not be in the spirit of DI, but a simpler solution would be to have a WidgetFactory:

class Page {
    private $wf;
    private $widgets = array('whiz', 'bang');
    public function __construct(WidgetFactory $wf) {
       $this->wf = $wf;
    }
    public function widgetize() {
       foreach ($this->widgets as $widget) {
          $widget = $this->wf::build($widget);
       }
    }
}

The WidgetFactory interface can be separate from application to application and even in testing. This still gives you some level of abstraction for the widgets Page expects.

like image 192
Explosion Pills Avatar answered Oct 06 '22 12:10

Explosion Pills