DEV Community

Carlos Viana
Carlos Viana

Posted on • Edited on

Inheritance vs. Composition in PHP πŸš—

Inheritance vs. Composition in PHP πŸš—

When programming with Object-Oriented Programming (OOP), it's important to understand the difference between Inheritance and Composition:

Inheritance: It's one of the fundamental principles of OOP and allows a class to inherit properties and methods from another class. In simple terms, inheritance enables the creation of a new class (called a subclass or child class) that reuses, extends, or modifies the behavior of an existing class (called a superclass or parent class).

<?php 

// Defining an Engine class that has a start method
class Engine
{
    // The start method simulates the operation of an engine
    public function start()
    {
        return "Engine Started!";
    }
}

// The Car class extends the Engine class, meaning it inherits
// all properties and methods from the Engine class
class Car extends Engine
{
    // Here, we are overriding the start method from the Engine class
    // This allows us to customize the behavior of the method in the child class
    public function start()
    {
        return "The car engine is started!";
    }
}

// We create an instance of the Car class
$car = new Car();

// Since the start method was overridden in the Car class, it executes the new behavior
echo $car->start(); // Output: The car engine is started!
Enter fullscreen mode Exit fullscreen mode

Composition Now, imagine that we are modeling a car that is composed of several parts, like an engine and wheels. Instead of creating a single "Car" class with all the details, we can break down these functionalities into smaller classes. The car will be composed of objects from other classes, such as an Engine.

<?php

// Defining an Engine class with the start method
class Engine
{
    // The start method simulates the operation of an engine
    public function start()
    {
        return "Engine Started!";
    }
}

// The Car class is composed of an Engine object, rather than inheriting from Engine
class Car
{
    // Private property to store the instance of Engine
    private Engine $engine;

    // The constructor receives an instance of Engine as a dependency
    // This is an example of dependency injection
    public function __construct(Engine $engine)
    {
        // We assign the Engine object to the private property
        $this->engine = $engine;
    }

    // The start method delegates the responsibility of starting the engine to the Engine object
    public function start()
    {
        // Here we call the start method of the Engine object
        return $this->engine->start();
    }
}

// We create an instance of Engine
$v4Engine = new Engine();

// We create an instance of Car, passing the Engine as a parameter
$car = new Car($v4Engine);

// When we call the start method, it delegates the task to the Engine object

echo $car->start(); // Output: Engine Started!
Enter fullscreen mode Exit fullscreen mode

Understanding Instead of the Car class containing the logic to start the engine directly, it delegates that responsibility to the Engine object. This keeps the Car class focused on its own responsibility, making the code more modular and easier to maintain. In the future, you can modify or improve the Engine without needing to change the Car class's code.

Advantages This approach is more flexible because it allows the car to have different types of engines (for example, a V4, V6, or electric engine) without needing to change the Car class. This modularity makes it easier to maintain and expand the system.

You can run the code at https://onecompiler.com/php

Top comments (2)

Collapse
 
xwero profile image
david duymelinck

Great post.

I would change the Engine class typing, in the second Car example, with an Engine interface.
Using an interface gives you a lot more room to specify the different characteristics for the engines.
You mention V4, V6 and electric engine, but with a class you have to extend the class each time a new engine time has to added. With an interface you can create a separate class for every engine.

enum EngineType {
    case V4;
    case V6;
   case Electric;
}

class Engine {
   public function __construct(public Enginetype $engineType) {}

   public function start() : string {
        return match($this->engineType) {
            EngineType::V4, EngineType::V6 => 'started combustion',
            EngineType::Electric => 'closed circuit',
        };
   }
}

class Car
{
    public function __construct(private Engine $engine)
    {}

    public function start()
    {
        return $this->engine->start();
    }
}
Enter fullscreen mode Exit fullscreen mode

with Engine interface

interface Engine {
    public function start() : string;
}

class CombustionEngine implements Engine {
   public function start() : string {
      return 'started combustion';
  }
}

class V4Engine extends CombustionEngine {
   public function start() : string {
      return 'started V4 combustion';
  }
}

class V6Engine extends CombustionEngine {
   public function start() : string {
      return 'started V6 combustion';
  }
}

class ElectricEngine implements Engine {
   public function start() : string {
      return 'closed circuit';
  }
}

class Car
{
    public function __construct(private Engine $engine)
    {}

    public function start()
    {
        return $this->engine->start();
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example I added a CombustionEngine class to be the parent of the V4Egine and V6Engine.
So it is mix of inheritance and composition. This it how all codebases are structured.
Use both features of OOP when they are appropriate.

Collapse
 
mateus-ic1101 profile image
Mateus πŸ‡§πŸ‡·

Nice