PHP Constructors and InheritancePHP Constructors and Inheritance

When working in OOP PHP, understanding constructors is essential for manipulating objects. Constructors are magic methods that get invoked when an object is instantiated from a class. They typically handle the initialization of an object’s properties. These initialisations are split between parent and child classes, and the relationship between parent and child class constructors can be somewhat confusing. There is much to say about PHP constructors and inheritance.

In this post, we’ll explore PHP constructors in the context of inheritance and what it means when a child constructor explicitly calls the parent constructor.

What Is a Constructor in PHP?

In PHP, a constructor is a magic function that is automatically executed when a new instance of a class is created. It is used to initialize the object’s properties or perform setup tasks that are required at the time of object creation.

The constructor in PHP is defined with the __construct method. Here’s a basic example:

<?php
class X {
    private $property;

    public function __construct($argument) {
          $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

$object = new X(1);
?>

In this example, when the new object X is instantiated, PHP automatically invokes the __construct() method to initialize the properties of the car object.

What Happens with Inheritance?

When one class extends another in PHP, the child class inherits all public and protected methods from the parent class, including the constructor. However, parent constructors in PHP do not get called automatically, but in one case: when the child class doesn’t define a constructor, PHP calls the parent constructor automatically.

<?php
class X {
    private $property;

    public function __construct($argument) {
          $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

class Y extends X {}

$object = new Y(1);
// X::__construct was called
?>

This is where things get interesting: when the child class defines its own constructor, it does not automatically inherit the parent constructor. Instead, the child class has full control over the construction process, and the parent constructor will only be called if the child explicitly calls it.

<?php
class X {
    private $property;

    public function __construct($argument) {
          $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

class Y extends X {
    public function __construct($argument) {
          echo __METHOD__." was called\n";
    }
}

$object = new Y(1);
// Y::__construct was called
?>

In this example, the Y class does not call call the parent class constructor, and only the local constructor is called. The parent class is not initialized at all, in particular the property $property.

To do so, the child constructor must call the parent constructor explicitly. Heuristically, the parent must be called first, so as to ensure all needed is set up, before the child object can use specific features. Yet, when the parent and the child are well decoupled, that doesn’t impact much the constructor.

<?php
class X {
    private $property;

    public function __construct($argument) {
          $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

class Y extends X {
    public function __construct($argument) {
       parent::__construct($arguments);
          echo __METHOD__." was called\n";
    }
}

$object = new Y(1);
// X::__construct was called
// Y::__construct was called
?>

Fatal Error: Cannot Call Constructor

From that situation, it is easy to conclude that calling the parent constructor is a duty for the child: even if it has no impact, it is safer to relay the call to the parent.

But PHP now needs a concrete method to call, and constructors are not compulsory.

<?php
class X {
  private $p = 2;
    // no constructor
}

class Y extends X {
    private $property;

    public function __construct($argument) {
       parent::__construct();
       $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

$object = new Y(1);
//Fatal error: Uncaught Error: Cannot call constructor
?>

In this scenario, PHP throws a fatal error: Cannot call constructor. This is quite confusing, at first, since there is actually a constructor in Y. This happens because the Y class defines a constructor, but the parent class X doesn’t. When PHP tries to find the parent constructor, it doesn’t exist.

That problem is not the case when there is a grand-parent class, with an explicit constructor. Any existing parent constructor will prevent the Fatal error.

<?php
class W {
    public function __construct($argument) {
          echo __METHOD__." was called\n";
    }
}

class X extends W {
    // no constructor
}

class Y extends X {
    private $property;

    public function __construct($argument) {
       parent::__construct();
       $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

$object = new Y(1);
// W::__construct was called
// Y::__construct was called
?>

Must call the parent constructor

Interestingly, some PHP native classes exhibit a behavior where they throw a LogicException if a child class doesn’t explicitly call the parent constructor.

Consider the SplFileObject class, which is used to provide a OOP syntax to files. When extended, it requires a proper setup of the file path by the parent class. When SplFileObject is extended and lacks the call to the parent constructor, a LogicException is thrown at execution time:

<?php

class MyFile extends \SplFileObject
{
    public function __construct()
    {
    }

    public function fgets(): string
    {
            // It actually doesn't use the file
            return "MyFile";
    }
}

$cl = new MyFile();
try {
    $cl->fgets();
} catch (\Error $e) {
    var_dump($e);
}
?>

This native behavior ensures that the necessary setup is always performed correctly.

The Difficulty of Managing Parent Constructors

While PHP’s constructor inheritance model is straightforward, it introduces subtle problems in more complex scenarios. One of the primary issues is determining whether the parent class even has a constructor to call.

It is difficult to setup a coding convention without prior knowledge of the parent hierarchy (the family?):

  • always call the parent constructor, and end up coding empty constructors
  • only call the parent constuctor when needed, and forget one that need it
    • Static code analysis may help with that.

Furthermore, when dealing with frameworks where there is no full control over the parent class, for example, extending a third-party class, it may be hard to know when to call the parent constructor. This introduces a certain level of uncertainty into the design, potentially leading to bugs that are identified at execution time only.

And, finally, not calling the parent is also a valid strategy when creating mocks or unplugging existing code. Cases are rare to short-circuit the parent constructor, but do exist.

A Potential Solution: Default Empty Constructor

An idea to alleviate this issue would be to have PHP provide an empty constructor by default, similar to how it works with properties. In cases where a child class explicitly calls a parent class that does not define a constructor, PHP could automatically provide an empty constructor that does nothing. With inheritance, the parent __construct() may be in a grand-parent, though it is already handled that way today.

This way, a child class could still call parent::__construct() without worrying about whether or not the parent class has a constructor.

Here’s an illustration:

<?php
class X {
    // no constructor
}

class Y extends X {
    private $property;

    public function __construct($argument) {
       parent::__construct();  // does nothing
       $this->property = $argument;
          echo __METHOD__." was called\n";
    }
}

$object = new Y(1);
// Y::__construct was called
?>

In this case, PHP would simply use an empty constructor in X if it wasn’t explicitly defined, thus preventing errors and simplifying the inheritance model.

Conclusion

PHP’s handling of constructors in the context of inheritance can be tricky. When a child class overrides a parent constructor, it must explicitly call the parent’s constructor if any. Otherwise, a fatal error or unexpected behavior may arise. Additionally, some PHP classes enforce constructor calls through exceptions, adding another layer of complexity.

In more complex applications or frameworks, the uncertainty of whether to call a parent constructor—especially when dealing with third-party code—can introduce extra code burden. A default empty constructor mechanism could potentially make this process smoother, but for now, developers need to be diligent in understanding when and why to invoke a parent constructor.