13

I've recently learned about the advantages of using Dependency Injection (DI) in my PHP application. However, I'm still unsure how to create my container for the dependencies, or whether I should be using DI at all for the online forum that I'm building.

The following code is my version of the DI container I have made based on the example I learned from here .

class ioc {

   var $db;
   var $session;
   var $user_id;

   static function newUser(static::$db, static::$user_id) {
      $user = new User($db, $user_id);
      return $user;
   }

   static function newLogin(static::$db, static::$session) {
      $login = new Login($db, $session);
      return $login;
   }

}

$user = ioc::newUser();
$login = ioc::newLogin();

I have a few questions:

1) Where should I instantiate my injected dependencies, such as $database, $session, etc? Would it be outside the container class, or inside the container's constructor.

2) What if I need to create a multiple instances of the User class inside other classes? I can't inject the previously instantiated $user object because that instance is already being used. However, creating the multiple User instances inside of another class would violate the rules of DI. For example:

class Users {

    function __construct($db, $user_id) {
        $this->db = $db;
        $this->user_id = $user_id;
    }

    function create_friends_list() {
        $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = $this->user_id");
        $st->execute();
        while($row = $st->fetch()) {
            $friend = ioc::newUser($row['user_id']);
            $friend->get_user_name();
            $friend->get_profile_picture();
        }
    }
}   

3) I'm wondering if I should even adopt DI, knowing that I have to rewrite all of my previous code. I've previously been relying on global variables that I instantiate in my initialize.php, which is included in all my files.

It seems to me that DI creates a lot of overhead and there are situations where it is unusable (as in my #2 example). The following site is from a developer who cites many good reasons not to use DI. Does his arguments have any merit? Or am I just using DI wrong? check this link.

Anonymous
  • 171
  • 1
  • 7
  • I've read that website a couple of times, mainly out of a fascination about how consistently angry the author is. Apparently his freedom of speech extends to insulting people on their own blogs, and the people who delete him are Nazis. Oh dear... `:)` – halfer Jan 08 '13 at 12:32
  • To answer the question a bit more directly (!), Symfony2 has a [standalone DI component](http://symfony.com/doc/current/components/dependency_injection/index.html), which you may find worth looking at. – halfer Jan 08 '13 at 12:33
  • check out http://docs.codehaus.org/display/PICO/IoC+Types for some information on various approaches. – Gordon Jan 08 '13 at 12:59
  • 1
    You might also find cues in http://stackoverflow.com/questions/9348376/guice-like-dependency-injection-frameworks-in-php – Gordon Jan 08 '13 at 13:20
  • How much code would you need to re-write? I wouldn't halt good progress on something large _just_ to add an IoC container and re-factor classes to be used by it without depending on it. Additionally, what do your tests look like now? – Tim Post Jan 08 '13 at 13:38
  • @Tim, most of the code I am using is from a project I have done in the past. I am not entirely sold on this DI thing yet. It seems a bit complex (and very different) and more work than it's worth to implement at this point. I'm just trying to hack out something real quick and see if it gains traction. If not I plan on moving on to something else. One of the articles here suggest I do it the quick and dirty method until we get funding for long term development. – Anonymous Jan 08 '13 at 16:37
  • Maybe try look at [Pimple](http://pimple.sensiolabs.org/). It's a very simple dependency injection container used in [Silex framework](http://silex.sensiolabs.org/) by creators of [Symfony](http://symfony.com/). It's designed to be [extremely simple](https://raw.github.com/fabpot/Pimple/master/lib/Pimple.php). Maybe it's too simple for you but I hope you can find some ideas useful. – martin Jan 08 '13 at 13:17
  • You can try my project, https://github.com/db80/ovo-container. It's a standard dependency injection container and it does what you need, nothing more or less. – db80 Oct 09 '13 at 16:53

4 Answers4

7

Where should I instantiate my injected dependencies, such as $database, $session, etc? Would it be outside the container class, or inside the container's constructor.

Ideally your database connection and session would be bootstrapped in. Proper DI requires an instance of a base object for which everything is registered into. So taking your IOC class as an example you need to make an instance of it ($ioc = new IOC();) then you need some kind of service provider class say

$ioc->register('database', new DatabaseServiceProvider($host, $user, $pass))

Now every time you want a connection to the database you just need to pass in $ioc->get('database'); a very rough example but I think you can see the idea is basically to store everything inside a registry and nothing is statically binded meaning you can create another instance of $ioc with totally different settings making it easy to create connections to say a different database for testing purposes.

What if I need to create a multiple instances of the User class inside other classes? I can't inject the previously instantiated $user object because that instance is already being used. However, creating the multiple User instances inside of another class would violate the rules of DI.

This is a common issue and there are multiple different solutions. Firstly your DI should show the difference between logged in user and just a user. You would probably want to register your logged in user but not just any user. make your user class just normal and use

$ioc->register('login-user', User::fetch($ioc->get('database'), $user_id));

so now $ioc->get('login-user') returns your logged in user. You can then use User->fetchAll($ioc->get('database')); to get all your users.

I'm wondering if I should even adopt DI, knowing that I have to rewrite all of my previous code. I've previously been relying on global variables that I instantiate in my initialize.php, which is included in all my files.

If you need to rewrite all your code to use DI you shouldn't probably do it. Maybe look into making a new project and work in some of your old code if you have the time. If your codebase is large I would recommend looking into breaking it down into smaller projects and using RESTFUL apis for getting and saving data. Good examples of writing APIs would be for putting your forum search into its own application /search/name?partial-name=bob would return all users with the word bob in it. you could build it up and make it better over time and use it in your main forum

I hope you understand my answers but if you need any more info let me know.

Tim Post
  • 33,371
  • 15
  • 110
  • 174
  • 1
    Can you explain in further detail the 'register' and 'get' methods you are using? Thanks! – Anonymous Jan 08 '13 at 16:28
  • Register and get methods can be implemented in many different ways but in its most basic way it just adds the object into an array for example: public $registry = array(); function register($name, $value) { if (isset($this->registry[$name]) { throw new Exception($name . ' is already registered!'); } $this->registry[$name] = $value; } You can also register strings and arrays and with newer versions of PHP you can register lambda functions which are really cool in that each time you can it it executes all the code in within the function. – Keith Pincombe Jan 08 '13 at 20:25
  • I think if your new to these ideas, I would highly recommend starting a project with something like silex or laraval both have built in DI which you can follow and build off. – Keith Pincombe Jan 08 '13 at 20:25
2

I was going to write this a comment, but it grew too long. I am not an expert so I will just give my point of view from what I've learned through few years practicing and here in SO. Feel free to use or question any part of my answer (or none).

1.- Outside. What does the container do? The answer should be a single thing. It shouldn't have to be responsible to initialize the classes, connect to the database, handle the session and other things. Each class does one thing only.

class ioc
  {
  public $db;

  // Only pass here the things that the class REALLY needs
  static public function set($var, $val)
    {
    return $this->$var = $val;
    }

  static function newDB($user, $pass)
    {
    return new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    }

  static function newUser($user_id)
    {
    return new User($db, $user_id);
    }

  static function newLogin($session)
    {
    return new Login($this->db, $session);
    }
  }

if (ioc::set('db',ioc::newDB($user, $pass)))
  {
  $user = ioc::newUser($user_id);
  $login = ioc::newLogin($session);
  }

2.- You shouldn't do $friend = ioc::newUser($row['user_id']); inside your class. There you are assuming that there's a class called ioc with a method called newUser(), while each class should be able to act on it's own, not based on [possibly] other existing classes. This is called tight coupling. Basically, that's why you shouldn't use global variables either. Anything used within a class should be passed to it, not assumed in the global scope. Even if you know it's there and your code works, it makes the class not reusable for other projects and much harder to test. I will not extend myself (PUN?) but put a great video I discovered here in SO so you can dig more: The Clean Code Talks - Don't Look For Things.

I'm not sure about how the class User behaves, but this is how I'd do it (not necessary right):

// Allow me to change the name to Friends to avoid confusions
class Friends
  {
  function __construct($db)
    {
    $this->db = $db;
    }

  function create_friends_list($user_id)
    {
    if (!empty(id))
      {
      // Protect it from injection if your $user_id MIGHT come from a $_POST or whatever
      $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = ?");
      $st->execute(array($user_id));
      $AllData = $st->fetchAll()
      return $AllData;
      }
    else return null;
    }

  // Pass the $friend object
  function get_friend_data($friend)
    {
    $FriendData = array ('Name' => $friend->get_user_name(), 'Picture' => $friend->get_profile_picture());
    return $FriendData;
    }
  }

$User = ioc::newUser($user_id);
$Friends = new Friends($db);

$AllFriendsIDs = array();
if ($AllFriendsIDs = $Friends->create_friends_list($User->get('user_id')))
  foreach ($AllFriendsIDs as $Friend)
    {
    // OPTION 1. Return the name, id and whatever in an array for the user object passed.
    $FriendData = $Friends->get_friend_data(ioc::newUser($Friend['user_id']));
    // Do anything you want with $FriendData

    // OPTION 2. Ditch the get_friend_data and work with it here directly. You're already in a loop.
    // Create the object (User) Friend.
    $Friend = ioc::newUser($Friend['user_id']);
    $Friend->get_user_name();
    $Friend->get_profile_picture();
    }

I didn't test it, so it has probably some small bugs.

3.- If you are learning while coding, you will have to rewrite MANY things. Try to do somethings right from the beginning so you don't need to rewrite everything, but only the classes/methods and adopting some conventions for all your code. For example, never echo from within the function/method, always return and echo from outside. I'd say that yes, it's worth it. It's bad that you have to loose 1 or 2 hours just rewriting something, but if it has to be done, do it.

PS, sorry, I changed your bracket style everywhere.


EDIT Reading other answers, while you shouldn't connect to the database with your ioc object, it should be perfeclty fine create a new object with it. Edited above to see what I mean.

Community
  • 1
  • 1
Francisco Presencia
  • 8,732
  • 6
  • 46
  • 90
  • Ty for the help! 1) I have never seen the set method used in the container. I'm assuming it is used to create any instances I need to be injected into the methods inside the container class? Also, shouldn't it be static function set($var, $val) { return $this->$var=$val} ? 2) You said not to use $friend = ioc::newUser($row['user_id']); inside my class, but what if I need it inside my class? So the only way to create a list is to send out an array and iterate through the array on the page? It seems messy not to have this code done inside a class. What if I had multiple lists on one page? – Anonymous Jan 08 '13 at 16:12
  • 1) typo, corrected. 2) I am actually using an object inside `get_friend_data($friend)`. The `$friend` passed is an object actually. Pass only what you need, but ofc, if you need it, you can pass an entire object to a class (though it's better to avoid it).. You have to be able to test the `Friends` class *on it's own*, and for this you should be able to do it without having a working real User class. The option 1 in my code is done inside. I don't know what your function `create_friends_list()` returned, so I couldn't properly answer that part, thus leaving both options for you to choose. – Francisco Presencia Jan 08 '13 at 18:20
  • What is a good name to call my container object? Is $ioc or $reg acceptable? Also, are the methods inside the container required to be static? For example, can I instead do $user = $reg->newUser(); ? Thanks. – Anonymous Jan 10 '13 at 08:02
  • The more descriptive but short the name is, the better. I'd say both are good. The most common DI I've seen are with static methods. This has the advantage that you can initialize the object anywhere, even inside functions and other classes (while not good in general, in some cases you have to do it). This has to do with the object scope. If that's not an issue for you or other developers, go ahead. Also, [check out this link about why it's not always a good idea to use dependency injection](http://www.tonymarston.net/php-mysql/dependency-injection-is-evil.html). – Francisco Presencia Jan 10 '13 at 16:07
1

Instead of globals in your init.php define your objects like:

ioc::register('user', function() {
 return new User();
});

And inisde your create_friends_list method use:

ioc::get('user')->newUser($user_id);

This is a really plain implementation. Check out: http://net.tutsplus.com/tutorials/php/dependency-injection-huh/?search_index=2

or

http://net.tutsplus.com/tutorials/php/dependency-injection-in-php/?search_index=1

for more information.

Robin Drost
  • 507
  • 2
  • 8
1

The question you asked has one very important catch, which I asked about in a comment. Any time you examine the possibility of pausing forward progress in order to go back and accomplish a less than trivial re-factoring of existing classes, you have to really think about the payoff.

You replied:

It seems a bit complex (and very different) and more work than it's worth to implement at this point. I'm just trying to hack out something real quick and see if it gains traction.

Remember that you have the following things to do if you want to implement an DI with an IoC container properly:

  • Implement the IoC container and registry at bootstrap
  • Modify all existing classes that have dependencies to allow setter injection, while not depending on the IoC container itself
  • Re-writing / Re-structuring of your tests as needed

A note on the second bullet which reflects my own personal view and preference, developing with DI in mind can be hard and you want the full reward it can give. One of the biggest rewards is completely decoupled objects, you don't get the complete part of that if everything still depends on an IoC container.

That's a lot of work, especially if you haven't been considering the pattern until now. Sure, you'll have clear benefits, such as lots of re-usable and easily testable objects when you're done. But, as with all re-factoring, nothing new would be accomplished in the context of adding or finishing new functionality and features. Is difficulty testing or tight coupling getting in the way of that? That's something you'd have to weigh.

What I suggest you do instead is keep the pattern in mind as you write new code. Provide setter methods to inject dependencies, which can be utilized manually or through an IoC container. However, have your classes continue to create them just in time if none have been injected and avoid composition in a constructor.

At that point you'll have a collection of classes that lend much better to inversion of control, if that's a pattern you want to pursue in the future. If it works well for you and it's something you really want to incorporate into your design decisions, you can easily re-factor to remove the JIT logic later.

In conclusion, I'm afraid that the only thing you'll end up with if you try implementing it completely, and properly right now is a mess. You can change how you write classes now going forward, but I wouldn't go back and try to implement it across the board.

Community
  • 1
  • 1
Tim Post
  • 33,371
  • 15
  • 110
  • 174