PHP Object-Oriented Programming

Modern PHP OOP Patterns

2026-02-01

Explanation

OOP in Modern PHP

PHP has evolved significantly. PHP 8 brings constructor property promotion, named arguments, attributes, and more. Modern PHP OOP rivals any language.

Key Concepts

  • Classes and Objects: Blueprints and instances
  • Visibility: public, protected, private
  • Interfaces: Contracts for classes
  • Traits: Reusable code blocks
  • Abstract Classes: Partial implementations

PHP 8 Features

// Constructor property promotion
class User {
    public function __construct(
        public string $name,
        public string $email,
        private ?string $password = null
    ) {}
}

// Named arguments
$user = new User(email: 'art@bpc.com', name: 'Arthur');

Demonstration

Example 1: Modern PHP Classes

<?php

declare(strict_types=1);

class User
{
    // Constants
    public const STATUS_ACTIVE = 'active';
    public const STATUS_INACTIVE = 'inactive';

    // Static property
    private static int $count = 0;

    // Constructor with property promotion (PHP 8+)
    public function __construct(
        public readonly int $id,
        public string $name,
        public string $email,
        private string $passwordHash = '',
        private string $status = self::STATUS_ACTIVE
    ) {
        self::$count++;
    }

    // Getter
    public function getStatus(): string
    {
        return $this->status;
    }

    // Setter with validation
    public function setStatus(string $status): self
    {
        if (!in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE])) {
            throw new InvalidArgumentException('Invalid status');
        }
        $this->status = $status;
        return $this;
    }

    // Method
    public function isActive(): bool
    {
        return $this->status === self::STATUS_ACTIVE;
    }

    // Static method
    public static function getCount(): int
    {
        return self::$count;
    }

    // Magic method: string representation
    public function __toString(): string
    {
        return "{$this->name} <{$this->email}>";
    }

    // Magic method: JSON serialization
    public function jsonSerialize(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'status' => $this->status
        ];
    }
}

// Usage
$user = new User(
    id: 1,
    name: 'Arthur',
    email: 'art@bpc.com'
);

echo $user;  // Arthur <art@bpc.com>
echo User::getCount();  // 1

Example 2: Interfaces and Abstract Classes

<?php

// Interface
interface Authenticatable
{
    public function authenticate(string $password): bool;
    public function getAuthIdentifier(): string;
}

interface Notifiable
{
    public function notify(string $message): void;
}

// Abstract class
abstract class Entity
{
    protected \DateTimeImmutable $createdAt;
    protected \DateTimeImmutable $updatedAt;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
        $this->updatedAt = new \DateTimeImmutable();
    }

    abstract public function toArray(): array;

    public function touch(): void
    {
        $this->updatedAt = new \DateTimeImmutable();
    }

    public function getCreatedAt(): \DateTimeImmutable
    {
        return $this->createdAt;
    }
}

// Implementation
class User extends Entity implements Authenticatable, Notifiable
{
    public function __construct(
        public readonly int $id,
        public string $name,
        public string $email,
        private string $passwordHash
    ) {
        parent::__construct();
    }

    public function authenticate(string $password): bool
    {
        return password_verify($password, $this->passwordHash);
    }

    public function getAuthIdentifier(): string
    {
        return $this->email;
    }

    public function notify(string $message): void
    {
        // Send notification
        echo "Notifying {$this->name}: {$message}";
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt->format('c')
        ];
    }
}

Example 3: Traits

<?php

// Traits for reusable code
trait Timestampable
{
    protected ?\DateTimeImmutable $createdAt = null;
    protected ?\DateTimeImmutable $updatedAt = null;

    public function initTimestamps(): void
    {
        $now = new \DateTimeImmutable();
        $this->createdAt ??= $now;
        $this->updatedAt = $now;
    }

    public function touch(): void
    {
        $this->updatedAt = new \DateTimeImmutable();
    }
}

trait SoftDeletes
{
    protected ?\DateTimeImmutable $deletedAt = null;

    public function delete(): void
    {
        $this->deletedAt = new \DateTimeImmutable();
    }

    public function restore(): void
    {
        $this->deletedAt = null;
    }

    public function isDeleted(): bool
    {
        return $this->deletedAt !== null;
    }
}

trait HasUuid
{
    protected string $uuid;

    public function initUuid(): void
    {
        $this->uuid = bin2hex(random_bytes(16));
    }

    public function getUuid(): string
    {
        return $this->uuid;
    }
}

// Using multiple traits
class Post
{
    use Timestampable, SoftDeletes, HasUuid;

    public function __construct(
        public string $title,
        public string $content,
        public int $authorId
    ) {
        $this->initTimestamps();
        $this->initUuid();
    }
}

$post = new Post('Hello', 'World', 1);
$post->delete();
echo $post->isDeleted();  // true
$post->restore();
echo $post->isDeleted();  // false

Example 4: Enums (PHP 8.1+)

<?php

// Basic enum
enum Status
{
    case Pending;
    case Active;
    case Suspended;
    case Deleted;
}

// Backed enum (with values)
enum Role: string
{
    case Admin = 'admin';
    case Editor = 'editor';
    case User = 'user';
    case Guest = 'guest';

    public function permissions(): array
    {
        return match($this) {
            self::Admin => ['read', 'write', 'delete', 'admin'],
            self::Editor => ['read', 'write', 'delete'],
            self::User => ['read', 'write'],
            self::Guest => ['read']
        };
    }

    public function canDelete(): bool
    {
        return in_array('delete', $this->permissions());
    }
}

// Integer backed enum
enum Priority: int
{
    case Low = 1;
    case Medium = 2;
    case High = 3;
    case Critical = 4;

    public function label(): string
    {
        return match($this) {
            self::Low => 'Low Priority',
            self::Medium => 'Medium Priority',
            self::High => 'High Priority',
            self::Critical => 'Critical!'
        };
    }
}

// Usage
class User
{
    public function __construct(
        public string $name,
        public Role $role = Role::User,
        public Status $status = Status::Pending
    ) {}
}

$admin = new User('Arthur', Role::Admin);
echo $admin->role->value;  // 'admin'
echo $admin->role->canDelete();  // true

// Get all cases
$roles = Role::cases();
// [Role::Admin, Role::Editor, Role::User, Role::Guest]

// From value
$role = Role::from('admin');  // Role::Admin
$role = Role::tryFrom('invalid');  // null

Example 5: Design Patterns

<?php

// Singleton
class Database
{
    private static ?self $instance = null;
    private \PDO $connection;

    private function __construct()
    {
        $this->connection = new \PDO(
            'mysql:host=localhost;dbname=app',
            'user',
            'password'
        );
    }

    public static function getInstance(): self
    {
        return self::$instance ??= new self();
    }

    public function query(string $sql): array
    {
        return $this->connection->query($sql)->fetchAll();
    }

    // Prevent cloning
    private function __clone() {}
}

// Factory
interface PaymentProcessor
{
    public function charge(float $amount): bool;
}

class StripeProcessor implements PaymentProcessor
{
    public function charge(float $amount): bool
    {
        // Stripe implementation
        return true;
    }
}

class PayPalProcessor implements PaymentProcessor
{
    public function charge(float $amount): bool
    {
        // PayPal implementation
        return true;
    }
}

class PaymentFactory
{
    public static function create(string $provider): PaymentProcessor
    {
        return match($provider) {
            'stripe' => new StripeProcessor(),
            'paypal' => new PayPalProcessor(),
            default => throw new \InvalidArgumentException("Unknown provider: $provider")
        };
    }
}

// Repository Pattern
interface UserRepository
{
    public function find(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function save(User $user): void;
    public function delete(User $user): void;
}

class DatabaseUserRepository implements UserRepository
{
    public function __construct(private \PDO $db) {}

    public function find(int $id): ?User
    {
        $stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        $data = $stmt->fetch();

        return $data ? $this->hydrate($data) : null;
    }

    public function findByEmail(string $email): ?User
    {
        $stmt = $this->db->prepare('SELECT * FROM users WHERE email = ?');
        $stmt->execute([$email]);
        $data = $stmt->fetch();

        return $data ? $this->hydrate($data) : null;
    }

    public function save(User $user): void
    {
        // Insert or update logic
    }

    public function delete(User $user): void
    {
        $stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
        $stmt->execute([$user->id]);
    }

    private function hydrate(array $data): User
    {
        return new User(
            id: $data['id'],
            name: $data['name'],
            email: $data['email'],
            passwordHash: $data['password_hash']
        );
    }
}

Key Takeaways:

  • PHP 8 makes OOP cleaner
  • Constructor promotion reduces boilerplate
  • Traits enable code reuse
  • Enums replace magic strings/numbers
  • Interfaces define contracts

Imitation

Challenge 1: Create a Validation System

Task: Build a validation system using OOP principles.

Solution

<?php

interface Rule
{
    public function passes(mixed $value): bool;
    public function message(): string;
}

class Required implements Rule
{
    public function passes(mixed $value): bool
    {
        return $value !== null && $value !== '';
    }

    public function message(): string
    {
        return 'This field is required';
    }
}

class Email implements Rule
{
    public function passes(mixed $value): bool
    {
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
    }

    public function message(): string
    {
        return 'Invalid email address';
    }
}

class MinLength implements Rule
{
    public function __construct(private int $min) {}

    public function passes(mixed $value): bool
    {
        return strlen($value) >= $this->min;
    }

    public function message(): string
    {
        return "Must be at least {$this->min} characters";
    }
}

class Validator
{
    private array $errors = [];

    public function __construct(
        private array $data,
        private array $rules
    ) {}

    public function validate(): bool
    {
        $this->errors = [];

        foreach ($this->rules as $field => $fieldRules) {
            $value = $this->data[$field] ?? null;

            foreach ($fieldRules as $rule) {
                if (!$rule->passes($value)) {
                    $this->errors[$field][] = $rule->message();
                }
            }
        }

        return empty($this->errors);
    }

    public function errors(): array
    {
        return $this->errors;
    }
}

// Usage
$validator = new Validator(
    ['email' => 'invalid', 'password' => '123'],
    [
        'email' => [new Required(), new Email()],
        'password' => [new Required(), new MinLength(8)]
    ]
);

if (!$validator->validate()) {
    print_r($validator->errors());
}


Practice

Exercise 1: Event System

Difficulty: Intermediate

Build an event dispatcher:

  • Register event listeners
  • Dispatch events
  • Support priorities

Exercise 2: Dependency Container

Difficulty: Advanced

Create a simple DI container:

  • Register services
  • Auto-resolve dependencies
  • Singleton support

Summary

What you learned:

  • Modern PHP class features
  • Interfaces and abstract classes
  • Traits for code reuse
  • PHP 8.1 enums
  • Common design patterns

Next Steps:

  • Read: PHP APIs
  • Practice: Build a small framework
  • Explore: Laravel internals

Resources