Mastering Object Oriented PHP 24
Mastering Object Oriented PHP 24
Oriented PHP
SARAH SAVAGE
Copyright
& Credits
The contents of this book, unless otherwise indicated, are copyright © 2022 - 2023 Sarah
Savage, All Rights Reserved.
Books like this are made possible by the time and investment of the authors. If you
received this book but did not purchase it, please consider making future books possible
by buying a copy.
Introductioni
— Dependency Injection 2
— Liskov Substitution Principle 18
— The Open/Closed Principle 27
— Interface Segregation Principle 39
— The Single Responsibility Principle 46
— The Law of Demeter 56
— Antipatterns of Object-Oriented Development 73
— Exceptions 90
— Action-Domain-Responder 102
What comes to your mind when you think of object-oriented
programming? Complex? Difficult? Impossible to grasp? I know
that I struggled to grapple with the principles of object-oriented
programming and design for years. Lots of people do. Perhaps you’re
one of them. Perhaps that’s why you’re reading this book.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — i
— INTRODUCTION
establishes once and for all a resource that you can go to for answers.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — ii
There is often some confusion between the concepts of dependency
inversion and dependency injection. Dependency injection is the process of
injecting dependencies into an object or function as an argument, avoiding the
need for globals or statics in order to access them. We will discuss dependency
injection in detail in a moment.
Let’s take a close look at each principle, starting with dependency injection.
DEPENDENCY INJECTION
When we develop an application, we have to decide how we’re going to manage
dependencies in terms of other objects, functions, and methods. We need some
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 2
— DEPENDENCY INVERSION
The simplest way to provide one object to another object is to instantiate it,
whether that be in the constructor or at runtime when the object is needed. You
can accomplish instantiating the object line this:
<?php
class Controller
{
public function __construct()
{
$this->cache = new ApcCache();
}
public function manageData()
{
return $this->cache->get('someKey');
}
}
In the code sample provided, we’re simply instantiating the object we need and
then using it in Controller::manageData().
This has several advantages. It provides easy access to the objects that we need
and allows us to control when those objects are created, ensuring that we gain
access to the right object at the right time. It also streamlines the instantiation
of the Controller class, because all we have to do is instantiate new
Controller().
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 3
— DEPENDENCY INVERSION
There are some drawbacks to this approach as well. Chief among those
drawbacks is that this is difficult if not impossible to test in isolation: we always
get an instance of ApcCache() whenever we instantiate the Controller
class. We’re also tied to a particular implementation of ApcCache; we can’t use
another cache type like MemcacheCache or NullCache for development or
testing.
To solve for this, we can take a couple of strategies. One of them is to use service
location to locate the dependency we want and make it available to our code.
<?php
class Controller
{
public function __construct($cache = 'APCCache')
{
$this->cache = CacheLocator::locate($cache);
}
However, there are some problems with this approach. The biggest
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 4
— DEPENDENCY INVERSION
problem is that we are now tied directly to the service locator. We have an
inextricable link with the CacheLocator object and the global call to
CacheLocator::locate(). This makes testing possible, but very difficult,
because we have this global involved in our code. In addition, we’re not focused
on extracting the dependency from the Controller class.
<?php
class Controller
{
public function __construct(APCCache $cache)
{
$this->cache = $cache;
}
public function manageData()
{
return $this->cache->get('someKey');
}
}
Note that now we are injecting the APCCache class. This is dependency
injection in its simplest form: there’s no need to make it any more complicated
because it isn’t.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 5
— DEPENDENCY INVERSION
Dependency injection provides the greatest set of advantages and the least
disadvantages over any of the other methods.
Dependency injection isn’t without its disadvantages. Chief among them is that
it’s much more complex to instantiate the Controller class now that we have
dependencies. Previously, we could simply rely upon new Controller() in
both the hard-coded dependency and the service locator dependency models.
Now we must pass in a separate object that must be instantiated, and ultimately
may have its own dependencies. This can lead to long dependency chains,
simply to instantiate the object that we want. Many developers use dependency
inversion containers to help instantiate objects precisely for this reason.
DEPENDENCY INVERSION
Now that we’ve covered dependency injection, let’s cover dependency inversion.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 6
— DEPENDENCY INVERSION
DEPENDENCE ON ABSTRACTIONS
The core of the dependency inversion principle is the concept of dependence
upon abstractions. Both high- and low-level modules should depend upon
abstractions. But what is an abstraction?
PHP equips us with a concept for abstractions in both the abstract class
construct and the interface construct.
<?php
interface MyInterface
{
public function MyMethod();
}
Here we have defined a simple interface with a single method. (Interfaces in PHP
are not required to define any methods; you can have an empty interface.) We
have then defined an abstract class that implements the interface but does not
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 7
— DEPENDENCY INVERSION
actually implement the method in the interface (thus requiring the class to be
abstract). Both of these are perfectly valid abstractions.
This is where the importance of the “details” clause comes into play. Abstractions
should inform the details that go into your concretion, rather than the other way
around. By removing details from your abstractions, you remove the need for
higher-level modules to know the details. This frees the higher-level modules to
work the way you intend, while being unencumbered by the details of the lower-
level modules. This is a much better way to work.
What does this look like in practical terms? Take for a moment our Controller
class, which we are injecting APCCache into:
<?php
class Controller
{
public function __construct(APCCache $cache)
{
$this->cache = $cache;
}
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 8
— DEPENDENCY INVERSION
{
return $this->cache->get('someKey');
}
}
The APCCache class is a concrete class. We’re relying on the concretion, rather
than the abstraction. This means that there’s a potential to leak details into the
Controller class, which could cause us to have higher levels of coupling. To
resolve this, let’s define and use a Cache interface:
<?php
interface Cache {
}
class Controller
{
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function manageData()
{
return $this->cache->get('someKey');
}
}
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 9
— DEPENDENCY INVERSION
Note the change to the Controller class: the hint on the Cache interface,
rather than on the concrete APCCache class.
How is this better? It provides several advantages. First and foremost, we can
more easily substitute different instances of the Cache interface, realizing
the full potential of dependency injection. It also helps us follow the Liskov
Substitution Principle, which we’ll discuss later. It makes testing easier, and
it promotes low cohesion between classes, which is desirable because low
coupling improves reuse.
For the Cache interface, consider that it’s simply a matter of defining a
VoidCache class to have a cache available for development that doesn’t cache
at all. We simply need to put the class together and it meets the requirements:
<?php
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 10
— DEPENDENCY INVERSION
There are several very simple containers on the market that simply take
arguments and return a result. There are also significantly more complex
containers available that use reflection to determine the correct parameters and
can construct objects using parts previously defined, even if the object itself is
not defined. Choosing the right container is up to you.
Let’s build ourselves a dependency injection container to learn how they work.
Let’s start with storing data in the container.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 11
— DEPENDENCY INVERSION
<?php
class Container
{
/**
* @var array
*/
protected $registry = [];
/**
* @param $className
* @param callable $builder
*/
public function register($className, callable
$builder) {
$this->registry[$className] = $builder;
}
}
Here we have a very simple process for registering a new object definition. Our
first argument is the classname. This is best as a fully qualified class name, but
can also be a “named service,” e.g., “DatabaseConnection”. Second, we
accept a callable. We’re type hinting for a callable to make sure we always receive
one. We use a callable here because we don’t want to create the object right
away; we want to lazy load the object on demand. A callable lets us define the
code we’re going to use, without actually running it until it’s needed (sometimes
DI containers can have hundreds or thousands of definitions; we don’t want to
instantiate those unless we need them!).
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 12
— DEPENDENCY INVERSION
<?php
$container->register(SomeClass::class, function() {
return new SomeClass();
});
We also want to be able to build our objects by calling the callable (or the
invokable if we so choose), so we need a method that builds out our objects.
Here’s where we need to make some decisions.
Some containers will have the ability to use reflection and determine the
parameters, then go out and instantiate those objects automagically for you. For
a simple DI container, however, we aren’t interested in doing that. We probably
want to throw an exception in the case where the class we’re trying to build is not
found inside our DI container.
<?php
class Container
{
/**
* @var array
*/
protected $registry = [];
/**
* @param $className
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 13
— DEPENDENCY INVERSION
return $this->registry[$className]();
}
}
Now that we have our build method, we can construct some more complex
objects, including using the container to provide dependencies when they’re
required.
<?php
$container->register(Database::class, function() {
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 14
— DEPENDENCY INVERSION
$container->register(Controller::class,
function() use ($container) {
return new Controller(
$container->build(Database::class)
);
});
Notice how we’re passing the container into the closure, allowing the container
to be used to build our dependency and provide it directly to the Controller
when it was needed.
There are some important antipatterns to watch out for when using a DI
container. First is that a DI container is mainly a piece of infrastructure, and it
shouldn’t at all be involved with the heart of your application. For example,
if you’re passing the DI container into your controllers for locating your
dependencies, you’re probably not using the DI container in the way it was
intended.
In addition, it’s important to note that service location is not the same thing as
dependency injection. Service location relates to the locating of dependencies
on demand and is accomplished by passing the DI container into the class that
requires it or by using a static call to a globally available container. Neither
approach is a good one: you’re tied directly to the DI container in both, and
you can’t effectively type hint or evaluate your dependencies for correctness at
runtime.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 15
— DEPENDENCY INVERSION
CONCLUSION
Dependency inversion is an essential component of the SOLID principles,
offering us the foundation upon which we will build all the other components.
With dependency inversion we can know that our dependencies are properly
abstracted and that our application is architected in a reasonable way.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 16
One of the advantages to using dependency inversion is that it prepares us
for the possibility of substituting objects of similar type for one another. This is
what the Liskov Substitution Principle is all about: the substitution of one object
for another object of the same type (or subtype), without breaking the program.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 18
—LISKOV SUBSTITUTION
This was a major breakthrough in computer science and today remains a crucial
principle in object-oriented design and development. The
creation of a class of objects that behave
BARBARA LISKOV AND
similarly but are internally unique is a JENETTE WING
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 19
—LISKOV SUBSTITUTION
The formal specification piece comes from the concept of defining an interface
in the first place. For example, we defined an interface for our Cache class in
the last chapter, which is a formal interface by Meyer’s standard.
<?php
interface Cache {
The interface we have defined is also precise. It specifies all the preconditions,
postconditions, and invariants that we need to worry about right in the
specification. The precision of our interface can be improved by adding type
hints and return type hints; while that would be impractical with the example
we have provided, it is a valuable thing to do for other specifications and only
enhances the overall contract.
Finally, our specification is verifiable. We can test it, and we can do so easily. Any
cache I drop into a set of unit tests written for this interface will pass, assuming
that the preconditions and postconditions are preserved. If I have broken any
of the rules of the interface, the tests will fail, and I will know that I have made a
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 20
—LISKOV SUBSTITUTION
The same is true for postconditions. Return types are part of the postcondition:
they enforce what will be returned. You should use return types as part of your
interfaces to enforce what is being returned. Not only does this help further
define the interface specification, but it also cleans up your code. For example,
in many applications it’s common to return a string or an object on success, with
a boolean on failure. This is not possible with return type declarations. Instead,
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 21
—LISKOV SUBSTITUTION
you must throw an exception and return only the type you’re expecting. This
results in much better, bug-free code that’s easier to read.
Designing by contract makes it much easier to maintain and work with our code,
as well as to follow the Liskov Substitution Principle.
<?php
class User_Controller
{
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 22
—LISKOV SUBSTITUTION
{
if ($contacts = $cache->get('user_contacts')) {
return $contacts;
}
}
}
This simple controller takes Cache as an argument and uses the cache to look
up the user contacts if they exist in the cache.
Presume for the moment that we are using Memcache as our cache type. (We
could be using any other cache but for this implementation we’ll focus on
Memcache.) Our implementation makes sense, except for a persistent bug:
Memcache keeps failing to stay connected. In that case, we want to add a
MemcacheCache::reconnect() method to the cache object:
<?php
This simple method will reconnect to the server for us, ensuring that we always
have a connection. Great! Now let’s add this to our User_Controller class:
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 23
—LISKOV SUBSTITUTION
<?php
class User_Controller
{
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
But now we have a problem: we’re type hinting on the Cache interface, but
we’re depending on details that are specific to the MemcacheCache class
(particularly the MemcacheCache::reconnect() method).
This breaks our contract. The Liskov Substitution Principle no longer applies
because we can no longer rely upon the Cache interface to tell us if we’re
meeting the requirements of the class. In addition to breaking the contract,
we are also breaking the Dependency Inversion Principle, because we are now
dependent on the details of the MemcacheCache class, rather than dependent
on the abstraction in our Cache class.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 24
—LISKOV SUBSTITUTION
CONCLUSION
The Liskov Substitution Principle is a powerful component of the concept
of reuse and replacement of objects, and it allows us to achieve great things
through substitution and replacement. However, there are pitfalls along the
way: it’s possible to create objects that pass a type hint, but don’t actually follow
the principle as a whole.
Yet it’s impossible to completely avoid extending the interface in real world
application development. So what are we to do? In the next chapter we’re going
to discuss how we create contracts that honor the Liskov Substitution Principle
(and the Dependency Inversion Principle), and how we ensure that our objects
are able to be modified and extended.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 25
A TALE OF TWO PRINCIPLES
If you look up the definition of the open/closed principle on Wikipedia, it will
tell you that “software entities should be open for extension, but closed for
modification.” What it doesn’t tell you, at least not at first, is that this is one of
the least understood, most violated, and most challenging principles in the
entire SOLID world.
This is all for a good reason: depending on who you ask, there is more than one
open/closed principle. In fact, there are two.
Confused yet? That’s okay. We’ll explore both and see how each principle
applies, both historically and practically, to the object-oriented code that we
write today.
In other words, I can extend a class all day long, but I cannot change the
internals or the behavior of that class once it has been set. The act of inheriting
is what gives me the ability to extend and modify that class. But the original is
set in stone.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 28
— OPEN/CLOSED PRINCIPLE
What Meyer is asserting here is that the ability to inherit from a parent class
gives you the ability to have an open class, because the child may implement
new features.
T H E P O LY M O R P H I C O P E N / C L O S E D P R I N C I P L E
So, if we reject Meyer’s Open/Closed Principle as impractical, do we reject
the “O” in SOLID altogether? Absolutely not. The computer science world has
shifted, and the “O” in SOLID has shifted right along with it.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 29
— OPEN/CLOSED PRINCIPLE
<?php
class User_Controller
{
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
The reason that this violates the open/closed principle has to do with object
type. An object’s type refers to its behavior, which is defined in the public
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 30
— OPEN/CLOSED PRINCIPLE
methods that an object exposes to the rest of the world. The computer is
uninterested in the class name we’ve given an object. It only cares what public
methods are accessible and what we’ve given the object as its behaviors.
When we extend the interface, we change the inherent type of the object. Even
though it might pass a class name type hint, we have still changed the type. It’s
important to recognize that PHP is not type safe! PHP will allow objects that are
technically of a different type to pass a class-name-based type hint, but it will
not check true types. We must do this ourselves as developers.
The answer lies in making a tradeoff. Here, in this case, we want to type hint
on MemcacheCache specifically. This technically ignores the Dependency
Inversion Principle because we’re technically depending on details, but it allows
us to honor two other principles. By definition, MemcacheCache is unique, so
it’s not substitutable (which honors Liskov Substitution), and, by extending it
and acknowledging the extension, is creating a new type, we honor the Open/
Closed Principle.
<?php
class User_Controller
{
public function __construct(MemcacheCache $cache)
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 31
— OPEN/CLOSED PRINCIPLE
{
$this->cache = $cache;
}
It is impossible to follow the SOLID principles, or any set of rules, 100% of the
time. That’s why they’re considered guiding principles. They are not laws; they
do not apply all the time. It’s important to recognize this fact.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 32
— OPEN/CLOSED PRINCIPLE
So, when you find yourself faced with a situation where you must use a concrete
class over an abstraction, or you need to break the interface, accept the
tradeoff, and move on. This isn’t permission to ignore the principles at will. It’s
acceptance that real life doesn’t always follow the prim and proper science of a
computer science classroom.
The return type refers to the type of response from a method or function (e.g.
string, object, array). This is part of the interface, because it helps us to identify
how a function behaves. A function that returns an array and only an array and
suddenly starts returning a string will only confuse our code.
If you are writing PHP 7 code, you should be using return types as often as
possible with your objects. Return types are easy to define and look something
like this:
<?php
interface Cache {
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 33
— OPEN/CLOSED PRINCIPLE
As you can see, we define a return value for our methods. These return values
are important because they inform our use of the interface and make things
consistent across our code. We can’t and we won’t suddenly start returning
arrays or objects from the Cache::get() method.
But what about cases where you are returning a string on success and a null on
failure? Cases like this:
<?php
return $a + $b;
}
This is a classic old style error handling mechanism, whereby we return null
as quickly as possible when we can’t execute the code. But this is broken for a
number of reasons.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 34
— OPEN/CLOSED PRINCIPLE
First, this is broken because we have more than one return type, meaning we
have to test for a particular return type. Consider the following checking code:
<?php
Because I have to test whether or not this is null, my code gets pretty ugly pretty
fast. And as a result, my code is littered with lots of if(is_something())
references.
In addition, it’s impossible for me to predict with certainty what the behavior
of this function will be. I cannot know with certainty that I’ll get back a valid
response each and every time the function completes.
What I should do instead is use exceptions. When I’m unable to process the
addition for any reason, that’s an exceptional event and I should throw an
exception that the calling code can handle or pass up to its consuming code.
Now my code looks like this:
<?php
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 35
— OPEN/CLOSED PRINCIPLE
{
if (! $a || ! $b) {
throw new \InvalidArgumentException;
}
return $a + $b;
}
If my arguments are invalid, I raise the exception; the only time I get a return
value from the function is when the function worked properly.
<?php
try {
$result = addNumbers($a, $b);
// do something
} catch (\InvalidArgumentException $e) {
$this->log($e->getMessage());
}
At first glance, this might appear to be almost the same as the is_null()
check, but it’s not. First and foremost, there’s not a break between the call
of the function and the work being done after getting the return value. And
second, by catching an exception we have the ability to trigger more than one
exception if necessary and bubble up problems to calling code, or to even halt
the application entirely.
Some might argue that PHP 7.1’s inclusion of nullable return type declarations
means that returning null is valid. I disagree. I feel strongly that returning
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 36
— OPEN/CLOSED PRINCIPLE
The bottom line is, the return type is part of the object’s definition, and it
should be consistent across all calls. You should use return type declarations as
often as possible to cement the return type you expect and make your objects
predictable.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 37
We know now that extending the interface is not a preferred way to
increase the functionality of our application. But if we can’t extend the
interface, what kind of interfaces should we be building in the first place?
The answer is that we should be building small interfaces. Our interfaces should
be discrete.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 39
—INTEGRATION SEGREGATION
It’s easy to define an interface that contains every conceivable option within
it. For example, going back to our prior example, we might decide that the
reconnect() method really does need to be a part of the Cache interface,
so we might define it thusly:
<?php
interface Cache {
But what happens now, when we go to implement a cache that doesn’t require
connection with a server? Consider a file-based cache or APC: neither of them
requires a connection with the server. So, our code ends up looking like this:
<?php
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 40
—INTEGRATION SEGREGATION
Notice how we now have a method that we are not implementing, that
generates an exception at runtime, for no reason other than it’s not needed.
This violates the interface segregation principle, because our object is
dependent upon a method that it does not care about or need to implement.
This is exactly the reason that small interfaces are better. The smaller the
interface, the less likely it is that you’ll find yourself needing to implement
methods that you don’t need.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 41
—INTEGRATION SEGREGATION
A SMALL EXAMPLE
Within the Standard PHP Library (SPL) there are a number of different interfaces
offering a variety of different functionality. For example, you could create an
object that functions identically to an array using the interfaces provided within
the SPL.
When talking about small interfaces, take for example the Countable
interface:
<?php
interface Countable
{
public function count();
}
This simple interface does one thing for us: implements the code needed so
that when we call count() an object, we get back an integer that represents
the count.
We might want to take this a step further and implement a full array. There’s an
interface for that, as well. It’s called Iterator and it’s available to implement
the behaviors of an array:
<?php
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 42
—INTEGRATION SEGREGATION
This interface, when applied to an object, makes the object iterable. In other
words, we can use the object in a foreach loop.
There may be cases where we want our iterable object to be countable as well.
In that case, we can combine the Iterator and Countable interfaces to
create an object that implements both:
<?php
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 43
—INTEGRATION SEGREGATION
return key($this->collection);
}
Notice how we took two smaller interfaces and combined them together to
make a larger interface, where no methods needed to be omitted.
This is one of the powers of interfaces: the ability to create larger classes from
smaller interfaces. For type-hinting purposes, we would say that if we care
about both Iterator and Countable we would typehint on ArrayClass.
We can also typehint on either of the specific interfaces if those individual sets
of methods are what we need to consider for our application.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 44
The Single Responsibility Principle is responsible for a paradox: more
writing has been done about this principle than any other, yet people
understand this principle the least.
My goal, then, in this chapter, is to set the record straight. Hopefully I can bring
some clarity to the concept of the Single Responsibility Principle.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 46
— SINGLE RESPONSIBILITY
Not so much.
This has oft been boiled down to saying, “one class, one job.” In other words,
each single class should have a single job that it performs, no more, and no less.
This seems on its face to be what the Single Responsibility Principle is all about,
right?
Wrong.
The definition I provided above doesn’t mention the concept of “jobs” at all.
It mentions responsibilities. Specifically, it mentions the responsibilities that
a class has, and how those responsibilities are encapsulated and aligned. It is
silent on the notion of jobs and the like.
This is a good thing, because the first thing most developers get hamstrung
by is the notion of “what’s a job, anyway?” And then we get into a technical
discussion of what constitutes a job, listing out specific jobs, rather than
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 47
— SINGLE RESPONSIBILITY
How then are we to evaluate this definition down to something we can easily
understand? The definition is distillable, like the “one class, one job” definition,
but we have to look for it.
This little tweak has a big impact. Specifically, we are now focused on the
responsibility, or to use another word, the domain of a particular class. What
domain does the class rule over? What domain is the class responsible for? How
does the domain impact the application as a whole, and how can we make sure
that we’re managing that domain properly?
One thing of note to bring up here: when I’m talking about domain I’m talking
about an area of responsibility, not the domain layer of the application. They are
separate and distinct components.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 48
— SINGLE RESPONSIBILITY
Consider for a moment the base PDO class in PHP. It has several jobs. First, it
begins and ends transactions with the database. This is an important task that
belongs at the connection-level management component, which happens to
be PDO’s second job. The PDO class is also capable of sending simple queries to
the database and returning result sets. Finally, PDO is responsible for preparing
complex queries and returning a PDOStatement object. (Note: this isn’t
discretely a job of the domain, except that a connection is required).
The PDOStatement object has its own distinct set of jobs. It represents
a single PDO statement or query; it’s responsible for maintaining the state,
allowing for binding of values, etc., along with executing the statement against
the database; and finally, it’s responsible for returning the results back to the
consuming code. Each of these are separate jobs, but they fall under a common
set of responsibilities. Because they all fall under common responsibilities, they
belong within the various classes that PDO offers.
Imagine for a moment that we had broken out the jobs into individual classes.
We’d have tons of objects to work with! We’d have separate objects for
returning results and running simple queries. We’d have separate objects for
preparing queries and managing the database connection. All these objects
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 49
— SINGLE RESPONSIBILITY
would be tightly coupled together, because they are all dependent upon one
another. That’s not ultimately what we seek to achieve by applying the Single
Responsibility Principle.
Instead, we are focused on domain. The domain of the PDO class is connection
management. All the features of the object are narrowly aligned with that
domain save one: the ability to send simple queries. Even that feature is a matter
of convenience rather than a core behavior. The domain of the PDOStatement
object is to represent a specific query and provide functionality around using
that specific query to send or receive data from a database.
When we focus on domain rather than on discrete jobs, we come away with
far more robust objects. We also decouple our code, because the resulting
code is not dependent on a large number of related objects. When we use our
dependency, it’s the only dependency that we need because it is capable of
doing the entire job. This is called “encapsulation”.
E N T I R E LY E N C A P S U L AT E D
Remember back to our definition of the Single Responsibility Principle: “…that
responsibility should be entirely encapsulated by that class.” We asserted that
we should encapsulate all the duties pertaining to a particular responsibility
within a single class. But what exactly does this mean?
The definition I like best lies with Robert Martin, who said that you want to group
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 50
— SINGLE RESPONSIBILITY
together code that has “a single reason to change.” Martin states in a blog post
that we should “gather together the things that change for the same reasons.
Separate those things that change for different reasons.”
Contrast that with a class that incorrectly does double duty. Take for example
a front controller that also acts as a router. The front controller is responsible
for instantiating the controller object, dispatching the controller object, and
determining what route was called so that the proper controller object can
be loaded. Those are three discrete jobs. One could argue that they fall under
a common domain, but where the class fails is when things change. Things
change for the router component when the route changes. Things change for
the instantiation and dispatching of the controller when the controller changes.
Those are distinct and different reasons to change.
In light of that, it makes sense to abstract the router away from the front
controller and into its own class. The router can change when the route path
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 51
— SINGLE RESPONSIBILITY
changes. Refactoring this away also enhances the possible functionality of the
router, providing a greater ability to develop new features for the component
that is no longer constrained by the front controller. The front controller, on
the other hand, is now free of the router logic and can be focused solely on
instantiating and dispatching the controller when the controller is properly
identified by a third-party service (i.e., the router as an object).
S P OT T I N G S I N G L E R E S P O N S I B I L I T Y V I O L AT I O N S
Sometimes, it can be difficult to identify when and where the Single
Responsibility Principle is being violated. Being close to a problem often
introduces a kind of blindness that can make it hard to see when things ought
not be grouped together or should be refactored apart. There are several
techniques you can use to identify these kinds of mistakes and fix them in your
own code. It’s important to note that none of these are hard and fast rules,
but guidelines aimed at helping you to identify Single Responsibility Principle
violations in your own code.
The first thing I look for is the number of dependencies being passed into a
particular object. Large numbers of dependencies (say, more than 5) usually
indicate that the object is doing far too much and should be refactored. There
may indeed be legitimate reasons for having a large number of dependencies in
a particular class, but that doesn’t mean that it doesn’t merit a look and see if
perhaps there’s an opportunity for refactoring or restructuring of the underlying
code.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 52
— SINGLE RESPONSIBILITY
In some cases, you’ll see only a few dependencies injected into an object, but
that object will not use all the dependencies in all the operations. As another
general rule, dependencies should be utilized by the operations of the class.
For example, if you are writing a service that inserts data into a database and
retrieves data from the same database, it makes sense to inject the data storage
component into the service for persistence. What doesn’t make sense is for you
to add a part of the class that formats the data and doesn’t utilize the database
at all. That belongs in a separate helper class that you should create outside the
service and potentially inject into the service.
Finally, there’s what I call The Conjunction Test. If you can define your class
using the word “and” to separate responsibilities, it’s probably time for a
refactor. For example, if you were to say, “My class manages database data
and formats data,” that would indicate that it’s time to refactor for a Single
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 53
— SINGLE RESPONSIBILITY
Responsibility violation.
The problem with this rule, of course, is that there are many cases where we
can describe classes with “and” while getting away with it. For example: “PDO
maintains the database connection and begins/ends transactions.” When
coming across cases like this, dive deep to determine whether or not you’re
faced with a concern about responsibilities or jobs. In PDO’s case, we have a
mix of responsibilities and jobs: “PDO maintains the database connection” is a
responsibility, while “…and begins/ends transactions” is a job. Be careful that,
when using The Conjunction Test, you’re certain that you’re evaluating apples to
apples.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 54
The five SOLID principles are a great starting point for understanding
object-oriented design. They provide some anchors for organizing and writing
our code, and they offer us a basic understanding of object-oriented best
practices. Adhering to these principles alone would dramatically improve most
of the code most of us write, but there are a few other important rules and laws
for us to adhere to, laws like the Law of Demeter.
The Law of Demeter (also called the principle of least knowledge) is a rule
devised by Ian Holland at Northeastern University. It states the following
principles:
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 56
—LAW OF DEMETER
LIMITED KNOWLEDGE
The Law of Demeter is about what an object can know and who an object can
talk to. It’s important to note that this principle is not about how to organize
your code, but about how your code should talk to other bits of your code. That
makes the Law of Demeter different.
The first principle is that each object should only have limited knowledge of
other objects. More specifically, it should only have knowledge of objects that
are closely related to the subject.
There are any number of ways to violate this first principle. For example, take
the following code sample:
<?php
class MyController
{
public function __construct(\PDO $pdo)
{
$this->model = new Model($pdo);
}
}
In this example, we’re passing a PDO instance into the controller and
instantiating a model. This is a very common code example from many
different types of applications. The problem here is that whenever we require a
dependency, we are essentially making that dependency known to the subject
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 57
—LAW OF DEMETER
This violates the first principle by giving our object knowledge of another object
that it doesn’t need to know; it’s not closely related. Instead of passing in PDO,
we should inject the Model directly, like this:
<?php
class MyController
{
public function __construct(Model $model)
{
$this->model = $model;
}
}
Now the object only knows about a relation it needs to know about: the model
class that it’s using.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 58
—LAW OF DEMETER
<?php
class MyController
{
public function someMethod()
{
$this->object
->someParameter
->someOtherParameter
->someMethod();
}
}
In this example, we call through three different objects in order to find the
method that we want to call. The first object (“object”) is known to us. That
is a familiar object, and we should feel comfortable communicating with that
object.
The second and third objects, however, are not familiar; they are foreign.
In order to communicate with them we must learn the internal API of the
MyController::$object parameter. This API would be subject to change,
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 59
—LAW OF DEMETER
making it possible that our code would break later just from trying to call a non-
object on an object.
The second and third objects are “strangers.” We should not talk to them.
Talking to them makes them dependencies of our object and requires us to
maintain the internal state of the object as well as our own.
O N LY TA L K T O C L O S E F R I E N D S
But what happens if we’re in a situation where a getter returns an object for us
to use? Is that okay? Generally speaking, that still presents problems for our
object.
When we return an object and we interrogate that object for useful information
or behavior, we have introduced a dependency that’s not handled through
dependency inversion, setter injection, or some other means. We can’t type hint
on that object, either, meaning we’re at the mercy of the producer to produce
an object that matches our expectations. (Some of this is controlled by the fact
that the producer was type-hinted itself.)
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 60
—LAW OF DEMETER
<?php
class MyController
{
public function checkUserAuth()
{
$user = $this->user->getUser();
return $user->authenticate(
$this->username,
$this->password
);
}
}
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 61
—LAW OF DEMETER
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 62
—LAW OF DEMETER
the method and are permitted to be accessed from within the method.
The fewer calls you make to other objects can also result in fewer software bugs.
This makes sense from a logical perspective: the fewer calls in general, the less
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 63
—LAW OF DEMETER
complex your code, and the less likely you are to produce a bug. Of course, no
code is perfectly bug-free, and no developer is going to be able to produce code
that’s 100% free of all noted bugs, but it makes sense that the simpler a code
base, the lower the number of bugs overall.
Disadvantages to the Law of Demeter exist as well. In many cases, the Law
of Demeter increases the number of wrapper functions that are necessary to
accomplish the goals of the system. For example, rather than invoking the
wheels directly, a method must be written to wrap that functionality for the
driver of the car. CCar.wheels.forward() becomes Car.forward()
which essentially duplicates the forward() behavior without adding any new
functionality.
In addition, while the Law of Demeter limits the size and scope of the method’s
API knowledge, it increases the size of class APIs by forcing the development of
these wrapper calls or calls to internal members of the class. For example, a Car
interface without the Law of Demeter might have only a few methods. With the
Law of Demeter, that same class is going to have a few dozen methods in order
to provide support for consuming classes.
Disadvantages aside, I believe the Law of Demeter offers something useful and
provides adherents with better code overall. It’s impossible to adhere to the
Law of Demeter all the time (and not all libraries make it possible), but it’s still a
good practice to follow in your own code.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 64
The six principles we have discussed to this point serve as shining stars
for how to build lasting, maintainable, understandable object-oriented
applications. They represent best practices and present us with opportunities
for improvement in all our applications and how we deal with them.
But what the areas that serve as “black holes” to object-oriented design,
areas where best practices are challenged? These antipatterns of software
development serve to illustrate these bad practices, so that you can identify and
avoid them in your own code.
SINGLETON
The Singleton is perhaps the most famous of all design patterns, owing to
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 66
— ANTIPATTERNS
its simple implementation and the fact that it is the only design pattern to
specifically represent itself in code. But the Singleton pattern is fraught with
challenges and should be avoided at nearly all costs.
<?php
class MySingleton {
return self::$instance;
}
}
Because the Singleton is designed the way it is, there are limited ways to
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 67
— ANTIPATTERNS
instantiate one in PHP. You must first mark the __construct() and __
clone() methods as private. In addition, you provide a static function (in this
case MySingleton::getInstance()) for the creation or retrieval of the
Singleton object. Within that method, you check to see if a singleton has been
generated; if not, you create it and then return it. Otherwise, you return the
existing object.
1
Instantiating objects is technically a responsibility, so objects that create other objects are responsible
for that behavior and should not be given additional responsibilities. This rule does not apply to creat-
ing value objects or simple internal-use-only objects.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 68
— ANTIPATTERNS
The reality is that most problems can be solved without using a Singleton. Given
that fact, most developers should not rely on the Singleton pattern as a pattern
of choice and should consider it an antipattern.
TIGHT COUPLING
We have alluded to tight coupling in the past when discussing The Law of
Demeter, but we have not discussed the subject specifically or in depth. Tight
coupling is essentially a measure of how dependent objects are upon one
another. Tightly coupled objects are incredibly
interdependent upon one another, and this violates both the Law of Demeter
and the SOLID principles.
Tight coupling is a problem in a code base because it slowly strangles the code
base to death. When objects are tightly coupled, you must maintain a larger
mental map of the code base in order to function in it. You must also be more
careful to make changes, avoiding violating one of the conditionals that is
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 69
— ANTIPATTERNS
Tight coupling also makes testing that much harder. When objects are tightly
coupled together, it becomes difficult or impossible to extricate one object from
another for testing or mocking. Testing in isolation is a fundamental element of
being able to test code.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 70
— ANTIPATTERNS
UNTESTABLE CODE
Many developers consider tests as an afterthought. But the best developers
consider tests as the finished product, the way of proving what they sought
to build and demonstrating their completed goals. Viewing tests this way also
helps in writing code that’s ultimately testable and able to be tested through
automated means.
We discussed in the last section how tight coupling produces code that is hard
or impossible to test. This is a reality of tight coupling and a hallmark of this
kind of coding approach. But tight coupling isn’t the only coding approach that
produces difficult-to-test code.
One of the main reasons we use dependency injection is that it makes testing
possible in many cases where otherwise it might not be. For example, take this
code:
<?php
class Database
{
public function __construct()
{
$this->cache = new ApcCache();
}
}
If we try and test this, we’ll be hamstrung by the fact that the ApcCache class is
hardwired into the code. We won’t be able to mock that or get away from that,
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 71
— ANTIPATTERNS
so we’ll only be able to write a functional test at best. Many popular frameworks
today are built this way: they allow you to write functional tests, but never unit
tests,2 in order to test your code. I think this is a major mistake.
We can fix this using dependency injection and inject the object we want. This
will allow us to inject a mock, a substitute object, in order to avoid using the APC
cache (which is inconveniently not available on the command line anyway).
<?php
class Database
{
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
One way that you can ensure your code is testable is by writing tests beforehand
and making your code pass them before you move on to the next test. This is
called Test-Driven Development and is incredibly
popular. It’s worth taking a read of Robert Martin’s The Clean
2
There are essentially three kinds of testing: unit testing, integration testing, and functional testing. Unit
testing, the kind of testing we’re focused on here, tests individual code units to see how they perform in
isolation. Integration testing tests the integration points between objects in the code. Functional test-
ing tests a system end-to-end. It’s the least precise level of testing but offers the broadest spectrum of
testability.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 72
— ANTIPATTERNS
P R E M AT U R E O P T I M I Z AT I O N
We all want to write code that is performant and meets the needs of the people
who have hired us. This is an important part of our job, and we should work hard
to develop this kind of code. However, there exists a temptation to try and make
our code extra performant as we write it, without considering the implications of
that performance tuning from a production standpoint.
It’s not that performance tuning in and of itself is bad. It’s that you need hard
and real numbers in order to do it well. Nobody is omniscient, which makes it
impossible for us to accurately and adequately predict what the consequences
of our tweaks might be, especially related to performance. We need real
numbers to guide our decision making, instead of guessing at changes that
might or might not improve performance.
In addition, when we are writing code, we are not faced with real-world
conditions against which we are testing. We are instead facing the subset of
nearly ideal conditions in which we are writing our code. We need the real-world
conditions of latency, concurrency, race conditions, and the like before we’re
going to be fully able to grasp the performance implications of the work we’re
doing.
That’s not to say you should be sloppy in the work that you do. Surely not. You
should strive to avoid the most common performance pitfalls already known
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 73
— ANTIPATTERNS
(using a while loop in a while loop, etc.), but you should not go farther than that.
You should not guess that something won’t perform. You should prove it.
D O N ’ T R E P E AT Y O U R S E L F
Have you ever come across code that looked eerily familiar to other code in the
same application? Perhaps it used the same variable names; surely, it used the
same structure. You have encountered duplicated code, an insidious type of
code that makes its way into applications of all shapes and sizes.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 74
— ANTIPATTERNS
Duplicated code comes with several challenges. The first is maintenance. It’s
difficult, if not impossible, to maintain code when there is rampant duplication.
Inevitably, you’ll fix something in one part of the application and find out that
you forgot to fix it someplace else. You’re forever chasing the duplicated code
trying to squash bugs.
In addition, duplicated code makes for duplicated tests. Your tests (assuming
you have some) will be duplicated in order to ensure coverage of the areas of
duplication. This creates testing overhead and, potentially, cascade failures
when some bit of the code changes and breaks your tests. This makes your tests
less reliable because several different failures are harder to analyze and fix than
one single failure.
Fixing duplication is hard but possible. It starts with accepting the duplication
and setting about to fix it. Fixing it requires identifying areas where duplication
exists and factoring the duplicated code into submodules, domain components,
or the like until the code is sufficiently de-duplicated. In cases where there is a
large amount of duplication except for small components or routines that are
unique, look to patterns like the Strategy Pattern to solve these sorts of issues.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 75
— ANTIPATTERNS
NONDESCRIPTIVE NAMING
Let’s take a look at some code:
<?php
$dr = $d->fetch($s);
What is going on in the code sample above? There are a few things we can
tell right away: $d->fetch() returns an array, because we’re using it in a
foreach loop. But we don’t know where the source of the data is coming from,
and we don’t know much about the context.
<?php
$databaseResult = $database->fetch($sqlQuery);
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 76
— ANTIPATTERNS
This makes more sense! We have a database that we’re fetching data from using
a SQL query. We get back an array of database results, which we pass into a
foreach loop and apply some algorithm or routine.
When I sat down to write this book, I took the opportunity to apply word to
paper in a way that would communicate something extraordinary to the reader.
I wanted to share new insights and be clear and concise. I hope that I have
achieved that with this book, and I implore you to do the same with your code.
Agonize over names and structures. Consider using idioms that make sense for
the environment you’re writing for. Develop themes. Be consistent, clear, and
concise.
Remember that code is written for other humans to read, not for computers
to parse. The importance of writing code for these other humans cannot be
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 77
— ANTIPATTERNS
Part of making sure that you communicate purpose is picking names that
matter. In our first code sample, we had a long list of names that did not matter.
They were one or two characters long and they communicated nothing. The
code was syntactically correct; it would compile and run. But it would not inform
a reader as to what the code’s purpose or intent was.
By picking names that matter, you project your thought process into the code
and make it clear to another person how you arrived at your decisions. Source
code spends approximately 80% of its life in maintenance. It’s important to
carefully name things so that other developers maintaining your code can
understand what you intended. Naming a variable $deleteUserCommand
is explicit; $duc is not, even if they have the same purpose. Pick names that
matter and make sense for the purpose that you’re trying to communicate.
<?php
/**
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 78
— ANTIPATTERNS
* @param $xdd
*/
public function processXdd($xdd)
{
// do something
}
Does this docblock provide any insight that I couldn’t have gotten simply by
reading the signature of the method? The simple answer is no, it doesn’t. But
what if I’m more explicit?
<?php
/**
* Processes a single XDD for removal and archive.
*
* @param $xdd
*/
public function processXdd($xdd)
{
// do something
}
Now I’ve used actual words and my docblock communicates purpose and intent
for what the method is trying to achieve. Now we’re getting somewhere!
I am not a fan of inline code comments, because code is liable to change,
while the presence or absence of a method, and the intent of the method, is
more likely to be stable for a long period of time. Thus, it is safer to comment a
docblock with the intent of the method (not the means of the method) than it is
to comment inline in the code.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 79
When it comes to error handling in object-oriented applications, there
is really one chief way we do it: we use exceptions. Exceptions are built-
in objects in PHP that are designed for carrying error messages and halting
execution until they are resolved. Exceptions provide a flexible framework for
resolving problems in our applications.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 81
—EXCEPTIONS
<?php
Notice the new keyword used to instantiate the exception. This is because
exceptions are simply objects in PHP and can be instantiated like any other
object. When we dump the exception, we get the following data:
object(Exception)[1]
protected 'message' => string '' (length=0)
private 'string' => string '' (length=0)
protected 'code' => int 0
protected 'file' => string '/Users/sarah/Sites/test.
php' (length=29)
protected 'line' => int 3
private 'trace' =>
array (size=0)
empty
private 'previous' => null
The first thing to note is the fact that an exception is an object, and PHP
recognizes it as such inside var_dump(). We have several properties as wel:
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 82
—EXCEPTIONS
Exceptions are objects and can be treated as objects for the purpose of
accessing data and type hinting.
The exception API contains several helpful methods for managing exceptions
and accessing the internal data of exceptions:
<?php
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 83
—EXCEPTIONS
USING EXCEPTIONS
Since exceptions are simply objects, they can be extended the same way that
any other object can be extended: inheritance. Why would you extend an
exception? To create exceptions of specific types, so that we can type hint.
<?php
Now that we have our special exception type, we can throw and catch the
exception. Exceptions in PHP are thrown, meaning they are injected, into the
runtime and become the most important object in play. It must be handled, or
the entire runtime grinds to a halt.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 84
—EXCEPTIONS
<?php
<?php
try {
throw new MyException(‘Something went wrong’);
} catch (MyException $e) {
echo $e->getMessage();
}
Notice how in the catch block we specify the type of the exception we want
to handle. This is how we differentiate between different exceptions and
specify the exception that we wish to catch. This is part of why creating custom
exceptions is important: it allows us to be specific about the type of error that
we are catching.
But what if we want to catch multiple types of errors within the same try-catch
block?
E XC E P T I O N C ATC H I N G P R E C E D E N C E
It’s possible within a try-catch block to catch more than one exception type and
to specify multiple exception types. However, there are rules around how to
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 85
—EXCEPTIONS
achieve this that must be followed, or you can get unpredictable results.
To catch multiple types of exceptions, you need only add an additional catch
block to the try-catch block:
<?php
try {
throw new MyException('Something went wrong');
} catch (MyException $e) {
echo $e->getMessage();
} catch (Exception $e) {
echo "Caught a base exception.";
}
Notice now that we are catching any exception that matches MyException or
the base Exception class.
An important rule to observe is that PHP will only trigger one catch block,
and it will trigger the first catch block that it finds immediately following the
throwing of the exception. This makes ordering very important. Recall how
MyException extends from the base Exception class. You might expect
both catch blocks to be triggered but they won’t be: only the first block that
prints the message will be triggered. The second will never be triggered.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 86
—EXCEPTIONS
<?php
<?php
try {
If you guessed that the first block would be triggered, you are correct. Why?
Because MyExceptionTwo matches the type hint criteria for MyException
in all cases, even if it’s not the most specific type hint. In order to properly type
hint, you must reverse the order of the example:
<?php
try {
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 87
—EXCEPTIONS
In this example, MyExceptionTwo is the first exception we type hint for, and
so the proper error handling will take place, as we expect.
It’s also possible to type hint multiple exceptions on the same line. For example,
you can designate that all instances of MyException and MyExceptionTwo
get handled the same way, with the same catch block, using the pipe operator
between the exception names, like so:
<?php
try {
F I N A L LY
PHP supports a new keyword as of PHP 5.5 called finally. The finally keyword
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 88
—EXCEPTIONS
<?php
try {
Notice how the finally keyword appears at the end of the list of exception type
hints and provides code to be executed. Note that this code will be executed in
all cases, whether an exception is generated or not and even if an exception is
unhandled.
ERRORS AS EXCEPTIONS
Starting with PHP 7, fatal errors became exceptions. This means considerably
more in terms of flexibility for error handling in PHP 7.
First and foremost, all errors now extend from a Throwable base interface.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 89
—EXCEPTIONS
Exceptions do as well.
It is possible in PHP 7 to catch errors that should be fatal and suppress them.
This is a poor practice except for logging purposes. Fatal errors should remain
fatal and should terminate your application, while exceptions that you generate
can be handled and resolved appropriately.
If you want to catch all errors (for example, to log fatal errors), you can type hint
on the Throwable interface.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 90
Most of us are familiar with the pattern known as Model-View-Controller
(MVC). It’s a standard pattern popularized by Ruby on Rails, and provides the
foundation for much of the modern framework world in which we operate.
The problem with MVC is that it presents a number of issues that make using
MVC largely unworkable. As a substitute in steps Action-Domain-Responder,
which solves many of these issues and provides a solution to the pitfalls of
Model-View-Controller.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 92
— ACTION-DOMAIN-RESPONDER
This is well and good, except for the fact that MVC doesn’t actually do this. This
isn’t the exact pattern we follow. It’s closer to this:
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 93
— ACTION-DOMAIN-RESPONDER
Note the difference here. Instead of the controller passing data to the model
which in turn passes the data to the view, the data is passed straight away into
the view via the controller. The model and the controller communicate directly
with each other, and the view gets its data straight from the controller.
But this still isn’t quite right. We assume a view layer here, which is fine and
well, but the fact remains that most of the time we don’t have a true view layer.
A view layer contains logic for presenting the template to the end user; in most
cases we simply manage the templating process in the controller, like so:
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 94
— ACTION-DOMAIN-RESPONDER
This model most accurately represents the state of MVC in the modern PHP
world. Controllers do most of the work, dispatching requests to various other
pieces in order to gather their information, and then handling the bulk of the
request itself.
This leads to several problems. The first problem is that controllers end up
bloated and filled with cruft. In addition, most of the algorithms that really
belong in the domain end up in the controller; this leads to controllers being
procedural rather than objects in their own right.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 95
— ACTION-DOMAIN-RESPONDER
The list is long and extensive and continues on, but we get the point.
A BETTER WAY
Introducing Action-Domain-Responder, or ADR. A second generation model for
representing objects and managing application logic, ADR provides a pattern
that solves many of the harsh realities of MVC without sacrificing the flexibility
that MVC offers the end user.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 96
— ACTION-DOMAIN-RESPONDER
The Responder gets its data from the Action, which provides the data from the
Domain. The Domain receives data directly from the Action as well, but does not
return simple unstructured data; it returns a Payload object that the Action
passes straight into the Responder.
And here’s the coolest detail: the action is only three or four lines long at most. It
contains no conditionals, and provides no templating instructions. The Action’s
sole purpose is to pass data from Point A to Point B and invoke a service/
command/other request. That’s it.
Let’s take a deeper look at each of these components and understand how they
work.
THE ACTION
Actions are the first component we’ll examine. Actions are simple,
straightforward objects that do very little practical work. Most of the work is
done elsewhere, delegated by the Action but not managed or processed by the
Action.
The Action is most similar to the Controller, but it has a few distinct differences.
First and foremost, while a controller may have several related methods (get,
update, delete, etc.), Actions have single methods that are specific to a
particular request. This is because an Action is a discrete block of code for a
discrete purpose; it doesn’t need multiple related actions. Consider the Action
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 97
— ACTION-DOMAIN-RESPONDER
In addition, while Controllers interact with both the model and the presentation
layer (template), Actions interact only with the model, also known as the
Domain. The extent of the Action’s interaction with the Responder is by passing
the Responder an object for it to use in preparing the response. The Action
contains no template code, and no rendering logic.
Actions are primarily responsible for shuffling request data into the domain, and
shuffling response data out. This stands in stark contrast with many Controllers,
which contain much of the business logic that an application needs to execute.
The Action-Domain-Responder model believes strongly that an Action should be
as small and compact as possible. Consider:
<?php
class ScenerySign
{
/**
* @var ServerRequestInterface
*/
protected $request;
/**
* @var ValidateAndSignRequest
*/
protected $service;
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 98
— ACTION-DOMAIN-RESPONDER
) {
$this->request = $request;
$this->service =$service;
}
These 29 lines are a full action, representing the full behavior of the
ScenerySign action. The rules are simple enough: the object accepts a
ServerRequestInterface object compatible with PSR-7, as well as a
service called ValidateAndSignRequest. It pulls the body out of the
HTTP request, passes it into the service, and returns the payload. Simple, easy,
straightforward.
It’s worth noting that the Action stands as the last layer in which knowledge of
the request is permissible. Data passed into the Domain should be agnostic to
where the data came from; it shouldn’t care whether the data was from an HTTP
request or a CLI request or some other kind of request. Thus it’s important to
include any logic for extracting data from the request inside the Action.
M A S T E R I N G O B J E C T- O R I E N T E D P H P — 99
— ACTION-DOMAIN-RESPONDER
THE DOMAIN
Clasically, the Domain is also known as “the Model”. However, a Domain is
much more than a model. We are not simply modeling data, but business
relationships and behavior. Domains are complex elements that are unique for
each and every application you will ultimately work with.
This isn’t a domain book, so we won’t dive too deeply into an understanding
of what a domain is or how to create one. Creating a domain could fill an entire
series of books (and it does, in books like Domain Driven Design and others).
However, you should feel free to be creative in your domain design, creating a
domain that fits your business needs and solves your business problems.
M A S T E R I N G O B J E C T - O R I E N T E D P H P — 100
— ACTION-DOMAIN-RESPONDER
into a standard MVC application or any other kind of application and “just
work.” This is the ultimate goal: to have agnostic domains capable of doing work
without knowledge of the underlying architecture.
THE RESPONDER
Of the three components of the Action-Domain-Responder paradigm, perhaps
none is as challenging to grasp as the Responder. That’s because when people
think of the Responder they immediately assume “the thing that presents
HTML.” They could not be more wrong.
<?php
M A S T E R I N G O B J E C T - O R I E N T E D P H P — 101
— ACTION-DOMAIN-RESPONDER
return [
'text/html' => 'generateHtml',
];
}
This is a Responder that generates HTML. Notice how the Responder works.
The Responder is responsible for telling a templating engine which template to
use, and calls the templating engine to invoke the code. It also returns an HTML
response. But it doesn’t actually generate the HTML. The HTML is generated by
the templating engine!
<?php
M A S T E R I N G O B J E C T - O R I E N T E D P H P — 102
— ACTION-DOMAIN-RESPONDER
There are some important ways in which Responders differ from Controllers. For
example, Responders have a 1:1 relationship with specific actions. For a given
action there will be a given responder. Only the Responder knows anything
about the response logic; the Action is ignorant of these details and shouldn’t be
aware of them at all.
PAYLOAD OBJECTS
When passing messages between the Domain and the Responder, it’s important
M A S T E R I N G O B J E C T - O R I E N T E D P H P — 103
— ACTION-DOMAIN-RESPONDER
Payload objects are what they sound like: objects that represent state, and
transmit the payload from the Domain into the Responder. Aura framework has
a Payload object interface, which I highly recommend.
Within that Payload interface, Paul has included a spot to store input variables,
output values, messages, extras (miscellaneous stuff), and a status code. The
status code is useful for presenting the Payload object for inspection, because
the status code can be used to toggle the response you provide.
Payload objects are incredibly useful, far more useful than simply passing arrays
around, and more predictable.
CONCLUSION
Separating out the concerns using Action-Domain-Responder helps us to
separate out the responsibilities of our components as well. Now we have a
well-factored system of actions, responders, and the domain at large.
Going back to our list, where do things belong now? The request belongs in the
Action. Our business logic belongs firmly in the Domain, since it’s now separated
from the Action. The templating system goes squarely into the Responder. And
authentication?
M A S T E R I N G O B J E C T - O R I E N T E D P H P — 104
— ACTION-DOMAIN-RESPONDER
M A S T E R I N G O B J E C T - O R I E N T E D P H P — 105
About the Author
Around 2004, Sarah realized that she could automate tasks by writing a small program in
PHP. Her first application, an online roleplaying game, inspired her to continue learning
how to use PHP. After graduating in 2007 from University of the Pacific in California,
Sarah became a full-time software developer. But Sarah quickly discovered that most
businesses didn’t really struggle with software, they struggled with people and process.
Sarah learned that solving business problems required thinking differently about how
to approach them. Introducing software is expensive, and Sarah realized that solving
these problems needed solutions beyond the common ones proposed by most people.
By taking an in-depth outsider look at a business, Sarah found that she could tackle the
actual people and process problems facing businesses.
Sarah started Carved Path Consulting in 2021 to consolidate and focus her work by
consulting for businesses that want to solve their process and people problems. Sarah’s
expertise, combined with twenty years of experience in the technology world, helps
Sarah solve these problems and build businesses that run themselves, giving their
owners more free time and even the opportunity to take vacations.
Find Bugs?
Every effort was made to make this book as accurate as possible. But it’s possible that
bugs, typos and other errata may still exist. If you find something, please file a bug!
https://github.com/tailwindsllc/masteringobjectorientedphp