Dependency injection is another common theme among object oriented programmers. In this tutorial, we will take a look at dependency injection, and why it might make sense to make use of it. We’ve already had a look at basic classes, objects, getters and setters, as well as some basic concepts regarding encapsulation and inheritance. Let us now tackle a gentle introduction to dependency injection and object messages now.
There is no “I” in Team
Let’s think about building a soccer team that will face the world in a FIFA sanctioned event. In this type of soccer, there are many different Countries that participate. In order for a Country to participate, they will depend on a team of players. Each team depends on having enough players to be competitive. With this idea, we can build out some classes that follow these principles. Using our hunt down the nouns approach, let’s name some classes. So far we’ve talked about a Country
, a Team
, and a Player
. Let’s turn those into classes.
class Country
{
}
class Player
{
}
class Team
{
}
Excellent. Now of course, these classes are empty so we’re not going to be exactly tearing up the FIFA circuit, but let’s see if we can change that. Let’s think about the Country
. A Country
is going to need a name
, like the USA, and it will depend on a Team
, in order to be a candidate for the FIFA world cup. Let’s add those items in now.
Passing an Object as an argument
class Country
{
protected $team;
protected $name;
public function __construct(Team $team, $name = 'USA')
{
$this->team = $team;
$this->name = $name;
}
}
What we do here is pass two arguments to our constructor. The first is the dependency, which makes use of Type Hinting. We prefix the variable with the type of object we wish to depend on. This is our first instance of Dependency Injection and it comes in the form of Constructor Injection. The second argument is simply the name
of the Country
, which will default to USA if none is provided. Ok, we have a Country
that depends on a Team
, but our current Team
class is empty so it will not be of much benefit. Let’s add some meat to our Team. What does a Team need? It needs some players. How does it get players? Well, they would need to join
the team. We could also say we depend upon a player to join the team. Let’s code this up.
class Team
{
protected $players = [];
public function __construct($players = [])
{
$this->players = $players;
}
public function join(Player $player)
{
$this->players[] = $player;
}
public function getplayers()
{
return $this->players;
}
}
First off we create a players
array to hold all of our players. When we new up a Team
, we will automatically generate a players array to hold the players that join the team. There’s a keyword there, join. A Player will need to join the team. We create the join
method and pass in a Player
object. We again use type hinting here to indicate we expect an object of type Player
. This is our second instance of Dependency Injection, but this time we are using Method Injection. Finally, we just create a simple getter method of getplayers()
so we can see who is on our team as we add players. The Player
class should be easy as all we have to do is give our player a name
.
class Player
{
protected $name;
public function __construct($name)
{
$this->name = $name;
}
}
At this point we’re making some progress, but we have a small problem. How is a player going to join the team of a specific country. Most likely, the Country would need to recruit
a player to join their team. Let’s update the Country class to reflect this.
Sending Messages
class Country
{
protected $team;
protected $name;
public function __construct(Team $team, $name = 'USA')
{
$this->team = $team;
$this->name = $name;
}
public function recruit(Player $player)
{
$this->team->join($player);
}
}
Now our Country
has the ability to recruit
new players for their Team
. Note that this is an example of message passing. The name of the method and its input parameters are conceptualized as a message. We’re about ready to test this out. Now we now that a Country
is going to Depend On a Team
. In other words, when we new up a Country
, we need to pass in a Team
object. So first, we will create a Team
object, then we will create a Country
object, and pass in that instance of the Team
.
$team = new Team;
$usa = new Country($team);
Perfect. Now we need to recruit
some players to our Team
. In order to recruit
a player, they must first exist. Let’s create a new Player
now.
$player1 = new Player('Carli Lloyd');
Now that we have a Player
, our Country
can recruit
her!
$usa->recruit($player1);
Let’s now confirm who is on our Team
. Remember, we created a getplayers()
getter on our Team
class to do just that for us.
$team->getplayers();
Array ( [0] => Player Object ( [name:protected] => Carli Lloyd ) )
Awesome! We can see that we have one Player
object with the name
of Carli Lloyd on our team. We’re going to need much more than one player to be successful, so let’s add a bunch of teammates.
$player2 = new Player('Morgan Brian');
$player3 = new Player('Ashlyn Harris');
$player4 = new Player('Tobin Heath');
$player5 = new Player('Alex Morgan');
$usa->recruit($player2);
$usa->recruit($player3);
$usa->recruit($player4);
$usa->recruit($player5);
Bingo Bango -> let’s see who is now on our team!
$team->getplayers();
Array ( [0] => Player Object ( [name:protected] => Carli Lloyd ) [1] => Player Object ( [name:protected] => Morgan Brian ) [2] => Player Object ( [name:protected] => Ashlyn Harris ) [3] => Player Object ( [name:protected] => Tobin Heath ) [4] => Player Object ( [name:protected] => Alex Morgan ) )
Now that’s a Team! We have some fantastic players such as Morgan Brian, Ashlyn Harris, Tobin Heath, Alex Morgan, and Carli Lloyd.
Dependency Injection for Beginners
In this Dependency Injection for Beginners tutorial, we covered a whole lot of ground. Instead of creating one massive class and trying to put all functionality in one location, we broke out our class into several different classes. Even though the classes are separate, they are able to communicate with each other by sending messages, or calling methods on, each other. Sending a message can be thought of as, calling a method on an object. We also had an introduction to Type Hinting and Dependency Injection. These are approaches that allow for Composition, which is the concept of Objects containing other objects in their instance variables. By using Composition, we can build up an object to meet all of our logic objectives, when simple inheritance is not enough. Understanding Dependency Injection gives us a good idea of how Inversion Of Control works as well.