Skip to content

PHP Tutorial PHP Performance and Best Practices

Maintainable PHP Code

Lesson 346 of 369 4 min read By Deepak Paswan Updated Oct 5, 2026

Most of a project’s life is maintenance: fixing bugs, adding features, upgrading. Code that is easy to change saves more time than code that is clever or fast.

Principles that matter

Single responsibility. A class or function has one reason to change. A class that validates, saves, emails and renders will change for four different reasons.

Don’t repeat yourself. One rule, one place (see Avoiding Duplicate Code). But do not merge things that only look alike.

Depend on abstractions. Ask for an interface, not a concrete class (see Dependency Injection). Swapping an implementation then does not ripple through the code.

Keep it simple. The simplest thing that works, and no speculative features.

Make illegal states impossible. Use types, enums and value objects so invalid data cannot exist.

Code smells

A smell is a sign that code may need work.

SmellWhat it looks likeTypical cure
Long functionScrolls over a screenExtract smaller functions
Long parameter list6+ parametersGroup them into an object
Deep nestingif inside if inside ifEarly returns, extract
Duplicate codeCopy-pasted blocksExtract a function or class
God classOne class that does everythingSplit by responsibility
Magic valuesif ($status == 3)Constants or enums
Boolean flagssave($x, true, false)Named arguments, separate methods
Global stateglobal $db, static propertiesPass dependencies in
Primitive obsessionStrings and arrays everywhereValue objects (Email, Money)

Example: replace a flag with clear methods

index.php
PHP
<?php
// Unclear: what do the booleans mean?
function renderBad(string $text, bool $escape, bool $newlines): string
{
    $text = $escape ? htmlspecialchars($text) : $text;

    return $newlines ? nl2br($text) : $text;
}

// Clear
final class Html
{
    public static function escaped(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, "UTF-8");
    }

    public static function paragraphs(string $text): string
    {
        return nl2br(self::escaped($text), false);
    }
}

echo renderBad("a<b", true, false) . " | " . Html::paragraphs("line 1\nline 2");
Output
OUTPUT
a&lt;b | line 1<br>
line 2

Example: a value object instead of a loose string

index.php
PHP
<?php
final class Email
{
    private function __construct(public readonly string $value) {}

    public static function from(string $raw): self
    {
        $email = strtolower(trim($raw));

        if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
            throw new InvalidArgumentException("Invalid email: $raw");
        }

        return new self($email);
    }
}

function sendWelcome(Email $to): string
{
    return "Welcome email to " . $to->value;   // no need to re-check the address
}

echo sendWelcome(Email::from("  Isha@Example.COM "));
Output
OUTPUT
Welcome email to isha@example.com

Once an Email exists, everyone can trust it. The checking happens in one place.

Refactoring safely

Refactoring means changing the structure of code without changing its behaviour. Do it in small steps:

  1. Make sure there is a test (or at least a manual check) for the behaviour.
  2. Make one small change (rename, extract, move).
  3. Run the test. Commit.
  4. Repeat.

Never mix a refactor with a new feature in one change. If something breaks, you will not know which one caused it.

Tests make change safe

Automated tests let you change code with confidence. Start with tests for the important business rules (prices, permissions, validation), not for every getter.

Document the important things

  • A README that explains how to install, configure and run the project.
  • Why comments for surprising decisions.
  • Type declarations and DocBlocks for what the types cannot say (@param string[] $tags).
  • A short architecture note: the folder layout and the request flow (see Clean PHP Project Structure).

Version control habits

  • Small commits with clear messages (“Validate email on signup”).
  • One purpose per pull request.
  • Review each other’s code. A fresh reader finds confusing parts.

Handle upgrades early

Keep PHP, Composer packages and frameworks updated in small steps. Fix deprecation notices when they appear (see PHP Errors). A project that is five versions behind is expensive to rescue.

The final test

Could a new teammate:

  1. Install and run the project from the README?
  2. Find where a feature lives within a few minutes?
  3. Change it without fear of breaking something unrelated?

If yes, the code is maintainable.

info
You are ready for real projects
You now know the language, the tools and the habits. The next chapter puts them together in eleven projects, from a calculator to a small CMS.

Exercise: find the smells

This function has several problems. Name three, and say how you would fix each: function process($d, $t, $f) { $r = ...; if ($f == 1) { if ($t == 'a') { /* 80 lines */ } } return $r; }

Show the answer
  1. Unclear names ($d, $t, $f, process): rename to say what they are.
  2. Magic values and a flag ($f == 1, $t == 'a'): use named constants or an enum and separate methods.
  3. Deep nesting and a very long block: use early returns and extract the 80 lines into smaller, named functions.

Also: no types, and loose == comparison.