PHP OOP - Constructors

Last revision:

What are constructors

Constructors are special class methods that are automatically called when objects are created.

Classes do not HAVE to declare a constructor, but if they do, they can only do so once:  a class can only declare one constructor. While some programming languages support multiple constructors (called constructor overloading), PHP does not.

Constructors typically initialize objects

Constructors are typically used to initialize an object: allocate memory, set (default) property values, create a database connection, write entries in log files, and so on.

In PHP, constructors have a fixed name: they must be named __construct(). They must be public, and you can declare them with or without parameters.

Constructors don't return anything; they may call other methods and/or initialize properties, but have no return value.

Constructors without parameters

Let's rework the Reservation example from the Encapsulation unit:

Code
<?php

class Reservation {

   private $number_of_guests;

   public function __construct() {
     $this->number_of_guests = 0;
   }

   // Ensure $number is an integer number larger than zero
   // before setting the value.
   public function setNumberOfGuests($number) {
     if (is_int($number) && $number > 0) {
       $this->number_of_guests = $number;
       return TRUE;
     }
     else {
       return FALSE;
     }
   }

   // Return the number of guests.
   public function getNumberOfGuests() {
       return $this->number_of_guests;
   }

}

Compared to the previous version from the Encapsulation unit, getNumberOfGuests() has become simpler: we no longer need to make sure $number_of_guests has an appropriate value because:

  • thanks to the constructor,  $number_of_guests will always be at least 0
  • $number_of_guests is private, so after object creation, its value can only be set via  setNumberOfGuests(), which makes sure a proper value is set, or leaves it at 0

This example illustrates how keeping properties private, and providing public get/set methods can help ensure a class works as expected, and that developers don't start changing internal object values without knowing or considering the consequences.

That said, setNumberOfGuests() still needs to check that the provided value is a number and that it is higher than 0 because we don't know what $number might contain by mistake: a negative number, a zero, a decimal number, a string of text, nothing at all... we just don't know. So we have to make sure.

→ You can never trust what kind of values people will try to put in your objects, either on purpose or by mistake. As a developer it's your responsibility to make your code as robust as you can, and prevent (or at least gracefully handle) everything that can go wrong. Techniques and tools for robustness include encapsulation, error handling, type checking, and static analysis, which we'll cover later.

Constructors with parameters

Constructors can have one or more parameters, just like normal methods and functions. Their purpose is to give users a faster way to initialize an object.

Let's say you have a Mail class with 6 properties, and you have created getter and setter methods for each of them:

Code
<?php

Class Mail {

 private $from;
 private $to;
 private $subject;
 private $message;
 private $cc;
 private $bcc;

 public function getFrom() {
   // Code goes here.
 }

 public function getTo() {
   // Code goes here.
 }

 // Other getters here.

 public function setFrom($from) {
   // Code goes here.
 }

 public function setTo($to) {
   // Code goes here.
 }

 // Other setters here.

}

Without a constructor, here's how you use this class:

Code
<?php

$mail = new Mail();
$mail->setFrom("sarah@example.com");
$mail->setTo("ahmed@example.com");
$mail->setSubject("Order confirmation");
$mail->setMessage("Thank you, we have received your order.");

Now let's add a constructor that initializes the most often used mail properties:

Code
<?php

public function __construct($from, $to, $subject, $message) {
  $this->from = $from;
  $this->to = $to;
  $this->subject = $subject;
  $this->message = $message;
}

With this constructor in place we can (actually we now must) pass along our parameters when we create the object:

Code
<?php

$from = "sarah@example.com";
$to = "ahmed@example.com";
$subject = "Order confirmation";
$message = "Thank you, we have received your order.";

$mail = new Mail($to, $from, $subject, $message);

Instead of instantiating an empty object and then calling 4 setter methods, we can now do the same with one line of code.

One small disadvantage of this approach is that you can now NO LONGER instantiate a Mail object by just calling new Mail(). The constructor needs 4 parameters, and you can no longer instantiate Mail objects without them.

One way to overcome this problem is by using optional constructor parameters.

Optional constructor parameters

Constructors are methods just like any other method, and a method's parameters can be made optional.

You could, for example, decide to make constructor's $from parameter mandatory, and all the others optional:

Code
<?php

public function __construct($from, $to = NULL, $subject = NULL, $message = NULL) {
 $this->from = $from;
 $this->to = $to;
 $this->subject = $subject;
 $this->message = $message;
}

Here are a few ways to use this class:

Code
<?php

$from = "sarah@example.com";
$to = "ahmed@example.com";
$subject = "Order confirmation";
$message = "Thank you, we have received your order.";

$mail = new Mail($to, $from, $subject, $message);

or

Code
<?php

$mail = new Mail("sarah@example.com", "ahmed@example.com");
$mail->setSubject("Order confirmation");
$mail->setMessage("thank you for your order.");

or

Code
<?php

$mail = new Mail("sarah@example.com");
$mail->setTo("ahmed@example.com");
$mail->setSubject("Order confirmation");
$mail->setMessage("Message goes here");

Ultimately it's up to you as the developer to decide if you want to force your users to use parameters when constructing a Mail object, or give them the option to use one (with optional parameters), or no constructor (parameters) at all.

Constructor property promotion

Constructor property promotion was introduced in PHP 8, and gives you the option to use a shorter syntax to assign constructor parameters to object properties.

When a constructor parameter includes an access modifier, PHP will interpret it as both an object property and a constructor parameter, and assign the parameter value to the property.

The constructor body may be left empty or may contain other statements. Any additional statements will be executed after the parameter values have been assigned to the corresponding properties.

It is possible to mix and match promoted and not-promoted arguments, in any order.

Example

Let's look at the classic way of assigning constructor parameters to object properties, and then compare it with the newer, optional constructor property promotion syntax:

Using the classic approach

Code
<?php

Class Mail {

 private $from;
 private $to;
 private $subject;
 private $message;

 public function __construct($from, $to, $subject, $message) {
   $this->from = $from;
   $this->to = $to;
   $this->subject = $subject;
   $this->message = $message;
 }

}

Using constructor property promotion

Code
<?php

Class Mail {

 public function __construct(private $from, private $to, private $subject, private $message) {
 }

}

How does constructor parameter promotion work?

Every constructor parameter that is preceded by an access modifier (private, in this case) is automatically "promoted" to a property.

Remember, for normal functions and methods, providing access modifiers to parameters is invalid syntax and results in an error:

Code
<?php

// INVALID SYNTAX:

class Calculation {
 public function sum(public $a, public $b) {
 // ...
 }
}


// CORRECT SYNTAX:

class Calculation {

 public function sum($a, $b) {
 // ...
 }

}

The only time you are allowed to do this is during constructor parameter promotion:

Code
<?php

class Mail {

 public function __construct(private $from, private $to, private $subject, private $message) {
 }

}

Mixing promotable and non-promotable constructor parameters

In your constructor you are allowed to mix parameters that have to be promoted to properties with parameters that don't have to be promoted.

All parameters that have access modifiers will automatically be promoted to properties. Parameters that don't have access modifiers will not be promoted to properties, but you can still use them in your constructor for other reasons:
 

Code
<?php

class TableReservation {

 public function __construct(private $customer_name, private $seats, private $date, $agent_id) {
 }

}

The above example shows a class that represents restaurant table bookings.

The $customer_name, $seats, and $date are promotable properties because they have access modifiers.

The $agent_id also has to be provided to the constructor when instantiating an object, but it is not automatically promoted to property because it doesn't have an access modifier.

Empty constructor body or not?

In the previous examples you've seen that the constructor's body has remained empty when talking about constructor property promotion.

With constructor property promotion you no longer have to write code to assign parameters to properties, but you can still do other things in your constructor. If you do so, first the parameters you provided will be promoted to properties; afterwards the code in the constructor's body will be executed.

Example

Code
<?php

Class TableReservation {

 public function __construct(private $customer_name, private $seats, private $date, $agent_id) {
   
  // Code here to record that agent with $agent_id has entered a booking request.
 
}

}

In the above example, the first three parameters are promoted to properties; the last parameter $agent_id is used in the constructor for something else (creating a log entry).

In other words, with constructor property promotion you are allowed to keep the constructor body empty, but you don't have to.

Mandatory?

This constructor parameter promotion mechanism is optional. You can still choose to declare and initialize your class properties the classic way.

Summary

  • A constructor is a special class method named __construct() that is called automatically when new instances (objects) of that class are created.
  • Constructors are often used with parameters, and those parameter values are then often assigned to corresponding class properties.
  • PHP 8 introduced the option of constructor parameter promotion to automate this process and save developers time.
  • To automatically have PHP promote (some) constructor parameters to class properties:
    • no longer declare those properties explicitly
    • only declare them as constructor parameters, and add an access modifier for each parameter you want to automatically promote to property.
  • With constructor parameter promotion, you're allowed to leave the constructor body empty; the promotion to class properties will still happen.