AbstractShipStorage
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
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:
// ... 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
:
// ... 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()
:
// ... 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'
:
// ... 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:
// ... 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 ofPdoShipStorage
, instance ofJsonFileShipStorage
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:
// ... 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
:
// ... 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:
// ... lines 1 - 2 | |
abstract class AbstractShipStorage | |
{ | |
abstract public function fetchAllShipsData(); | |
abstract public function fetchSingleShipData($id); | |
} |
Cool!
Now, JsonFileShipStorage
can extend AbstractShipStorage
:
// ... lines 1 - 2 | |
class JsonFileShipStorage extends AbstractShipStorage | |
// ... lines 4 - 32 |
And the same thing for PdoShipStorage
:
// ... 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:
// ... 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:
// ... 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!
Reminder :
Pay attention to the order of "require" in bootstrap.php.
AbstractShipStorage must be included before PdoShipStorage and JsonFileShipStorage.