Container: Force Single Objects, Celebrate
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.
Home stretch! Our goal is to make Container
responsible for creating every
service object: like PDO
, but also ShipLoader
and BattleManager
.
Guaranteeing only One PDO Object
Here's our issue: 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: let's guarantee that only one PDO object is ever created.
We did this before in ShipLoader
. Create a private $pdo
property at the
top of Container
. In getPDO()
, add an if
statement to see if the property
is null. If it is, create the new PDO()
object and set it on the property.
Return $this->pdo
at the bottom:
// ... lines 1 - 2 | |
class Container | |
{ | |
// ... lines 5 - 6 | |
private $pdo; | |
// ... lines 8 - 16 | |
public function getPDO() | |
{ | |
if ($this->pdo === null) { | |
$this->pdo = new PDO( | |
$this->configuration['db_dsn'], | |
$this->configuration['db_user'], | |
$this->configuration['db_pass'] | |
); | |
// ... lines 25 - 26 | |
} | |
// ... line 28 | |
return $this->pdo; | |
} | |
// ... lines 31 - 32 |
Again, the first time we call this: the pdo
property is null, so we create
the object and set the property. The second, third and fourth time we call
this, the object is already there, so we just return it.
Oh, and while I'm here, I'll paste back one line I lost on accident earlier:
// ... lines 1 - 18 | |
if ($this->pdo === null) { | |
$this->pdo = new PDO( | |
$this->configuration['db_dsn'], | |
$this->configuration['db_user'], | |
$this->configuration['db_pass'] | |
); | |
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
} | |
// ... lines 28 - 32 |
This just sets up PDO to throw nice exceptions if something goes wrong so I can see them.
Move ShipLoader to the Container
Keep going! We don't want to instantiate a ShipLoader
object manually in
battle.php
and index.php
. Let's just do it inside Container
.
Follow the same pattern: create a private
property called $shipLoader
,
and a public function getShipLoader()
:
// ... lines 1 - 2 | |
class Container | |
{ | |
// ... lines 5 - 8 | |
private $shipLoader; | |
// ... lines 10 - 36 | |
public function getShipLoader() | |
{ | |
// ... lines 39 - 43 | |
} | |
} |
In here, add the same if
statement: if ($this->shipLoader === null)
,
then $this->shipLoader = new ShipLoader()
. Remember, it has a required
argument for the PDO object. That's easy, just say $this->getPDO()
.
At the bottom return $this->shipLoader
and add the PHPDoc above it:
// ... lines 1 - 2 | |
class Container | |
{ | |
// ... lines 5 - 8 | |
private $shipLoader; | |
// ... lines 10 - 33 | |
/** | |
* @return ShipLoader | |
*/ | |
public function getShipLoader() | |
{ | |
if ($this->shipLoader === null) { | |
$this->shipLoader = new ShipLoader($this->getPDO()); | |
} | |
return $this->shipLoader; | |
} | |
} |
Use it! In index.php
, say $shipLoader = $container->getShipLoader()
.
And I have a bonus for you! We don't need the $pdo
variable anymore - we
only did that to pass it to ShipLoader
. Simplify!
// ... lines 1 - 3 | |
$container = new Container($configuration); | |
$shipLoader = $container->getShipLoader(); | |
// ... lines 7 - 121 |
Copy the new $shipLoader
line and repeat this in battle.php
:
// ... lines 1 - 3 | |
$container = new Container($configuration); | |
$shipLoader = $container->getShipLoader(); | |
// ... lines 7 - 108 |
Ok, make sure this is all working. Refresh! Somebody make a sad trombone noise:
Call to a member function getShips() on a non-object index.php line 6.
Ok, trusty debugging cap back on. On line 6, we're calling getShips()
on the
$shipLoader
, which is apparently null. So $container->getShipLoader()
must not be returning the object for some reason. How rude.
Oh, and the problem is me! I added an extra !
in my if
statement so that
it never got inside. Lame. Make sure your's looks like mine does now:
// ... lines 1 - 2 | |
class Container | |
{ | |
// ... lines 5 - 8 | |
private $shipLoader; | |
// ... lines 10 - 33 | |
/** | |
* @return ShipLoader | |
*/ | |
public function getShipLoader() | |
{ | |
if ($this->shipLoader === null) { | |
$this->shipLoader = new ShipLoader($this->getPDO()); | |
} | |
return $this->shipLoader; | |
} | |
} |
Ok, now it works.
Move BattleManager to the Container
Only one more service to go! In battle.php
, we create the BattleManager
.
Let's move it! Add the private $battleManager
property and then the
public function getBattleManager()
. Copy the ship loader code to save
time... and so I don't mess up again. Update it for battleManager
:
$this->battleManager = new BattleManager()
. And return $this->battleManager
:
// ... lines 1 - 2 | |
class Container | |
{ | |
// ... lines 5 - 10 | |
private $battleManager; | |
// ... lines 12 - 47 | |
/** | |
* @return BattleManager | |
*/ | |
public function getBattleManager() | |
{ | |
if ($this->battleManager === null) { | |
$this->battleManager = new BattleManager(); | |
} | |
return $this->battleManager; | |
} | |
} |
Go use it in battle.php
: $battleManager = $container->getBattleManager()
:
// ... lines 1 - 26 | |
$battleManager = $container->getBattleManager(); | |
// ... lines 28 - 109 |
Ok, let's try the whole thing! Start a battle... and Engage. Ok, the bad guys won, but our app still works. And the code behind it is so much more awesome.
Ok i have couple of questions:
1. We moved all of the services to one php class ( Container ) why ?
How i think : PDO is simple, so it will be called once, i get it. But what about other services? so we could call them once too ? Or just to make everything look cleaner ?
2. When i write a program should i create lot of files/classes with different functionality, but include all of them to one class/Container and call all of them just from that file ?
For example : i have a class 'class makingFood'. So i should create getMakingFood in Container class and then call it $makingFood = $container->getMakingFood() where i need?
Sorry for bad English :D