In this series, we'll continue from Introduction to Object-Oriented PHP and really start using what we know. We'll clean up old functions with new classes, learn a practice called dependency injection and organize our objects into containers.
In this course, we're going to continue on from the Introduction to Object-Oriented PHP series. We're working on the same spaceship project: it has ships, you choose them, then they engage in epic battle!
In an editor, far far away, you'll see a simple application that runs this: index.php is the homepage and battle.php does the magic and shows the results. In the first course, we created a single class called
Ship, which describes all its properties—it's like a container for one ship's details. In this tutorial we're going to replace our flat functions and create a
BattleManager service class to provide the methods we'll need to do that.
In this PHP tutorial we are going to continue to remove the flat functions in our code. We'll refactor one of them to a private function and create a new
ShipLoader service class to clean up the rest. With this refactoring in place, we'll be able to rename the functions.php file since it won't contain any functions any longer.
The next thing we want to tackle is our
battle() function, with its array, inside our BattleManager class. It's not obvious at all what's inside the
$outcome variable, or whether the keys it has now might be missing or different in the future. To rectify this we're going to create a new BattleResult model class with some properties and methods that will clean things up and remove the need for weird associative arrays. Our
BattleManager::battle() function will return a nice BattleResult object, and we'll be in full control of what public methods we put on that.
We now have a
BattleResult class, and we type-hinted the two
Ship arguments. But now, if you look at the
battle() function, there's a case where the ships can destroy each other. When that happens, there is no winning or losing ship—they're both null. Since
null is not a
Ship object, PHP gets angry and throws an error. In this PHP tutorial we'll fix that problem by creating a
isThereAWinner semantic method in our
In this PHP tutorial we're going to update some information in our ship object and see another way that objects are different from arrays—objects are always passed by reference. We need to add a new feature to our app so that after the battle we can display the final health of the battling ships. One will be zero or negative, but how much health did the other have left? Let's take a look at how we can update the ships to reflect their new health after a battle.
So far we've been hard-coding our ships in the
ShipLoader class. In this tutorial we'll take things up a notch by fetching that information from a database. First we're going to create a new database to hold our data, using
PDO. (If you are not familiar with working with databases in PHP, you can check out the PHP for Beginners Part 3 series, which covers database fun.) Once we have the database created and filled with some ship data, we'll learn how to fetch that data back out again as an object our app can work with.
In the prevous tutorial we set up our app to fetch the ship objects from the database, but now our app ship select lists are not working properly. To deal with that, in this PHP tutorial, we're going to add a ship ID property to our class and then we will use the
ShipLoader class to query for individual ships. Lastly, we will once again need to turn the array that we fetch into an object that our app can use. As an added bonus we'll also update the PHPDoc so we can get autocompleting method names.
Our database is working well, but we currently have things set up so we are duplicating the database information, and that is going to be a nightmare to maintain. In this tutorial, we're going to clean up our database code by isolating the
PDO creation in the
ShipLoader class. Even with that in place though, we also need to make sure we only return one
PDO object. We don't need a new database connection every time it gets called on a page. We'll fix that with a little bit of logic in our
Things are working pretty well, but we still have some things to clean up. In this tutorial we're going to review some best practices. Our current problem is that at the bottom of
ShipLoader, our database connection information is hard-coded. That's a problem for two reasons. First, if this works on my computer, it probably won't work on production, unless everything matches up. Secondly, what if we need a database connection inside some other class? Right now, we'd just have to copy and paste those credentials into yet another spot, which is definitely not ideal.
Here's the goal: move the database configuration out of this class to somewhere more central so it can be re-used. The way you do this is fundamentally important to using object-oriented code correctly. The concept we are going to implement here is called dependency injection. The idea here is don't put configuration inside of a service class. Replace that hard-coded configuration with an argument. This allows anyone using your class to pass in whatever they want. The hard-coding is gone, and your class is more flexible.
Related to the previous tutorial where we implemented best practices to centralize our configuration, we also want to minimize the number of database connections. We want one connection that every class uses. In this tutorial we'll move the new
PDO() call out of
ShipLoader so that it can be created in a central location and used by everyone. How? By using the same strategy we just learned with configuration. If you want to move something out of a service class, add it as a
__construct() argument and pass it in.
So far we have to create our service objects by hand and this stuff is duplicated. We need to centralize our service objects to make our lives easier. To do that, in this tutorial we'll create one special class whose only job is to create these service objects. This class is called a service container because, well, it's basically a container for all the service objects. We're going to create our service container and then update our code to use it properly.
To wrap things up with our app, the last bit of housekeeping is to make one container responsible for creating every service object, like PDO, but also
BattleManager. The problem is that if we called
$container->getPDO() twice on the same request, we'd still end up with multiple
PDO objects, and so, multiple database connections. Ok, if we're careful, we can avoid this. We can do better though—let's guarantee that only one
PDO object is ever created. We did this previously in
ShipLoader, so now we'll move this into our container.
We've got a nice little app working now, using our service container well. Good work! In this final lesson for this series we're going to have a review of working with containers. We'll look at why this is such a good practice, discuss model classes versus service classes, and take a moment to acknowledge best practices versus the real world implementations you can end up with.