If you ask 10 PHP developers what clean code is, you’ll get 9 answers, and one very angry person who spends a half hour explaining to you that WordPress plugins have to be dirty because that’s just how it works, and if you were a better programmer you’d understand.
Once you’ve backed away from that tenth guy, and filed the requisite restraining orders, you’ll finally be able to see that only one or two of the other 9 developers even has an opinion about clean code.
I’ve spent the last few years of my development journey trying to figure out what clean code means to me. That journey has led me to topics like Test Driven Development, Object Calisthenics, Value Objects, and more.
My biggest gripe, and this is definitely just my own personal issue, is that there is very little out there that focuses on clean PHP code.
A bit about PHP
PHP has a checkered past when it comes to clean code, have you heard? If you’ve spent any time online in the developer circles, you’d have had a harder time not hearing negative things about PHP.
I won’t go into all of the things that people say, because frankly I think they’re mostly wrong (now), but much like high school, PHP has a bad reputation. Unlike high school, PHP can’t just wear a leather jacket and pretend not to care.
PHP, for all the faults it has seen, was little more than a toddler for years, and still successfully developed the majority of the internet.
These days, PHP has grown into a respectable young adult language, and with the release of PHP 8.0, is even showing signs of maturity.
Next up: Mature developers
Now, let’s start this section off by saying there are a lot of mature developers who write clean code in PHP. I’m not trying to say there aren’t.
The problem is different, and it is twofold:
- There are far more immature developers writing non-clean PHP code; and
- The distance between clean PHP and non-clean PHP is greater than most other languages.
Both of these problems have a root cause, however, and as with most things in PHP that root cause is known, and a core to the language.
PHP, as a language, is very forgiving.
It doesn’t force you to do much of anything that (many) other C-based languages do by default — looking at you, type declarations. Because of that, many PHP developers fall into PHP accidentally, and find their code working quickly.
That’s great for adoption, but not so great for proliferation of clean code.
You have to go out of your way to write clean PHP
That’s not to say it’s hard to write clean PHP, just that it requires you to take steps that you aren’t forced to take by the language.
Of course, that’s true of all clean code, but my point is that with PHP, there are more required steps to take.
For that reason, I’m going to go over just a few steps that you can take that will immediately help you improve the cleanliness of your PHP. Some of these are suggestions provided by Uncle Bob’s (Robert Martin’s) Clean Code book [affiliate link], others are ones that I’ve found to help me write cleaner code.
This isn’t an exhaustive list, and I’m only touching briefly on each here, because each suggestion could quite easily be it’s own article. But let’s begin.
We’ll start with the one that is required for all others to exist:
Global scope is for calling your main method, and that’s it
Or written in other words: You should be using Classes — or at the very least Functions — and not writing code as a single file from top to bottom.
There are very few programs that can be run long term from a single script that doesn’t use any custom functions. As your application grows, you should find yourself moving blocks to their own methods, and methods to their own Classes.
This is a necessary aspect of everything that comes next, so if you’re not already doing this step, stop reading right now, and go practice. If you are already doing this step, let’s move on.
Use type declarations
Type declarations aren’t a required part of PHP, which we discussed above, but they make your code smell better. When you’re using type declarations, you don’t have to guess what a variable contains a year later when you’re being asked to completely rewrite that part of your script.
Prepend Private/Protected Class Members with Underscores
This is a convention I use, and would love to stop using. The problem is, until we get industry-wide agreement on my next point, I find this one to be necessary.
It is important to know, at a glance, what scope the variables you are working with are. By adding a simple underscore to the beginning of parameter names, you can tell which are private and protected, and which are public. This can be safely ignored if you follow the next suggestion:
Zero Public Class Parameters
Why do we create classes? To add a layer of separation between that class’ code, and the rest of the application. Public class parameters break this barrier. They are, for all intents and purposes, infection vectors that others can use to mess with your class.
Classes should have (limited) public APIs. These public APIs should be restricted to class methods, and should not include getters or setters. Yea, I said it.
If you want clean code, you need to limit the scope of your class. Getters and setters don’t do that. Getters can be forgiven in some instances, but setters should be avoided at all cost.
Use Value Objects for Immutability
Value Objects give a name to a concept. They encapsulate the logic around what it means to be whatever that concept is, and they allow you to separate the concerns of handling that logic from the usage of that data.
I’ve worked on quite a few browser based games, and one thing they all have in common is money of some form. Traditionally, that money is passed around as integers in various fields. But by using a Value Object, you’re able to self-document that portion of your code while cleaning it all up.
Let’s take a look at an example:
This is a very small sample, and one with quite a few potential bugs.
addMoney is quite clearly a setter with a fancy name.
Second, let’s take a minute to think about
$_wallet shall we? Being very specific about intentions here, what is wallet? Given it’s usage, we can assume it to be an integer, right?
But, are wallets integers? I have on in my pocket, and it looks like a block of leather to me. But I guess I could ask it if it’s really a natural number.
You (hopefully) get my point. Wallets are their own thing, and contain their own logic. So we should treat them as such.
Let’s look at an example that improves on this idea:
First, we create the Wallet value object. This is just a quick class I put together, and it likely could be improved over time as it was put into use. But for this example, it does everything we need.
Note: There’s a getter in this. I’m a hypocrite, right? Well, no. In this case, I would have preferred to use the __toString() magic method, but PHP doesn’t have an equivalent __toInt() magic method, and instead of having (int)(string)$wallet throughout the code, we can use this instead.
Next, let’s talk about how we use this value object.
I’ve skipped the inclusion of the Wallet class, because if you’re reading this I assume you know how to connect different files in PHP.
Anyway, now that we have our two classes, we create an empty wallet when we create the player, and then we’re able to add money to that wallet by calling
addMoney on the player class. This keeps everything contained nicely.
Of course, this isn’t a perfect example. There are much better ways of handling wallets, and you likely want your wallet to be a model and not a value object, but in our example that’s what we’ve got to work with.
The thing is, until you’ve actually used value objects, you won’t really understand the value that they provide for both readability and usability.
I’ve spent way too much time on value objects, so let’s move on.
Maximum of 1 Indentation Level using Early Return
Indentation is as evil as setters. Indentation makes code difficult to follow, and makes you more prone to forgetting to close a brace. And if I hear you tell me that doesn’t matter because VS Code will close the brace for you, so help me I’m going to be forced to break your keyboard.
VS Code isn’t the developer.
Do. Your. Job.
Let’s look at an example.
We’ve all seen this code, haven’t we? We want to register a user, so we pass their details to the function and do this.
How many levels of indentation do we have there? Well, using zero indexing, we start at an indentation level of 0 in the body of the function. From there we can count out 3 if-statements, which gives us 3 levels of indentation.
We want to get that down to no more than 1.
It’s super simple. We just need to invert the logic that we have here.
This is the exact same code as above, except we’ve inverted the logic and instantly made it more readable.
Logic inversion and early returning could be it’s own article. It is core to the tactics for tackling too many indentation levels, getting rid of the else keyword, and so many other ways of making your code cleaner.
Don’t use else… or else
In a perfect world, we could get rid of conditionals entirely. I’m not quite that far down the road of clean code, but I could be convinced. For now, however, I will die upon this hill: There is never a reason to use the else keyword.
I have never found an else block that I couldn’t move and get rid of the else keyword. The else keyword is generally a code smell for me that tells me that you’re doing more than one thing in that method or section of code.
What is else, anyway? If we look at the concept above about logic inversion, we can see that an else is the equivalent of all previous if statements returning false.
What that meant for the example above was that each else block became the main block of the if, and the main logic of the method became the defacto else block.
But if the code that you want to run is inside of an else block, and the only way to get there is if everything in your method runs correctly, the else block isn’t necessary.
For example, these two functions perform identically:
Notice, though, how the second function is very clear about the flow? There’s no need to use the else, because if the first if is true, execution of that function stops.
And if all of those if statements are false, the defacto else will run, so there is no need for the final else either.
Is that everything?
No, that’s not everything. PHP is loaded with dirty code, including in the language itself — though the same can be said for all languages — but this is a start.
If you start here, your code will be easier to read, and easier to understand as you refactor it. I can’t teach you to be a clean coder in a single article, but I can start you on your way towards becoming one.
If you’re really interested in learning more, I encourage you to pick up Robert Martin’s Clean Code [affiliate link], and read it front to back. His examples are written in Java, but Java is similar enough to PHP that you should be able to make the mental translation.
And if not, you should really learn a little Java anyway.
If this article was interesting to you, you’re going to love the stream of tech-consciousness that is my Twitter feed. Head over there and give me a follow. You won’t be disappointed.
Links marked above as affiliate links are links that provide me a small commission for simply linking to products that I would already have been suggesting. Prices don’t increase for you, but by using the link you show your support for the content I provide. I thank you for that support!