Buy

Our goal is to make ShipLoader load things from the database or from a JSON file. In the resources directory I've already created a JsonFileShipStorage class.

Copy that into the service directory and let's take a look inside of here:

32 lines lib/Service/JsonFileShipStorage.php
... lines 1 - 2
class JsonFileShipStorage
{
private $filename;
public function __construct($jsonFilePath)
{
$this->filename = $jsonFilePath;
}
public function fetchAllShipsData()
{
$jsonContents = file_get_contents($this->filename);
return json_decode($jsonContents, true);
}
public function fetchSingleShipData($id)
{
$ships = $this->fetchAllShipsData();
foreach ($ships as $ship) {
if ($ship['id'] == $id) {
return $ship;
}
}
return null;
}
}

It has all of the same methods as PdoShipStorage. Except that this loads from a JSON file instead of querying from a database. Let's try and use this in our project.

First, head over to bootstrap of course and require JsonFileShipStorage.php:

19 lines bootstrap.php
... lines 1 - 15
require_once __DIR__.'/lib/Service/JsonFileShipStorage.php';
... lines 17 - 19

In theory since this class has all the same methods as PdoShipStorage we should be able to pass a JsonFileShipStorage object into ShipLoader and everything should just work. Really, the only thing ShipLoader should care about is that it's passed an object that has the two methods it's calling: fetchAllShipsData() and fetchSingleShipData():

61 lines lib/Service/ShipLoader.php
... lines 1 - 2
class ShipLoader
{
... lines 5 - 31
public function findOneById($id)
{
$shipArray = $this->shipStorage->fetchSingleShipData($id);
... lines 35 - 36
}
... lines 38 - 54
private function queryForShips()
{
return $this->shipStorage->fetchAllShipsData();
}
}
... lines 60 - 61

In Container let's give this a try. Down in getShipStorage() let's say, $this->shipStorage = new JsonFileShipStorage(). And we'll give it a path to our JSON of __DIR__.'/../../resources/ships.json':

72 lines lib/Service/Container.php
... lines 1 - 2
class Container
{
... lines 5 - 49
public function getShipStorage()
{
if ($this->shipStorage === null) {
//$this->shipStorage = new PdoShipStorage($this->getPDO());
$this->shipStorage = new JsonFileShipStorage(__DIR__.'/../../resources/ships.json');
}
... lines 56 - 57
}
... lines 59 - 70
}

From this directory I'm going up a couple of levels, into resources and pointing at this ships.json file which holds all of our ship info:

35 lines resources/ships.json
... line 1
[
{
"id": "1",
"name": "Jedi Starfighter",
"weapon_power": "5",
"jedi_factor": "15",
"strength": "30",
"team": "rebel"
},
... lines 11 - 26
{
"id": "4",
"name": "RZ-1 A-wing interceptor",
"weapon_power": "4",
"jedi_factor": "4",
"strength": "50",
"team": "empire"
}
]

Back to the browser and refresh. Ok no success yet, but as they say, try try again. Before we do that, let's check out this error:

Argument 1 passed to ShipLoader::__construct() must be an instance of PdoShipStorage, instance of JsonFileShipStorage given.

What's happening here is that in ShipLoader we have this type-hint which says that we only accept PdoShipStorage and our Json file is not an instance of that:

61 lines lib/Service/ShipLoader.php
... lines 1 - 2
class ShipLoader
{
... lines 5 - 6
public function __construct(PdoShipStorage $shipStorage)
{
... line 9
}
... lines 11 - 58
}
... lines 60 - 61

The easiest way to fix this is to say extends PdoShipStorage in JsonFileShipStorage:

32 lines lib/Service/JsonFileShipStorage.php
... lines 1 - 2
class JsonFileShipStorage extends PdoShipStorage
... lines 4 - 32

This makes the json file an instance of PdoShipStorage. Try refreshing that again. Perfect, our site is working.

But having to put that extends in our JSON file kinda sucks, when we do this we're overriding every single method and getting some extra stuff that we aren't going to use.

Creating a "Ship storage" contract

Instead, you should be thinking, "This is a good spot for Abstract Ship Storage!" And well, I agree so let's create that. Inside the Service directory add a new PHP Class called AbstractShipStorage. The two methods that this is going to need to have are: fetchSingleShipData() and fetchAllShipsData() so I'll copy both of those and paste them over to our new class.

Of course we don't have any body in these methods, so remove that. Now, set this as an abstract class. Make both of the functions abstract as well:

8 lines lib/Service/AbstractShipStorage.php
... lines 1 - 2
abstract class AbstractShipStorage
{
abstract public function fetchAllShipsData();
abstract public function fetchSingleShipData($id);
}

Cool!

Now, JsonFileShipStorage can extend AbstractShipStorage:

32 lines lib/Service/JsonFileShipStorage.php
... lines 1 - 2
class JsonFileShipStorage extends AbstractShipStorage
... lines 4 - 32

And the same thing for PdoShipStorage:

33 lines lib/Service/PdoShipStorage.php
... lines 1 - 2
class PdoShipStorage extends AbstractShipStorage
... lines 4 - 33

With this setup we know that if we have a AbstractShipStorage it will definitely have both of those methods so we can go into the ShipLoader and change this type hint to AbstractShipStorage because we don't care which of the two storage classes are actually passed:

61 lines lib/Service/ShipLoader.php
... lines 1 - 2
class ShipLoader
{
... lines 5 - 6
public function __construct(AbstractShipStorage $shipStorage)
... lines 8 - 58
}
... lines 60 - 61

To be very well behaved developers, we'll go into our Container and above getShipStorage() change the type hint to AbstractShipStorage. Not a requirement, but it is a good idea.

Go back to the browser and refresh... oh, class AbstractShipStorage not found because we forgot to require it in our bootstrap file. We will eventually fix the need to have all of these require statements:

20 lines bootstrap.php
... lines 1 - 14
require_once __DIR__.'/lib/Service/AbstractShipStorage.php';
... lines 16 - 20

Refresh again and now it works perfectly.

We created an AbstractShipStorage because it allows us to make our ShipLoader more generic. It now doesn't care which one is passed, as long as it extends AbstractShipStorage.

But there's an even better way to handle this... interfaces!

Leave a comment!