The last post talked about how PHP 8.0 makes PHP faster. Today, we'll look at a way that it will make you faster.
Perhaps the largest quality-of-life improvement to PHP 8.0 is Class constructor property promotion. The syntax has been borrowed largely from Hack, Facebook's PHP fork, although it exists in other languages like TypeScript and Kotlin with various syntaxes.
It's easiest to explain with an example. Consider your typical service class in a modern application:
<?php
class FormRenderer
{
private ThemeSystem $theme;
private UserRepository $users;
private ModuleSystem $modules;
public function __construct(ThemeSystem $theme, UserRepository $users, ModuleSystem $modules) {
$this->theme = $theme;
$this->users = $users;
$this->modules = $modules;
}
public function renderForm(Form $form): string
{
// ...
}
}
That class has three dependencies. Each one has to be named four times: once to declare the property, once for the constructor parameters, once when referencing the parameter, and once when referencing the property. That is ridiculously verbose, and while many IDEs have some code completion features to help, it's still a pain. I have known people to avoid classes and dependency injection purely because of that verbosity.
As of PHP 8.0, that can all be collapsed down to this:
<?php
class FormRenderer
{
public function __construct(
private ThemeSystem $theme,
private UserRepository $users,
private ModuleSystem $modules,
) { }
public function renderForm(Form $form): string
{
// ...
}
}
Moving the visibility marker into the constructor declaration tells PHP "this parameter is also a property, you should assign the passed value to it." The effect at runtime is exactly the same as the first example, but it requires one-fourth as much typing. That's a win.
(Also, see that trailing comma in the parameter list? This feature is exactly why it was added.)
It's still entirely possible to declare additional properties the traditional way, and the constructor can also still have a body. In this example the body is empty, but if there is more work to do in the constructor than "assign stuff to properties," that code can still be included and will execute after the PHP 8.0 constructor assignment (or "promotion") is done.
<?php
class FormRenderer
{
private User $currentUser;
public function __construct(
private ThemeSystem $theme,
private UserRepository $users,
private ModuleSystem $modules,
) {
$this->currentUser = $this->users->getActiveUser();
}
public function renderForm(Form $form): string
{
// ...
}
}
In practice, the vast majority of classes I've written in the last few years have had such "boring" constructors, which means property promotion will dramatically cut down the amount of typing needed in the future.
Valuing your data
Constructor property promotion is even more valuable on value objects. (Value objects and service objects should make up the vast majority of your classes; there are only a tiny few other good models in PHP.) Many value objects (or Data Transfer Objects, or various other names they go by) are in practice little more than structs: a collection of named properties with, possibly, getters and setters on them. In that case, the entire class can now be reduced to little more than a constructor:
<?php
Class MailMessage
{
public function __construct(
public string $to,
public string $subject,
public string $from,
public string $body,
public array $cc,
public array $bcc,
public string $attachmentPath,
) {}
}
Making the properties public may be uncomfortable for some. In general, I would argue that for an internal (not part of the API) object it's fine, but for an object that is part of a public API making a series of getter methods and making the properties private is worthwhile. It's still less typing than it was, and it's also faster and far more memory efficient than using arrays.
It's also entirely allowed to have only some constructor parameters promoted and not others. The ones that are not will behave the same as they always have.
As usual, there are a few caveats.
- The visibility keywords only apply for constructors; using them in any other function or method signature is an error.
- A property may not be defined both in the constructor signature and on its own. If the property needs more than can be included in the signature, do it the old style way. That still works fine.
- Properties cannot be typed as callable (for implementation complexity reasons we won't get into here), so neither can constructor promoted properties. They can be left untyped, however.
- Variadic arguments cannot be promoted, as that makes the types harder to juggle.
Once again, we have Nikita Popov to thank for this RFC, although the instigation was a blog post earlier this year by yours truly, so I'll claim a 0.1% credit.
We're in the home stretch. Stay tuned for next week where we discuss PHP 8.0's most contentious, and most anticipated, new feature.
You can try out pre-release copies of PHP 8.0 today on Platform.sh, with just a one-line change. Give it a whirl, and let us know what your favorite features are.