Writing Clean PHP Code

Writing clean code will help you (and others who read your code) to better understand it in the future. Imagine yourself coming back to a piece of code you've written a while ago and be like "What does that even do?"
By following a few simple principles and avoiding some common pitfalls, you'll make life easier for yourself and others.

Proper naming without abbreviations

Try to convey meaning by naming variables, functions/methods and classes properly and explicit. Especially new developers seem to like weird 3 character abbreviations - don't do it, you'll only end up confusing yourself at some point. Commonly accepted abbreviations are usually fine though.
Acronyms can be a bit of a pain and it's quite an opinionated topic. Personally I prefer to stick with camelCase for everything and StudlyCase for class names e.g. HTTP ends up as Http. Well - the exception is array keys, here I prefer snake_case. Here are some examples to give you a general idea.

// Be explicit with simple types
$totalOrderAmount       // vs $amount or even $amt
$orderCurrency          // vs $currency or $cur
$deliveryTimeInDays     // vs $delivery or $time or $dt
$productNumber          // vs $prodNo or $pno or $num

// Preserve singular nouns for objects
$customer               // Holds an object of class Customer
$product                // Holds an object of class Product

// and plural ones for arrays and collections
$customers              // Array of Customer objects
$products               // Array of Product objects

// Let the method name explain what it does
$customer->getFirstName()           // Return first name as string
$product->getStockQuantity()        // Return available quantity
$product->getStock()->getQuantity() // or like so if stock is an object
$product->stock()->getQuantity()    // or even drop the get for objects
// or if you're not a fan of the getter/setter anti pattern
$product->stock->quantity

// Method names can be quite long ... and thats ok
$orderRepository->findAllByCurrency($currency)
$userRegistration->registerWithEmailAndPassword($email, $password)

// Naming acronyms can be weird
$xmlHttpRequest             // vs $XMLHTTPRequest
class SqlRepository         // vs SQLRepository

// Underscores aka snake_case for array keys
$order['order_reference']   // vs ['orderReference'] 
$user['email']              // vs ['Email'] etc

Whatever conventions you decide to follow - make sure to stick with it throughout a project. Consistency is king here.

Indention and spacing

Properly indented code allows you to easily recognize blocks of code that belong together. Most modern code editors offer auto formatting and indention nowadays, so this should be easy to do.

// This is really hard to read

while($line=fgets($fh)){
$ltrimLine=ltrim($line);
$trimLine=trim($line);
$level=strlen($line)-strlen($ltrimLine);
$colonPos=intval(strpos($trimLine,':'));
if(1!==preg_match($pattern,$trimLine,$m))continue;
if(!empty($m['key_value'])||!empty($m['key_only'])){
$colonKey=substr($trimLine,0,$colonPos+1);
if($level===0){
$path=$colonKey;
}elseif($level>$lastLevel){
$path.=$colonKey;}else{
$levelDiff=abs($lastLevel-$level)/2+1;
$path=preg_replace('~(([^:]+:){'.$levelDiff.'})$~',$colonKey,$path);
}$lastLevel=$level;}}

// This is much more readable already

while ($line = fgets($fh)) {

    $ltrimLine = ltrim($line);
    $trimLine = trim($line);
    $level = strlen($line) - strlen($ltrimLine);
    $colonPos = intval(strpos($trimLine, ':'));

    if (1 !== preg_match($pattern, $trimLine, $m)) continue;

    if (!empty($m['key_value']) || !empty($m['key_only'])) {

        $colonKey = substr($trimLine, 0, $colonPos + 1);

        if ($level === 0) {
            $path = $colonKey;
        } elseif ($level > $lastLevel) {
            $path .= $colonKey;
        } else {
            $levelDiff = abs($lastLevel - $level) / 2 + 1;
            $path = preg_replace('~(([^:]+:){' . $levelDiff . '})$~', $colonKey, $path);
        }

        $lastLevel = $level;
    }
}

If you're wondering what the piece of code above does ... it's an excerpt of class that searches yaml files and displays where in the yaml tree a match is located.

Object Spaghetti

There are two things I like to refer to as Object Spaghetti.

  • Throwing a huge chunk of spaghetti code into a single method
  • Creating dozens of classes and interfaces for no reason, also referred to as over-engineering.

There's a rather famous and brilliant quote by Tony Hoare https://en.wikiquote.org/wiki/C._A._R._Hoare

There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies.

Once you have become a somewhat experienced developer, it's easy to fall into a habit where you are no longer focused on solving problems, but rather on trying to satisfy code quality metrics and following fancy design patterns etc.
Such elegance, such fancy, so much wow!

$firstString = 'Hello';
$secondString = 'World';

// Jokes aside - I've seen code similar to this
$stringManipulationFacade = new StringManipulationFacade(
    ...
    new StringConcatenator(
        new StringConcatenationValidator()
    ),
    ...
);

$concatenatedString = $stringManipulationFacade
    ->concatenateWithWhiteSpace($firstString, $secondString);

// when all we want to do is this
$concatenatedString = $firstString . ' ' . $secondString;
// or this
$concatenatedString = "$firstString $secondString";
// or this
$concatenatedString = sprintf("%s %s", $firstString, $secondString);

Sometimes it's a good idea to take step back and ask yourself:

  • is it more readable?
  • will a junior easily understand it?
  • does it improve stability?
  • does it improve security?
  • does it improve performance?
  • is it easy to debug?
  • is it easily maintainable by others?

... to be continued ...