In this article by Junade Ali, author of the book Mastering PHP Design Patterns, we will be revisiting object-oriented programming. Back in 2010 MailChimp published a post on their blog, it was entitled Ewww, You Use PHP? In this blog post they described the horror when they explained their choice of PHP to developers who consider the phrase good PHP programmer an oxymoron. In their rebuttal they argued that their PHP wasn't your grandfathers PHP and they use a sophisticated framework. I tend to judge the quality of PHP on the basis of, not only how it functions, but how secure it is and how it is architected. This book focuses on ideas of how you should architect your code. The design of software allows for developers to ease the extension of the code beyond its original purpose, in a bug free and elegant fashion.
(For more resources related to this topic, see here.)
As Martin Fowler put it:
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
This isn't just limited to code style, but how developers architect and structure their code. I've encountered many developers with their noses constantly stuck in documentation, copying and pasting bits of code until it works; hacking snippets together until it works. Moreover, I far too often see the software development process rapidly deteriorate as developers ever more tightly couple their classes with functions of ever increasing length.
Software engineers mustn't just code software; they must know how to design it. Indeed often a good software engineer, when interviewing other software engineers will ask questions surrounding the design of the code itself. It is trivial to get a piece of code that will execute, and it is also benign to question a developer as to whether strtolower or str2lower is the correct name of a function (for the record, it's strtolower). Knowing the difference between a class and an object doesn't make you a competent developer; a better interview question would, for example, be how one could apply subtype polymorphism to a real software development challenge. Failure to assess software design skills dumbs down an interview and results in there being no way to differentiate between those who are good at it, and those who aren't. These advanced topics will be discussed throughout this book, by learning these tactics you will better understand what the right questions to ask are when discussing software architecture.
Moxie Marlinspike once tweeted:
As a software developer, I envy writers, musicians, and filmmakers. Unlike software, when they create something it is really done, forever.
When developing software we mustn't forget we are authors, not just of instructions for a machine, but we are also authoring something that we later expect others to extend upon. Therefore, our code mustn't just be targeted at machines, but humans also. Code isn't just poetry for a machine, it should be poetry for humans also.
This is, of course, better said than done. In PHP this may be found especially difficult given the freedom PHP offers developers on how they may architect and structure their code. By the very nature of freedom, it may be both used and abused, so it is true with the freedom offered in PHP.
PHP offers freedom to developers to decide how to architect this code. By the very nature of freedom it can be both used and abused, so it is true with the freedom offered in PHP. Therefore, it is increasingly important that developers understand proper software design practices to ensure their code maintains long term maintainability. Indeed, another key skill lies in refactoringcode, improving design of existing code to make it easier to extend in the longer term
Technical debt, the eventual consequence of poor system design, is something that I've found comes with the career of a PHP developer. This has been true for me whether it has been dealing with systems that provide advanced functionality or simple websites. It usually arises because a developer elects to implement bad design for a variety of reasons; this is when adding functionality to an existing codebase or taking poor design decisions during the initial construction of software. Refactoring can help us address these issues.
SensioLabs (the creators of the Symfonyframework) have a tool called Insight that allows developers to calculate the technical debt in their own code. In 2011 they did an evaluation of technical debt in various projects using this tool; rather unsurprisingly they found that WordPress 4.1 topped the chart of all platforms they evaluated with them claiming it would take 20.1 years to resolve the technical debt that the project contains.
Those familiar with the WordPress core may not be surprised by this, but this issue of course is not only associated to WordPress. In my career of working with PHP, from working with security critical cryptography systems to working with systems that work with mission critical embedded systems, dealing with technical debt comes with the job. Dealing with technical debt is not something to be ashamed of for a PHP Developer, indeed some may consider it courageous. Dealing with technical debt is no easy task, especially in the face of an ever more demanding user base, client, or project manager; constantly demanding more functionality without being familiar with the technical debt the project has associated to it.
I recently emailed the PHP Internals group as to whether they should consider deprecating the error suppression operator @. When any PHP function is prepended by an @ symbol, the function will suppress an error returned by it. This can be brutal; especially where that function renders a fatal error that stops the execution of the script, making debugging a tough task. If the error is suppressed, the script may fail to execute without providing developers a reason as to why this is.
Despite the fact that no one objected to the fact that there were better ways of handling errors (try/catch, proper validation) than abusing the error suppression operator and that deprecation should be an eventual aim of PHP, it is the case that some functions return needless warnings even though they already have a success/failure value. This means that due to technical debt in the PHP core itself, this operator cannot be deprecated until a lot of other prerequisite work is done. In the meantime, it is down to developers to decide the best methodologies of handling errors. Until the inherent problem of unnecessary error reporting is addressed, this operator cannot be deprecated. Therefore, it is down to developers to be educated as to the proper methodologies that should be used to address error handling and not to constantly resort to using an @ symbol.
Fundamentally, technical debt slows down development of a project and often leads to code being deployed that is broken as developers try and work on a fragile project.
When starting a new project, never be afraid to discus architecture as architecture meetings are vital to developer collaboration; as one scrum master I've worked with said in the face of criticism that "meetings are a great alternative to work", he said "meetings are work…how much work would you be doing without meetings?".
When it comes to coding style, I would like to introduce you to the PSR standards created by the PHP Framework Interop Group. Namely, the two standards that apply to coding standards are PSR-1 (Basic Coding Style) and PSR-2 (Coding Style Guide). In addition to this there are PSR standards that cover additional areas, for example, as of today; the PSR-4 standard is the most up-to-date autoloading standard published by the group. You can find out more about the standards at http://www.php-fig.org/.
Coding style being used to enforce consistency throughout a codebase is something I strongly believe in, it does make a difference to your code readability throughout a project. It is especially important when you are starting a project (chances are you may be reading this book to find out how to do that right) as your coding style determines the style the developers following you in working on this project will adopt. Using a global standard such as PSR-1 or PSR-2 means that developers can easily switch between projects without having to reconfigure their code style in their IDE. Good code style can make formatting errors easier to spot. Needless to say that coding styles will develop as time progresses, to date I elect to work with the PSR standards.
I am a strong believer in the phrase: Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. It isn't known who wrote this phrase originally; but it's widely thought that it could have been John Woods or potentially Martin Golding.
I would strongly recommend familiarizingyourself with these standards before proceeding in this book.
Object-oriented programming is more than just classes and objects, it's a whole programming paradigm based around objects(data structures) that contain data fields and methods. It is essential to understand this; using classes to organize a bunch of unrelated methods together is not object orientation.
Assuming you're aware of classes (and how to instantiate them), allow me to remind you of a few different bits and pieces.
Polymorphism is a fairly long word for a fairly simple concept. Essentially, polymorphism means the same interfaceis used with a different underlying code. So multiple classes could have a draw function, each accepting the same arguments, but at an underlying level the code is implemented differently.
In this article, I would also like to talk about Subtype Polymorphism in particular (also known as Subtyping or Inclusion Polymorphism).
Let's say we have animals as our supertype;our subtypes may well be cats, dogs, and sheep.
In PHP, interfaces allow you to define a set of functionality that a class that implements it must contain, as of PHP 7 you can also use scalar type hints to define the return types we expect.
So for example, suppose we defined the following interface:
interface Animal
{
public function eat(string $food) : bool;
public function talk(bool $shout) : string;
}
We could then implement this interface in our own class, as follows:
class Cat implements Animal {
}
If we were to run this code without defining the classes we would get an error message as follows:
Class Cat contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Animal::eat, Animal::talk)
Essentially, we are required to implement the methods we defined in our interface, so now let's go ahead and create a class that implements these methods:
class Cat implements Animal
{
public function eat(string $food): bool
{
if ($food === "tuna") {
return true;
} else {
return false;
}
}
public function talk(bool $shout): string
{
if ($shout === true) {
return "MEOW!";
} else {
return "Meow.";
}
}
}
Now that we've implemented these methods we can then just instantiate the class we are after and use the functions contained in it:
$felix = new Cat();
echo $felix->talk(false);
So where does polymorphism come into this? Suppose we had another class for a dog:
class Dog implements Animal
{
public function eat(string $food): bool
{
if (($food === "dog food") || ($food === "meat")) {
return true;
} else {
return false;
}
}
public function talk(bool $shout): string
{
if ($shout === true) {
return "WOOF!";
} else {
return "Woof woof.";
}
}
}
Now let's suppose we have multiple different types of animals in a pets array:
$pets = array(
'felix' => new Cat(),
'oscar' => new Dog(),
'snowflake' => new Cat()
);
We can now actually go ahead and loop through all these pets individually in order to run the talk function.We don't care about the type of pet because the talkmethod that is implemented in every class we getis by virtue of us having extended the Animals interface.
So let's suppose we wanted to have all our animals run the talk method, we could just use the following code:
foreach ($pets as $pet) {
echo $pet->talk(false);
}
No need for unnecessary switch/case blocks in order to wrap around our classes, we just use software design to make things easier for us in the long-term.
Abstract classes work in a similar way, except for the fact that abstract classes can contain functionality where interfaces cannot.
It is important to note that any class that defines one or more abstract classes must also be defined as abstract. You cannot have a normal class defining abstract methods, but you can have normal methods in abstract classes. Let's start off by refactoring our interface to be an abstract class:
abstract class Animal
{
abstract public function eat(string $food) : bool;
abstract public function talk(bool $shout) : string;
public function walk(int $speed): bool {
if ($speed > 0) {
return true;
} else {
return false;
}
}
}
You might have noticed that I have also added a walk method as an ordinary, non-abstract method; this is a standard method that can be used or extended by any classes that inherit the parent abstract class. They already have implementation.
Note that it is impossible to instantiate an abstract class (much like it's not possible to instantiate an interface). Instead we must extend it.
So, in our Cat class let's substitute:
class Cat implements Animal
With the following code:
class Cat extends Animal
That's all we need to refactor in order to get classes to extend the Animal abstract class. We must implement the abstract functions in the classes as we outlined for the interfaces, plus we can use the ordinary functions without needing to implement them:
$whiskers = new Cat();
$whiskers->walk(1);
As of PHP 5.4 it has also become possible to instantiate a class and access a property of it in one system. PHP.net advertised it as: Class member access on instantiation has been added, e.g. (new Foo)->bar(). You can also do it with individual properties, for example,(new Cat)->legs. In our example, we can use it as follows:
(new IcyAprilChapterOneCat())->walk(1);
Just to recap a few other points about how PHP implemented OOP, the final keyword before a class declaration or indeed a function declaration means that you cannot override such classes or functions after they've been defined.
So, if we were to try extending a class we have named as final:
final class Animal
{
public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
}
This results in the following output:
Fatal error: Class Cat may not inherit from final class (Animal)
Similarly, if we were to do the same except at a function level:
class Animal
{
final public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
public function walk () {
return "walking with tail wagging...";
}
}
This results in the following output:
Fatal error: Cannot override final method Animal::walk()
Traits were introduced into PHP as a mechanism for introducing Horizontal Reuse. PHP conventionally acts as a single inheritance language, namely because of the fact that you can't inherit more than one class into a script.
Traditional multiple inheritance is a controversial process that is often looked down upon by software engineers.
Let me give you an example of using Traits first hand; let's define an abstract Animal class which we want to extend into another class:
class Animal
{
public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
public function walk () {
return "walking with tail wagging...";
}
}
So now let's suppose we have a function to name our class, but we don't want it to apply to all our classes that extend the Animal class, we want it to apply to certain classes irrespective of whether they inherit the properties of the abstract Animal class or not.
So we've defined our functions like so:
function setFirstName(string $name): bool
{
$this->firstName = $name;
return true;
}
function setLastName(string $name): bool
{
$this->lastName = $name;
return true;
}
The problem now is that there is no place we can put them without using Horizontal Reuse, apart from copying and pasting different bits of code or resorting to using conditional inheritance. This is where Traits come to the rescue; let's start off by wrapping these methods in a Trait called Name:
trait Name
{
function setFirstName(string $name): bool
{
$this->firstName = $name;
return true;
}
function setLastName(string $name): bool
{
$this->lastName = $name;
return true;
}
}
So now that we've defined our Trait, we can just tell PHP to use it in our Cat class:
class Cat extends Animal
{
use Name;
public function walk()
{
return "walking with tail wagging...";
}
}
Notice the use of theName statement? That's where the magic happens. Now you can call the functions in that Trait without any problems:
$whiskers = new Cat();
$whiskers->setFirstName('Paul');
echo $whiskers->firstName;
All put together, the new code block looks as follows:
trait Name
{
function setFirstName(string $name): bool
{
$this->firstName = $name;
return true;
}
function setLastName(string $name): bool
{
$this->lastName = $name;
return true;
}
}
class Animal
{
public function walk()
{
return "walking...";
}
}
class Cat extends Animal
{
use Name;
public function walk()
{
return "walking with tail wagging...";
}
}
$whiskers = new Cat();
$whiskers->setFirstName('Paul');
echo $whiskers->firstName;
Let me take this opportunity to introduce you to a PHP7 concept known as scalar type hinting; it allows you to define the return types (yes, I know this isn't strictly under the scope of OOP; deal with it).
Let's define a function, as follows:
function addNumbers (int $a, int $b): int
{
return $a + $b;
}
Let's take a look at this function; firstly you will notice that before each of the arguments we define the type of variable we want to receive, in this case,int or integer. Next up you'll notice there's a bit of code after the function definition : int, which defines our return type so our function can only receive an integer.
If you don't provide the right type of variable as a function argument or don't return the right type of variable from the function; you will get a TypeError exception. In strict mode, PHP will also throw a TypeError exception in the event that strict mode is enabled and you also provide the incorrect number of arguments.
It is also possible in PHP to define strict_types; let me explain why you might want to do this. Without strict_types, PHP will attempt to automatically convert a variable to the defined type in very limited circumstances. For example, if you pass a string containing solely numbers it will be converted to an integer, a string that's non-numeric, however, will result in a TypeError exception. Once you enable strict_typesthis all changes, you can no longer have this automatic casting behavior.
Taking our previous example, without strict_types, you could do the following:
echo addNumbers(5, "5.0");
Trying it again after enablingstrict_types, you will find that PHP throws a TypeError exception.
This configuration only applies on an individual file basis, putting it before you include other files will not result in this configuration being inherited to those files. There are multiple benefits of why PHP chose to go down this route; they are listed very clearly in Version: 0.5.3 of the RFC that implemented scalar type hints called PHP RFC: Scalar Type Declarations. You can read about it by going to http://www.wiki.php.net (the wiki, not the main PHP website) and searching for scalar_type_hints_v5.
In order to enable it, make sure you put this as the very first statement in your PHP script:
declare(strict_types=1);
This will not work unless you define strict_typesas the very first statement in a PHP script; no other usages of this definition are permitted. Indeed if you try to define it later on, your script PHP will throw a fatal error.
Of course, in the interests of the rage induced PHP core fanatic reading this book in its coffee stained form, I should mention that there are other valid types that can be used in type hinting. For example, PHP 5.1.0 introduced this with arrays and PHP 5.0.0 introduced the ability for a developer to do this with their own classes.
Let me give you a quick example of how this would work in practice, suppose we had an Address class:
class Address
{
public $firstLine;
public $postcode;
public $country;
public function __construct(string $firstLine, string $postcode, string $country)
{
$this->firstLine = $firstLine;
$this->postcode = $postcode;
$this->country = $country;
}
}
We can then type the hint of the Address class that we inject into a Customer class:
class Customer
{
public $name;
public $address;
public function __construct($name, Address $address)
{
$this->name = $name;
$this->address = $address;
}
}
And just to show how it all can come together:
$address = new Address('10 Downing Street', 'SW1A2AA', 'UK');
$customer = new Customer('Davey Cameron', $address);
var_dump($customer);
If you define a class which contains private or protected variables, you will notice an odd behavior if you were to var_dumpthe object of that class. You will notice that when you wrap the object in a var_dumpit reveals all variables; be they protected, private, or public.
PHP treats var_dump as an internal debugging function, meaning all data becomes visible.
Fortunately, there is a workaround for this. PHP 5.6 introduced the __debugInfo magic method. Functions in classes preceded by a double underscore represent magic methods and have special functionality associated to them. Every time you try to var_dump an object that has the __debugInfo magic method set, the var_dump will be overridden with the result of that function call instead.
Let me show you how this works in practice, let's start by defining a class:
class Bear {
private $hasPaws = true;
}
Let's instantiate this class:
$richard = new Bear();
Now if we were to try and access the private variable that ishasPaws, we would get a fatal error; so this call:
echo $richard->hasPaws;
Would result in the following fatal error being thrown:
Fatal error: Cannot access private property Bear::$hasPaws
That is the expected output, we don't want a private property visible outside its object. That being said, if we wrap the object with a var_dump as follows:
var_dump($richard);
We would then get the following output:
object(Bear)#1 (1) {
["hasPaws":"Bear":private]=>
bool(true)
}
As you can see, our private property is marked as private, but nevertheless it is visible. So how would we go about preventing this?
So, let's redefine our class as follows:
class Bear {
private $hasPaws = true;
public function __debugInfo () {
return call_user_func('get_object_vars', $this);
}
}
Now, after we instantiate our class and var_dump the resulting object, we get the following output:
object(Bear)#1 (0) {
}
The script all put together looks like this now, you will notice I've added an extra public property called growls, which I have set to true:
<?php
class Bear {
private $hasPaws = true;
public $growls = true;
public function __debugInfo () {
return call_user_func('get_object_vars', $this);
}
}
$richard = new Bear();
var_dump($richard);
If we were to var_dump this script (with both public and private property to play with), we would get the following output:
object(Bear)#1 (1) {
["growls"]=>
bool(true)
}
As you can see, only the public property is visible. So what is the moral of the story from this little experiment? Firstly, that var_dumps exposesprivate and protected properties inside objects, and secondly, that this behavior can be overridden.
In this article, we revised some PHP principles, including OOP principles. We also revised some PHP syntax basics.
Further resources on this subject: