Buy

In modern PHP, you're going to spend a lot of time working with other people's classes: via external libraries that you bring into your project to get things done faster. Of course, when you do that: you can't actually edit their code if you need to change or add some behavior.

Fortunately, OO code gives us some really neat ways to deal with this limitation.

Modifying a Class without Modifying it?

For the next few minutes, I want you to pretend like our PDOShipStorage is actually from a third-party library. In other words, we can't modify it.

Now, let's say whenever we call fetchAllShipsData(), it's really important for us to log to a file, how many ships were found. But if we can't edit this file, how can we do that?

Using Inheritance

There's actually two ways to do this, and both are pretty awesome. The first way is to create a new class that extends PDOShipStorage, like LoggablePDOShipStorage, and override some methods to add logging.

Nah, Use Composition

But forget that, let's skip to a better method called composition. First, create a new class in the Service directory called LoggableShipStorage, but do not extend PDOShipStorage:

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 2
namespace Service;
class LoggableShipStorage implements ShipStorageInterface
{
... lines 7 - 32
}

Now, the only rule for any ship storage object is that it needs to implement the ShipStorageInterface. Add that, and then go to our handy "Code"->"Generate" method to implement the 2 methods we need:

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 4
class LoggableShipStorage implements ShipStorageInterface
{
... lines 7 - 13
public function fetchAllShipsData()
{
... lines 16 - 20
}
public function fetchSingleShipData($id)
{
... line 25
}
... lines 27 - 32
}

So far, this is how every ship storage starts.

But LoggableShipStorage will not actually do any of the ship-loading work - it'll offload all that hard work to some other ship storage object, like PDOShipStorage. To do that, add a new private $shipStorage property and a public function __construct() method that accepts one ShipStorageInterface argument. Then, set that value onto the $shipStorage property:

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 4
class LoggableShipStorage implements ShipStorageInterface
{
private $shipStorage;
public function __construct(ShipStorageInterface $shipStorage)
{
$this->shipStorage = $shipStorage;
}
... lines 13 - 32
}

For fetchAllShipData(), just return $this->shipStorage->fetchAllShipsData(). Repeat for the other method: return $this->shipStorage->fetchSingleShipData():

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 4
class LoggableShipStorage implements ShipStorageInterface
{
... lines 7 - 22
public function fetchSingleShipData($id)
{
return $this->shipStorage->fetchSingleShipData($id);
}
... lines 27 - 32
}

We've now created a wrapper object that offloads all of the work to an internal ship storage object. This is composition: you put one object inside of another.

To use the new class, open up Container. Inside getShipStorage(), add $this->shipStorage = new LoggableShipStorage() and pass it $this->shipStorage, which is the PDOShipStorage object:

77 lines lib/Service/Container.php
... lines 1 - 4
class Container
{
... lines 7 - 51
public function getShipStorage()
{
if ($this->shipStorage === null) {
$this->shipStorage = new PdoShipStorage($this->getPDO());
//$this->shipStorage = new JsonFileShipStorage(__DIR__.'/../../resources/ships.json');
// use "composition": put the PdoShipStorage inside the LoggableShipStorage
$this->shipStorage = new LoggableShipStorage($this->shipStorage);
}
... lines 61 - 62
}
... lines 64 - 75
}

We've just pulled a "fast one" on our application: our entire app thinks we're using PDOShipStorage, but we just changed that! If you refresh now, nothing is different: everything still eventually goes through the PDOShipStorage object.

But now, we have the opportunity to add more functionality - or to change functionality - in either of these methods.

Add some Logging!

To give a really simple example, replace the return statement with $ships = and add return $ships below that:

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 4
class LoggableShipStorage implements ShipStorageInterface
{
... lines 7 - 13
public function fetchAllShipsData()
{
$ships = $this->shipStorage->fetchAllShipsData();
... lines 17 - 19
return $ships;
}
... lines 22 - 32
}

Between, we could call some new log() method, passing it a string like: just fetched %s ships - passing that a count() of $ships:

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 4
class LoggableShipStorage implements ShipStorageInterface
{
... lines 7 - 13
public function fetchAllShipsData()
{
$ships = $this->shipStorage->fetchAllShipsData();
$this->log(sprintf('Just fetched %s ships', count($ships)));
return $ships;
}
... lines 22 - 32
}

Below, add a new private function log() with a $message argument:

34 lines lib/Service/LoggableShipStorage.php
... lines 1 - 4
class LoggableShipStorage implements ShipStorageInterface
{
... lines 7 - 27
private function log($message)
{
// todo - actually log this somewhere, instead of printing!
echo $message;
}
}

You should do something more intelligent in a real app, but to prove it's working, echo that message.

Let's refresh! There's our message!

Why is Composition Cool?

Wrapping one object inside of another like this is called composition. You see, when you want to change the behavior of an existing class, the first thing we always think of is

Oh, just extend that class and override some methods

But composition is another option, and it does have some subtle advantages. If we had extended PDOShipStorage and then later wanted to change back to our JsonFileShipStorage, then all of a sudden we would need to change our LoggableShipStorage to extend JsonFileShipStorage. But with composition, our wrapper class can work with any ShipStorageInterface. We could change just one line to go back to loading files from JSON and not lose our logging.

This isn't always a ground-breaking difference, but this is what people mean when they talk about "composition over inheritance".

Alright guys! I have tried to think of all the weird stuff that we haven't talked about with object oriented coding, and I've run out! You are now super qualified with this stuff - so get out there, find some classes, find some interfaces, make some traits, do some good, and just keep practicing. It's going to sink in more and more over time, and serve you for years to come, in many different languages.

See you next time!

Leave a comment!

  • 2017-11-18 Jeremy Carlson

    Thank you for this series of tutorials. This has done wonders for my understanding of OO PHP. Truly helpful!

  • 2017-04-07 Diego Aguiar

    Hey Brian!

    We are glad to hear that you find useful our tutorials, we make them with passion ;)

    Cheers!

  • 2017-04-07 Brian Morris

    Thank you for the wonderful and informative series of tutorials. Coming from old-skool php land this has brought me up to speed in a hurry. Now to go code something!

  • 2017-02-06 weaverryan

    Hey Ivan!

    Actually, yes! There are many different use-cases for the proxy pattern, but this is certainly one of them: the LoggableShipStorage is effectively a proxy for whatever other ship storage we pass into it. In fact, it fits the "smart reference" proxy pattern that Marco (core Doctrine contributor) talks about in his presentation here: http://ocramius.github.io/p...

    Cheers!

  • 2017-02-04 Ivan

    So in this example our class LoggableShipStorage is actually implements a Proxy pattern?
    I wish someday your Design Patterns course will be completed :)

  • 2016-11-10 weaverryan

    Totally :). I think using inheritance is a bit more common when people develop their *own* applications, as it's a bit more straightforward, and you probably don't need the flexibility that composition gives you. This happens a lot actually: if you're building a re-usable library, then you often need to code things "more correct" than you should need to in your own code :).

    Cheers!

  • 2016-11-10 Hakim Ch

    In this case, use the composition, but if I have only one handler, can I use the inheritance?

  • 2016-10-24 weaverryan

    When I mess up (i.e. by forgetting the return) we keep the code and script correct :). But we'll add a note about the missing return.

    Thanks!

  • 2016-10-24 Max

    And another remark: at 2:30 you are saying return (as it's written in the script), but fetchSingleShipData does not return anything... :)

  • 2016-10-24 Max

    You couldn't as I wasn't registered until now ;)

  • 2016-10-24 weaverryan

    Laaaame :). Thanks a lot for reporting that - fixed at https://github.com/knpunive... I wanted to give you a GitHub shout-out, but couldn't find your user!

    Cheers!

  • 2016-10-23 Max

    Typo in challenge one:
    But here's the challenge: the existing PlanetRenderer returns a div **aroudn** the plane.