Skills Development Modern PHP Best Practices Guide

Modern PHP Best Practices Guide

v20260617
php
A comprehensive reference guide detailing modern and idiomatic PHP coding standards. It covers best practices across various topics, including type safety (strict typing, union types), object-oriented programming (constructor promotion, enums, match expressions), efficient array handling, robust error handling (exceptions), and modern string manipulation functions. Ideal for developers looking to upgrade legacy PHP code to modern, clean, and performant standards.
Get Skill
89 downloads
Overview

PHP: Idiomatic Efficiency Reference

Table of Contents

  1. Arrays & Collections
  2. Type Safety
  3. Error Handling
  4. String Handling
  5. OOP & Modern PHP
  6. Functions & Closures
  7. Anti-patterns specific to PHP

1. Arrays & Collections {#arrays}

// ❌ Manual accumulation
$result = [];
foreach ($items as $item) {
    if ($item->isActive()) {
        $result[] = strtoupper($item->getName());
    }
}

// ✅
$result = array_map(
    fn($i) => strtoupper($i->getName()),
    array_filter($items, fn($i) => $i->isActive())
);
// ❌ Manual key-value grouping
$grouped = [];
foreach ($items as $item) {
    $grouped[$item->getCategory()][] = $item;
}

// ✅ (PHP 8.1+) — or use the loop above; PHP lacks a built-in groupBy
// The foreach is actually idiomatic PHP for grouping. No need to force array_* here.
// ❌ Checking isset then accessing
if (isset($data['key'])) {
    $value = $data['key'];
} else {
    $value = 'default';
}

// ✅
$value = $data['key'] ?? 'default';
// ❌ array_push for single element
array_push($items, $newItem);

// ✅
$items[] = $newItem;

Use array_map/array_filter for transforms. The foreach loop is fine when array functions would be less readable.


2. Type Safety {#types}

// ❌ No type declarations
function process($items) {
    return $items;
}

// ✅ (PHP 8.0+)
function process(array $items): array {
    return $items;
}
// ❌ Union type for nullable
function find(string $key): string|null { ... }

// ✅
function find(string $key): ?string { ... }
// ❌ Loose comparison
if ($value == '0') { ... } // true for 0, '', false, null

// ✅
if ($value === '0') { ... }
// ❌ Type checking with gettype()
if (gettype($x) === 'integer') { ... }

// ✅
if (is_int($x)) { ... }
// or with union types, avoid checks entirely

Enable declare(strict_types=1) at the top of every file.


3. Error Handling {#errors}

// ❌ Suppressing errors with @
$data = @file_get_contents($path);

// ✅
$data = file_get_contents($path);
if ($data === false) {
    throw new RuntimeException("Failed to read: $path");
}
// ❌ Catching \Exception and swallowing
try { process(); }
catch (\Exception $e) { /* silence */ }

// ✅
try {
    process();
} catch (SpecificException $e) {
    $this->logger->error($e->getMessage(), ['exception' => $e]);
    throw new AppException('Processing failed', previous: $e);
}
// ❌ Returning mixed types for error indication
function divide(int $a, int $b): int|false {
    if ($b === 0) return false;
    return intdiv($a, $b);
}

// ✅ — throw exception for exceptional cases
function divide(int $a, int $b): int {
    if ($b === 0) throw new \DivisionByZeroError();
    return intdiv($a, $b);
}

4. String Handling {#strings}

// ❌ Concatenation for variable interpolation
$msg = 'Hello, ' . $name . '! You have ' . $count . ' messages.';

// ✅
$msg = "Hello, {$name}! You have {$count} messages.";
// ❌ Manual string contains check
if (strpos($haystack, $needle) !== false) { ... }

// ✅ (PHP 8.0+)
if (str_contains($haystack, $needle)) { ... }
// ❌ substr for prefix/suffix check
if (substr($str, 0, 4) === 'http') { ... }
if (substr($str, -4) === '.php') { ... }

// ✅ (PHP 8.0+)
if (str_starts_with($str, 'http')) { ... }
if (str_ends_with($str, '.php')) { ... }

5. OOP & Modern PHP {#oop}

// ❌ Manual constructor property assignment
class User {
    private string $name;
    private int $age;
    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

// ✅ (PHP 8.0+)
class User {
    public function __construct(
        private readonly string $name,
        private readonly int $age,
    ) {}
}
// ❌ Constants as class properties
class Status {
    const ACTIVE = 'active';
    const INACTIVE = 'inactive';
}

// ✅ (PHP 8.1+)
enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
}
// ❌ instanceof chains
if ($shape instanceof Circle) { ... }
elseif ($shape instanceof Rectangle) { ... }

// ✅ (PHP 8.0+)
$area = match(true) {
    $shape instanceof Circle => $shape->radius ** 2 * M_PI,
    $shape instanceof Rectangle => $shape->width * $shape->height,
    default => throw new \InvalidArgumentException("Unknown shape"),
};
// ❌ Named constructor via static method returning new self()
class Money {
    public static function fromCents(int $cents): self {
        $m = new self();
        $m->cents = $cents;
        return $m;
    }
}

// ✅ (PHP 8.0+) — constructor promotion + named arguments
class Money {
    public function __construct(
        public readonly int $cents,
    ) {}
}
$m = new Money(cents: 500);

6. Functions & Closures {#functions}

// ❌ Verbose closure for simple operation
$doubled = array_map(function ($x) { return $x * 2; }, $numbers);

// ✅ (PHP 7.4+)
$doubled = array_map(fn($x) => $x * 2, $numbers);
// ❌ Passing globals or using `global` keyword
global $db;
function getUser(int $id) {
    global $db;
    return $db->find($id);
}

// ✅ — dependency injection
function getUser(int $id, PDO $db): ?User {
    return $db->find($id);
}
// ❌ Named arguments abused for every call
str_pad(string: $s, length: 10, pad_string: ' ', pad_type: STR_PAD_LEFT);

// ✅ — named args are useful for readability on ambiguous params; don't force
str_pad($s, 10, ' ', STR_PAD_LEFT);
// but named args shine for: new User(name: 'Alice', age: 30)

7. Anti-patterns specific to PHP {#antipatterns}

Anti-pattern Preferred
== for comparison === (strict equality)
@ error suppression explicit error handling
global keyword dependency injection
extract() on user input access keys explicitly
die() / exit() in library code throw exception
strpos !== false for contains str_contains() (PHP 8.0)
Manual constructor assignment constructor promotion (PHP 8.0)
Class constants for enums enum (PHP 8.1)
mixed return types specific typed returns
array for everything typed classes / DTOs
var_dump / print_r debugging proper logging (PSR-3)
Not using declare(strict_types=1) always enable

Limitations

  • These are language-specific guidelines and do not cover overall architectural decisions.
  • Over-compression might reduce readability; apply judgement.
Info
Category Development
Name php
Version v20260617
Size 6.69KB
Updated At 2026-06-18
Language