If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
I can't stand it any longer. The app is small, but our database credentials
are already duplicated and hidden inside this one class. What if we added
a second table - like battle
- and a BattleLoader
class? At this rate,
we'd be copying and pasting the database password there too. Gross.
Enough is enough. Let's fix this little by little. First, I don't want to
duplicate the new PDO
code twice in this class. To fix that, create a
private function getPDO()
- private because - at least so far - we only
want to call this from inside ShipLoader
. Copy the new PDO
line and
the one below it and put them here. Return $pdo
and let's even add some
nice PHPDoc:
... lines 1 - 2 | |
class ShipLoader | |
{ | |
... lines 5 - 48 | |
/** | |
* @return PDO | |
*/ | |
private function getPDO() | |
{ | |
$pdo = new PDO('mysql:host=localhost;dbname=oo_battle', 'root'); | |
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
return $pdo; | |
} | |
... lines 59 - 67 | |
} | |
... lines 69 - 70 |
You know what's next: use this above with: $pdo = $this->getPDO()
. Repeat
this in the other spot:
... lines 1 - 2 | |
class ShipLoader | |
{ | |
... lines 5 - 23 | |
*/ | |
public function findOneById($id) | |
{ | |
$statement = $this->getPDO()->prepare('SELECT * FROM ship WHERE id = :id'); | |
... lines 28 - 35 | |
} | |
... lines 37 - 59 | |
private function queryForShips() | |
{ | |
$statement = $this->getPDO()->prepare('SELECT * FROM ship'); | |
... lines 63 - 66 | |
} | |
} | |
... lines 69 - 70 |
Head back to the homepage! Ha! Nothing broken yet.
Ok, a little bit better. Here's the next problem: what if a single page calls
findOneById()
multiple times? Well, getPDO()
would be called twice, two
PDO
objects would be created and this would mean that two database
connections would be made. Such waste! We only need one connection and we only
need one PDO
object.
How can we guarantee that only one PDO object is created?
By using a property! But in a way that we haven't seen yet. Up until now,
we've only put properties on our model classes - like Ship
- and that has
been to hold data about the object, like name
, weaponPower
, etc.
In service classes - any class whose main job is to do work instead of hold data - you use properties for two reasons: to hold options about how the class should behave. And to hold other tools - like a PDO object.
Create a private $pdo
property:
... lines 1 - 2 | |
class ShipLoader | |
{ | |
private $pdo; | |
... lines 6 - 71 | |
} | |
... lines 73 - 74 |
Now, we can use a little trick thanks to OO! Down in getPDO()
, add an if
statement to check if the pdo
property is equal to null
. Why of course
it is! So far, nothing is setting it, so it's always null. But now, if
it is null, move the new PDO()
code into this and then assign this to
the pdo
property. Finish by returning $this->pdo
:
... lines 1 - 2 | |
class ShipLoader | |
{ | |
private $pdo; | |
... lines 6 - 53 | |
private function getPDO() | |
{ | |
if ($this->pdo === null) { | |
$this->pdo = new PDO('mysql:host=localhost;dbname=oo_battle', 'root'); | |
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
} | |
return $this->pdo; | |
} | |
... lines 63 - 71 | |
} | |
... lines 73 - 74 |
The first time you call this, $this->pdo
is null so we create a new PDO
object and set the property. Then, if someone calls this during the same
request, the pdo
property will already be an object, so it'll skip creating
a second one and just return it. Boom!
This is the first time we've seen a service class - something that does work
for us - have a property. And in service classes, properties aren't about
holding data that describe something - like a Ship
- they're used to store
options about how the class should work or other useful objects that class
needs.
We shouldn't notice any difference - so refresh to try it. Yes! Think about it: thanks to objects, we were able to reduce the number of database connections being created by touching one file and not breaking anything.
Hey Marc R.
I don't think an object can live after the request-response cycle. That trick is useful because creating a PDO instance is expensive and it's likely that it will be used more than one time during a request
Cheers!
Hi!
You have a minor typo at the final code sample:
if ($this->pdo === null) {
$this->pdo = new PDO('mysql:host=localhost;dbname=oo_battle', 'root');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
I bet it must be "$this->pdo->setAttribute" instead of "$pdo->setAttribute".
Hi Ivan,
You're absolutely right! I fixed it, but it takes a while when our cache regenerate and you will see these changes.
Thanks for noticing us!
Hey Thrasos,
Really good question! Actually, since we don't know what is dependency injection and dependency injection container yet, it was just a simple refactoring to avoid code duplication. And the another point is that we have a lazy loading PDO instance here, since we create it *only* when the getPDO() method is called at first time. But anyways, it was just a intermediate refactoring and we will consider more cool stuff as DI Container further.
Cheers!
Thanks for the quick reply! Just a small question. Would it be a better practice (without knowing what DI is) to create a constructor method and within call a createPDO method that would initiate a PDO object and set it at a $pdo property?
Hey Thrasos,
Yes, I think so as long as you have a hard dependency with PDO object. But as we see, ShipLoader has and just won't work without PDO instance. Actually, we will do this further in the course.
Cheers!
Hi !
What do you mean by
?
This trick seams to be interesting only if the object lives during the whole session, not only during a simple request.