PHP 8.0 Model Classes with constructor promotion

Guy Thomas
2 min readMay 10, 2023

Let's start with what I mean by “Model Class”. A “Model Class” is nothing more than a class used to describe some data. This could be a table in a database, or it could also be the request/response body of a rest API call.

In the past, I’ve created abstract classes to make model composition easier — e.g:

<?php

class AbstractModel {
public function __construct($data) {
if (is_array($data)) {
$data = (object) $data;
}
foreach (get_object_vars($data) as $key => $var) {
$this->$key = $var;
}
}
}

Then we can have a model that extends this — e.g. for a user record:

<?php

class User extends AbstractModel {
public string $firstname;
public string $lastname;
public int $age;
}

Then to create an instance of User, you could do something like:

<?php

// Obviously, this would come from a DB call, but it's hard-coded here for this article.
$dataFromDBRow = ['firstname' => 'Guy', 'lastname' => 'Thomas', 'age' => 45];
$user = new User($dataFromDBRow);

?>

With PHP 8, a new language feature called “constructor promotion” has changed this for the better.

Here’s how I’d write this now:

<?php

class User {
public function __construct(
public string $firstname,
public string $lastname,
public int $age
) {}
}
<?php

$dataFromDBRow = ['firstname' => 'Guy', 'lastname' => 'Thomas', 'age' => 21];
$user = new User(...$dataFromDBRow);

?>

OK, so it doesn’t look like much has changed. However, something important has changed, and that is that the User model class defines its properties and its construct signature in one go. This means I now get validation on the data going into my model for free at the point the model is instantiated.

Also, I don’t need the AbstractModel class that I used to use to populate my class instance with data. Now I can use the PHP 8 feature ‘named arguments’, which allows functions to accept arguments by name instead of just by order. The spread operator (…) unpacks my key value array into named arguments as follows:

$user = new User(...$dataFromDBRow);

This is much nicer than writing:

$user = new User(
firstname: $dataFromDBRow['firstname'],
lastname: $dataFromDBRow['lastname'],
age: $dataFromDBRow['age']
);

Or, if I didn’t want to use ‘named arguments’:

$user = new User(
$dataFromDBRow['firstname'],
$dataFromDBRow['lastname'],
$dataFromDBRow['age']
);

In conclusion, PHP 8 makes writing models much easier!

--

--