PHP 8.0 Model Classes with constructor promotion
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!