Skip to content

PHP Inheritance and Traits — Code Reuse Through Class Hierarchies and Horizontal Composition

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about PHP Inheritance and Traits. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP inheritance allows a child class to extend a parent class inheriting its properties and methods, while traits provide horizontal code reuse by letting you import method sets into unrelated classes without creating deep inheritance chains.

Why It Matters

Inheritance lets you model "is-a" relationships: a Car is a Vehicle, an Admin is a User. Traits solve the problem of sharing code between unrelated classes without forcing them into the same inheritance hierarchy. PHP uses single inheritance (one parent per class), which prevents the diamond problem found in C++. Understanding when to use inheritance, traits, or composition determines whether your codebase is flexible or fragile.

Real-World Use

Laravel's Eloquent models extend an Eloquent base class that provides database query methods. Symfony's Controller class provides shortcut methods for rendering templates and generating URLs. PHP uses traits extensively: Laravel's Notifiable trait, Illuminate\Support\Traits\Macroable, and Symfony's SerializerComponent. DodaTech's codebase uses traits for audit logging and cache invalidation across unrelated model classes.

What You Will Learn

  • Extending classes with the extends keyword
  • Overriding methods and calling parent methods
  • The final keyword for classes and methods
  • Abstract classes and methods
  • Defining and using traits
  • Trait Conflict Resolution with insteadof and as
  • Composition Over Inheritance principle

Learning Path

flowchart LR
  A[Classes and OOP] --> B[Inheritance and Traits
You are here] B --> C[Namespaces] C --> D[PDO] D --> E[MVC Pattern] style B fill:#f90,color:#fff

Basic Inheritance

A child class inherits all public and protected members from its parent:

<?php
class Vehicle {
    public function __construct(
        public readonly string $brand,
        protected int $speed = 0,
    ) {}

    public function accelerate(int $amount): void {
        $this->speed += $amount;
        echo "Accelerating to {$this->speed} km/h\n";
    }

    public function getSpeed(): int {
        return $this->speed;
    }
}

class Car extends Vehicle {
    private bool $doorsLocked = true;

    public function lockDoors(): void {
        $this->doorsLocked = true;
        echo "Doors locked\n";
    }

    public function unlockDoors(): void {
        $this->doorsLocked = false;
        echo "Doors unlocked\n";
    }
}

// Usage
$car = new Car('Toyota');
$car->accelerate(50);   // Inherited from Vehicle
$car->lockDoors();      // Defined in Car
echo $car->getSpeed();  // 50

Overriding Methods

Child classes can override parent methods. Use parent:: to call the parent version:

<?php
class Animal {
    public function makeSound(): string {
        return 'Some generic sound';
    }
}

class Dog extends Animal {
    public function makeSound(): string {
        return 'Woof! Woof!';
    }
}

class Cat extends Animal {
    public function makeSound(): string {
        return 'Meow';
    }
}

// Preserving parent behavior
class Logger {
    public function log(string $message): void {
        echo '[' . date('Y-m-d H:i:s') . '] ' . $message . "\n";
    }
}

class FileLogger extends Logger {
    private string $file;

    public function __construct(string $file) {
        $this->file = $file;
    }

    public function log(string $message): void {
        parent::log($message);  // Call parent for formatting
        file_put_contents($this->file, $message . "\n", FILE_APPEND);
    }
}

The Final Keyword

final prevents a method from being overridden or a class from being extended:

<?php
final class SecurityConfig {
    public function __construct(
        public readonly string $encryptionKey,
        public readonly string $hashAlgorithm,
    ) {}
}

// Error: Cannot extend final class
// class CustomConfig extends SecurityConfig {}

class PaymentProcessor {
    final public function validateCard(string $number): bool {
        // Critical validation logic that should not be overridden
        return preg_match('/^\d{16}$/', $number) === 1;
    }
}

class CustomProcessor extends PaymentProcessor {
    // Error: Cannot override final method
    // public function validateCard(string $number): bool { ... }
}

Abstract Classes

Abstract classes define a contract that child classes must implement:

<?php
abstract class DatabaseConnection {
    protected \PDO $pdo;

    abstract public function connect(): void;
    abstract public function query(string $sql): array;

    public function disconnect(): void {
        $this->pdo = null;
        echo "Disconnected\n";
    }
}

class MySQLConnection extends DatabaseConnection {
    public function connect(): void {
        // MySQL specific connection logic
        echo "Connected to MySQL\n";
    }

    public function query(string $sql): array {
        // MySQL specific query execution
        return [];
    }
}

class PostgreSQLConnection extends DatabaseConnection {
    public function connect(): void {
        echo "Connected to PostgreSQL\n";
    }

    public function query(string $sql): array {
        // PostgreSQL specific query execution
        return [];
    }
}

Traits

Traits let you reuse methods across unrelated classes:

<?php
trait Timestampable {
    private \DateTimeImmutable $createdAt;
    private ?\DateTimeImmutable $updatedAt = null;

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

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

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

    public function getUpdatedAt(): ?\DateTimeImmutable {
        return $this->updatedAt;
    }
}

trait SoftDeletable {
    private ?\DateTimeImmutable $deletedAt = null;

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

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

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

class User {
    use Timestampable, SoftDeletable;

    public function __construct(
        public string $name,
        public string $email,
    ) {
        $this->initializeTimestamps();
    }
}

// Usage
$user = new User('Alice', 'alice@example.com');
echo $user->getCreatedAt()->format('Y-m-d');  // Current date
$user->softDelete();
echo $user->isDeleted() ? 'Deleted' : 'Active';  // Deleted

Trait Conflict Resolution

When two traits have methods with the same name, use insteadof and as:

<?php
trait A {
    public function sayHello(): string {
        return 'Hello from A';
    }
}

trait B {
    public function sayHello(): string {
        return 'Hello from B';
    }
}

class Greeter {
    use A, B {
        B::sayHello insteadof A;  // Use B's version
        A::sayHello as sayHelloFromA;  // Alias A's version
    }
}

$greeter = new Greeter();
echo $greeter->sayHello();       // Hello from B
echo $greeter->sayHelloFromA();  // Hello from A

Trait Properties

Traits can also define properties:

<?php
trait Cachable {
    private array $cache = [];

    public function cacheGet(string $key): mixed {
        return $this->cache[$key] ?? null;
    }

    public function cacheSet(string $key, mixed $value): void {
        $this->cache[$key] = $value;
    }

    public function cacheClear(): void {
        $this->cache = [];
    }
}

class ExpensiveCalculator {
    use Cachable;

    public function fibonacci(int $n): int {
        $cached = $this->cacheGet("fib_$n");
        if ($cached !== null) return $cached;

        $result = $n <= 1 ? $n : $this->fibonacci($n - 1) + $this->fibonacci($n - 2);
        $this->cacheSet("fib_$n", $result);
        return $result;
    }
}

Composition Over Inheritance

Prefer composition when the relationship is "has-a" rather than "is-a":

<?php
// Inheritance (is-a)
// class AdminUser extends User {}  // OK if Admin IS a User

// Composition (has-a)
class NotificationService {
    public function send(string $message, string $recipient): void {
        echo "Sending: $message to $recipient\n";
    }
}

class OrderProcessor {
    public function __construct(
        private NotificationService $notifier,  // Has a notifier
        private Logger $logger,                 // Has a logger
    ) {}

    public function process(Order $order): void {
        // Process order
        $this->notifier->send('Order confirmed', $order->email);
        $this->logger->log('Order processed: ' . $order->id);
    }
}

Common Mistakes

1. Deep Inheritance Hierarchies

More than 3 levels of inheritance usually indicates a design problem. Deep hierarchies are fragile -- a change at the top affects everything below. Prefer composition or traits.

2. Forgetting parent::constructor

class Child extends Parent {
    public function __construct() {
        // Parent constructor not called
    }
}

Always call parent::__construct() if the parent has initialization logic.

3. Using Inheritance for Code Sharing Only

If two classes share code but have no "is-a" relationship, use a trait instead of forcing them under a common parent.

4. Making Classes That Should Be Final Not Final

If a class was not designed for extension, mark it final. This prevents misuse and communicates intent.

5. Trait Properties Causing Conflicts

If a class using a trait also defines a property with the same name, it causes a fatal error. Trait properties must be unique.

Practice Questions

  1. What is the difference between extends and implements? extends inherits from a class (single inheritance). implements fulfills an interface contract (multiple interfaces allowed).

  2. Why does PHP use single inheritance? To avoid the diamond problem where a class inherits the same method from two paths. Traits provide an alternative with explicit conflict resolution.

  3. What is the difference between an abstract class and a trait? Abstract classes can have constructor logic and properties. Traits cannot have constructors and are composed, not inherited.

  4. When should you use final on a method? When the method contains critical logic that must not be altered by subclasses, such as security validation or Transaction handling.

  5. Challenge: Design a class hierarchy for a document management system with abstract Document, concrete types (PDF, Word, HTML), and traits for Versionable and Shareable behavior.

Mini Project: Plugin Architecture

Build a plugin system using abstract classes and traits:

<?php
abstract class Plugin {
    public function __construct(
        public readonly string $name,
        public readonly string $version,
    ) {}

    abstract public function initialize(): void;
    abstract public function execute(): void;

    public function getInfo(): string {
        return "{$this->name} v{$this->version}";
    }
}

trait Loggable {
    protected function log(string $message): void {
        echo "[" . date('H:i:s') . "] " . static::class . ": $message\n";
    }
}

class CachePlugin extends Plugin {
    use Loggable;

    public function initialize(): void {
        $this->log('Cache plugin initialized');
    }

    public function execute(): void {
        $this->log('Clearing expired cache entries');
    }
}

class SecurityPlugin extends Plugin {
    use Loggable;

    public function initialize(): void {
        $this->log('Security plugin initialized');
    }

    public function execute(): void {
        $this->log('Running security audit');
    }
}

FAQ

What is the difference between public, protected, and private inheritance access?

PHP does not have access modifiers for inheritance. All public and protected members are inherited. Private members stay in the parent class.

Can a class use multiple traits?

Yes. A class can use multiple traits separated by commas. If two traits define the same method, you must resolve the conflict with insteadof or as.

Can traits have constructors?

Traits cannot have constructors. If a trait initializes properties, the using class must call an initialization method manually.

What is the diamond problem?

In languages with multiple inheritance, if two parent classes define the same method, the child does not know which to use. PHP avoids this with single inheritance + traits.

Should I use inheritance or composition?

Use inheritance for clear 'is-a' relationships (a Dog is an Animal). Use composition for 'has-a' relationships (a Car has an Engine). Prefer composition for flexibility.

What is Next

Now that you understand inheritance and traits, proceed to Namespaces and Autoload to organize your classes in namespaces and set up PSR-4 autoloading. Then read PDO to connect to databases securely.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro