Improving code using typed properties
In the first section of this chapter, Using constructor property promotion, we discussed how data types can be used to control the type of data supplied as arguments to functions or class methods. What this approach fails to do, however, is guarantee that the data type never changes. In this section, you will learn how assigning a data type at the property level provides stricter control over the use of variables in PHP 8.
What are typed properties?
This extremely important feature was introduced in PHP 7.4 and continues in PHP 8. Simply put, a typed property is a class property with a data type preassigned. Here is a simple example:
// /repo/ch01/php8_prop_type_1.php declare(strict_types=1) class Test { public int $id = 0; public int $token = 0; public string $name = ''; } $test = new Test(); $test->id = 'ABC';
In this example, if we attempt to assign a value representing a data type other than int
to $test->id
, a Fatal error
is thrown. Here is the output:
Fatal error: Uncaught TypeError: Cannot assign string to property Test::$id of type int in /repo/ch01/php8_prop_type_1.php:11 Stack trace: #0 {main} thrown in /repo/ch01/php8_prop_type_1.php on line 11
As you can see from the preceding output, a Fatal error
is thrown when the wrong data type is assigned to a typed property.
You have already been exposed to one form of property typing: constructor property promotion. All properties defined using constructor property promotion are automatically property typed!
Why is property typing important?
Typed properties is part of a general trend in PHP first seen in PHP 7. The trend is toward making language refinements that restrict and tighten the use of your code. This leads to better code, which means fewer bugs.
The following example illustrates the danger of relying solely upon property-type hinting to control the data type of properties:
// /repo/ch01/php7_prop_danger.php declare(strict_types=1); class Test { protected $id = 0; protected $token = 0; protected $name = ''; public function __construct( int $id, int $token, string $name) { $this->id = $id; $this->token = md5((string) $token); $this->name = $name; } } $test = new Test(111, 123456, 'Fred'); var_dump($test);
In the preceding example, notice in the __construct()
method that the $token
property is accidentally converted to a string. Here is the output:
object(Test)#1 (3) { ["id":protected]=> int(111) ["token":protected]=> string(32) "e10adc3949ba59abbe56e057f20f883e" ["name":protected]=> string(4) "Fred" }
Any subsequent code expecting $token
to be an integer might either fail or produce unexpected results. Now, have a look at the same thing in PHP 8 using typed properties:
// /repo/ch01/php8_prop_danger.php declare(strict_types=1); class Test { protected int $id = 0; protected int $token = 0; protected string $name = ''; public function __construct( int $id, int $token, string $name) { $this->id = $id; $this->token = md5((string) $token); $this->name = $name; } } $test = new Test(111, 123456, 'Fred'); var_dump($test);
Property typing prevents any change to the preassigned data type from occurring, as you can see from the output shown here:
Fatal error: Uncaught TypeError: Cannot assign string to property Test::$token of type int in /repo/ch01/php8_prop_danger.php:12
As you can see from the preceding output, a Fatal error
is thrown when the wrong data type is assigned to a typed property. This example demonstrates that not only does assigning a data type to a property prevent misuse when making direct assignments, but it also prevents misuse of the property inside class methods as well!
Property typing can lead to a reduction in code
Another beneficial side effect of introducing property typing to your code is a potential reduction in the amount of code needed. As an example, consider the current practice of marking properties with a visibility of private
or protected
, and then creating a series of get
and set
methods to control access (also called getters and setters).
Here is how that might appear:
- First, we define a
Test
class with protected properties, as follows:// /repo/ch01/php7_prop_reduce.php declare(strict_types=1); class Test { protected $id = 0; protected $token = 0; protected $name = '';o
- Next, we define a series of
get
andset
methods to control access to the protected properties, as follows:public function getId() { return $this->id; } public function setId(int $id) { $this->id = $id; public function getToken() { return $this->token; } public function setToken(int $token) { $this->token = $token; } public function getName() { return $this->name; } public function setName(string $name) { $this->name = $name; } }
- We then use the
set
methods to assign values, as follows:$test = new Test(); $test->setId(111); $test->setToken(999999); $test->setName('Fred');
- Finally, we display the results in a table, using the
get
methods to retrieve property values, as follows:$pattern = '<tr><th>%s</th><td>%s</td></tr>'; echo '<table width="50%" border=1>'; printf($pattern, 'ID', $test->getId()); printf($pattern, 'Token', $test->getToken()); printf($pattern, 'Name', $test->getName()); echo '</table>';
Here is how that might appear:
The main purpose achieved by marking properties as protected
(or private
) and by defining getters and setters is to control access. Often, this translates into a desire to prevent the property data type from changing. If this is the case, the entire infrastructure can be replaced by assigning property types.
Simply changing the visibility to public
alleviates the need for get
and set
methods; however, it does not prevent the property data from being changed! Using PHP 8 property types achieves both goals: it eliminates the need for get
and set
methods and also prevents the data type from being accidentally changed.
Notice here how much less code is needed to achieve the same results in PHP 8 using property typing:
// /repo/ch01/php8_prop_reduce.php declare(strict_types=1); class Test { public int $id = 0; public int $token = 0; public string $name = ''; } // assign values $test = new Test(); $test->id = 111; $test->token = 999999; $test->name = 'Fred'; // display results $pattern = '<tr><th>%s</th><td>%s</td></tr>'; echo '<table width="50%" border=1>'; printf($pattern, 'ID', $test->id); printf($pattern, 'Token', $test->token); printf($pattern, 'Name', $test->name); echo '</table>';
The preceding code example shown produces exactly the same output as the previous example and also achieves even better control over property data types. Using typed properties, in this example, we achieved a 50% reduction in the amount of code needed to produce the same result!
Tip
Best practice: Use typed properties whenever possible, except in situations where you explicitly want to allow the data type to change.