Mastering PHP OOP: Architecture, Namespaces, and Modern PHP 8+ Patterns
Transition from procedural scripting to robust object-oriented architecture. This article provides a deep exploration of OOP principles—including inheritance, polymorphism, and magic methods—before diving into the modern syntax improvements introduced in PHP 8.0, 8.1, and 8.2.
Core OOP Concepts
Classes and Objects
A class is a blueprint/template that defines the structure and behavior of objects, while an object is a concrete instance of that class created in memory. Think of a class as a cookie cutter and objects as the actual cookies.
class Car { public string $brand; public function start(): void { echo "Engine started!"; } } $myCar = new Car(); // Object instantiation $myCar->brand = "Toyota"; $myCar->start();
Properties and Methods
Properties are variables that hold an object's state/data, while methods are functions that define the object's behavior and can manipulate those properties.
class User { public string $name; // Property public int $age; // Property public function greet(): string { // Method return "Hello, I'm {$this->name}"; } }
Access Modifiers (public, private, protected)
Access modifiers control the visibility of properties and methods: public (accessible everywhere), private (only within the class), and protected (within the class and its children).
┌─────────────────────────────────────────────────────┐ │ Modifier │ Class │ Subclass │ Outside │ ├─────────────────────────────────────────────────────┤ │ public │ ✅ │ ✅ │ ✅ │ │ protected │ ✅ │ ✅ │ ❌ │ │ private │ ✅ │ ❌ │ ❌ │ └─────────────────────────────────────────────────────┘
class BankAccount { public string $owner; // Anyone can access protected float $balance; // Class + children only private string $pin; // This class only }
Constructors and Destructors
The constructor (__construct) is automatically called when an object is created to initialize its state, while the destructor (__destruct) is called when the object is destroyed or goes out of scope, useful for cleanup operations.
class Database { private $connection; public function __construct(string $host) { $this->connection = new PDO("mysql:host=$host"); echo "Connected!\n"; } public function __destruct() { $this->connection = null; echo "Connection closed!\n"; } }
$this Keyword
The $this keyword is a reference to the current object instance, allowing you to access the object's own properties and methods from within its class definition.
class Counter { private int $count = 0; public function increment(): self { $this->count++; // Access own property return $this; // Return self for chaining } public function getCount(): int { return $this->count; } } $c = (new Counter())->increment()->increment()->getCount(); // 2
Static Properties and Methods
Static members belong to the class itself rather than any specific instance, accessed via ClassName:: syntax, and are shared across all instances—useful for counters, utility functions, or singleton patterns.
class Logger { private static int $logCount = 0; public static function log(string $msg): void { self::$logCount++; echo "[" . self::$logCount . "] $msg\n"; } } Logger::log("Error occurred"); // No object needed Logger::log("Another error");
Class Constants
Class constants are immutable values defined within a class using const, accessible without instantiation, and conventionally named in UPPER_CASE.
class HttpStatus { public const OK = 200; public const NOT_FOUND = 404; public const SERVER_ERROR = 500; } echo HttpStatus::OK; // 200
Inheritance
Inheritance allows a child class to extend a parent class, inheriting its public and protected members while enabling code reuse and establishing "is-a" relationships.
class Animal { protected string $name; public function eat(): void { echo "Eating...\n"; } } class Dog extends Animal { public function bark(): void { echo "Woof!\n"; } } $dog = new Dog(); $dog->eat(); // Inherited method $dog->bark(); // Own method
Method Overriding
Method overriding allows a child class to provide a specific implementation of a method already defined in its parent class, replacing the inherited behavior.
class Shape { public function area(): float { return 0.0; } } class Circle extends Shape { public function __construct(private float $radius) {} public function area(): float { // Override parent return pi() * $this->radius ** 2; } }
final Keyword
The final keyword prevents a class from being extended or a method from being overridden, enforcing design constraints and ensuring critical behavior remains unchanged.
final class SecurityManager { // Cannot be extended // Critical security logic } class Payment { final public function validate(): bool { // Cannot be overridden return true; } }
Abstract Classes and Methods
Abstract classes cannot be instantiated and serve as base templates; abstract methods declared within them have no body and must be implemented by concrete child classes.
abstract class PaymentGateway { abstract public function charge(float $amount): bool; // No body public function log(string $msg): void { // Concrete method echo "[Payment] $msg\n"; } } class StripePayment extends PaymentGateway { public function charge(float $amount): bool { // Must implement this return true; } }
Interfaces
Interfaces define a contract of methods that implementing classes must provide; they contain only method signatures (no implementation) and a class can implement multiple interfaces.
interface Cacheable { public function getKey(): string; public function getTTL(): int; } interface Serializable { public function serialize(): string; } class User implements Cacheable, Serializable { public function getKey(): string { return "user_1"; } public function getTTL(): int { return 3600; } public function serialize(): string { return json_encode($this); } }
Traits
Traits are a mechanism for horizontal code reuse in single-inheritance languages, allowing you to "copy and paste" methods into multiple unrelated classes.
trait Timestampable { public DateTime $createdAt; public DateTime $updatedAt; public function touch(): void { $this->updatedAt = new DateTime(); } } class Post { use Timestampable; // "Paste" the trait methods here } class Comment { use Timestampable; // Reuse in another class }
Anonymous Classes
Anonymous classes are one-off, unnamed classes defined inline, useful for simple implementations or mocking in tests without creating a separate class file.
interface Logger { public function log(string $msg): void; } function process(Logger $logger): void { $logger->log("Processing..."); } // Create anonymous class on the fly process(new class implements Logger { public function log(string $msg): void { echo $msg; } });
Object Cloning
The clone keyword creates a shallow copy of an object; the __clone magic method can be implemented to customize the cloning behavior (e.g., deep copying nested objects).
class Document { public array $pages = []; public function __clone() { // Deep clone the array $this->pages = array_map(fn($p) => clone $p, $this->pages); } } $doc1 = new Document(); $doc2 = clone $doc1; // New object, triggers __clone
Object Comparison
Use == to compare if two objects have the same class and equal property values, and === to check if they are the exact same instance in memory.
$a = new stdClass(); $a->x = 1; $b = new stdClass(); $b->x = 1; $c = $a; var_dump($a == $b); // true (same values) var_dump($a === $b); // false (different instances) var_dump($a === $c); // true (same instance)
Late Static Binding
Late static binding uses static:: instead of self:: to reference the class that was actually called at runtime, rather than the class where the method is defined.
class Model { public static function create(): static { return new static(); // Resolves to called class } public static function className(): string { return static::class; // Late bound } } class User extends Model {} $user = User::create(); // Returns User, not Model echo User::className(); // "User"
Magic Methods
Magic methods are special methods prefixed with __ that PHP calls automatically in certain situations (object creation, property access, method calls, serialization, etc.).
class MagicDemo { private array $data = []; public function __construct() { } // On new public function __destruct() { } // On destroy public function __get(string $name) { // On undefined prop read return $this->data[$name] ?? null; } public function __set(string $name, $val): void { // On undefined prop write $this->data[$name] = $val; } public function __call(string $name, array $args) { // On undefined method echo "Called $name with " . count($args) . " args"; } public function __toString(): string { // On (string) cast return json_encode($this->data); } }
Autoloading Classes
Autoloading automatically loads class files when needed using spl_autoload_register(), eliminating manual require statements—typically handled by Composer following PSR-4 standards.
// Manual autoloader spl_autoload_register(function (string $class) { $file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php'; if (file_exists($file)) { require $file; } }); // With Composer (just include once): require 'vendor/autoload.php';
Namespaces
Namespaces organize code into logical groups, prevent naming collisions between classes/functions, and typically mirror the directory structure following PSR-4.
// File: src/App/Models/User.php namespace App\Models; class User { public function __construct(public string $name) {} }
src/ └── App/ ├── Models/ │ └── User.php → namespace App\Models └── Services/ └── UserService.php → namespace App\Services
use Statements and Aliasing
The use statement imports classes/functions/constants from namespaces into the current scope, and aliasing with as resolves naming conflicts or shortens long names.
namespace App\Controllers; use App\Models\User; use App\Services\UserService as Service; use Vendor\Package\User as VendorUser; // Alias to avoid conflict use function App\Helpers\format_date; use const App\Config\MAX_USERS; class UserController { public function show() { $user = new User(); // App\Models\User $svc = new Service(); // App\Services\UserService $other = new VendorUser(); // Vendor\Package\User } }
PHP 8+ OOP Features
Constructor Property Promotion
Constructor property promotion (PHP 8.0+) allows you to declare and initialize properties directly in the constructor signature, drastically reducing boilerplate code.
// Before PHP 8 class OldUser { private string $name; private int $age; public function __construct(string $name, int $age) { $this->name = $name; $this->age = $age; } } // PHP 8+ (same result) class User { public function __construct( private string $name, private int $age ) {} }
Readonly Properties (PHP 8.1+)
Readonly properties can only be initialized once (typically in the constructor) and cannot be modified afterward, providing immutability guarantees.
class User { public function __construct( public readonly string $id, public readonly string $email ) {} } $user = new User('123', 'a@b.com'); echo $user->email; // ✅ Can read $user->email = 'new'; // ❌ Fatal error: Cannot modify readonly property
Readonly Classes (PHP 8.2+)
A readonly class automatically makes all declared properties readonly, ensuring complete immutability of the object's state.
readonly class ImmutablePoint { public function __construct( public float $x, public float $y ) {} } $p = new ImmutablePoint(3.0, 4.0); // All properties are implicitly readonly
Enumerations (Enums)
Enums (PHP 8.1+) define a type with a fixed set of possible values, optionally backed by scalar values, and can include methods—far superior to class constants.
enum Status: string { case Pending = 'pending'; case Active = 'active'; case Archived = 'archived'; public function label(): string { return match($this) { self::Pending => '⏳ Pending', self::Active => '✅ Active', self::Archived => '📦 Archived', }; } } function setStatus(Status $s): void {} // Type-safe! setStatus(Status::Active); echo Status::Active->value; // "active"
Intersection Types
Intersection types (PHP 8.1+) require a value to satisfy all specified types simultaneously, written with &, useful when an object must implement multiple interfaces.
interface Loggable { public function log(): void; } interface Jsonable { public function toJson(): string; } class Event implements Loggable, Jsonable { public function log(): void { } public function toJson(): string { return '{}'; } } function process(Loggable&Jsonable $item): void { $item->log(); echo $item->toJson(); }
Disjunctive Normal Form (DNF) Types
DNF types (PHP 8.2+) allow combining union (|) and intersection (&) types in a normalized form: intersections grouped in parentheses, combined with unions.
interface A {} interface B {} class C implements A, B {} // (A&B) | null — Must implement both A and B, or be null function process((A&B)|null $obj): void { if ($obj !== null) { // $obj definitely implements A and B } }
Attributes (Annotations)
Attributes (PHP 8.0+) are structured metadata attached to classes, methods, properties, or parameters, replacing docblock annotations and accessible via Reflection.
#[Attribute] class Route { public function __construct( public string $path, public string $method = 'GET' ) {} } class UserController { #[Route('/users', 'GET')] public function index(): array { return []; } #[Route('/users/{id}', 'GET')] public function show(int $id): array { return []; } } // Read attributes via Reflection $ref = new ReflectionMethod(UserController::class, 'index'); $attrs = $ref->getAttributes(Route::class); $route = $attrs[0]->newInstance(); // Route object echo $route->path; // "/users"
Fibers
Fibers (PHP 8.1+) provide lightweight cooperative concurrency, allowing you to pause and resume code execution—the building block for async frameworks like ReactPHP and Amp.
$fiber = new Fiber(function(): void { echo "1. Fiber started\n"; $value = Fiber::suspend('paused'); // Pause here echo "3. Resumed with: $value\n"; }); echo $fiber->start(); // Prints 1, returns "paused" echo "2. Main code\n"; echo $fiber->resume('hello'); // Prints 3 /* Output: 1. Fiber started 2. Main code 3. Resumed with: hello */
┌─────────────────────────────────────────────────────────┐ │ Fiber Flow │ ├─────────────────────────────────────────────────────────┤ │ Main Fiber │ │ │ │ │ │ │──start()────►│ │ │ │ │ (runs until suspend) │ │ │◄──suspend()──│ │ │ │ │ │ │ │──resume()───►│ │ │ │ │ (continues execution) │ │ │◄─────────────│ (fiber ends) │ └─────────────────────────────────────────────────────────┘
Quick Reference Summary
┌─────────────────────────────────────────────────────────────────┐ │ PHP OOP QUICK REFERENCE │ ├─────────────────────────────────────────────────────────────────┤ │ INHERITANCE: class Child extends Parent {} │ │ INTERFACE: class X implements A, B {} │ │ TRAIT: class X { use TraitA, TraitB; } │ │ ABSTRACT: abstract class X { abstract function y(); } │ │ FINAL: final class X {} / final function y() {} │ ├─────────────────────────────────────────────────────────────────┤ │ PHP 8+ FEATURES │ │ ───────────────── │ │ Promotion: __construct(private string $x) {} │ │ Readonly: public readonly string $x; │ │ Enum: enum Status: string { case Active = 'a'; } │ │ Attributes: #[Route('/path')] │ │ Intersection: TypeA&TypeB │ │ DNF: (TypeA&TypeB)|null │ └─────────────────────────────────────────────────────────────────┘