Exploring new data types
One thing any entry-level PHP developer learns is which data types PHP has available and how to use them. The basic data types include int
(integer), float
, bool
(Boolean), and string
. Complex data types include array
and object
. In addition, there are other data types such as NULL
and resource
. In this section, we discuss a few new data types introduced in PHP 8, including union types and mixed types.
Important note
It's extremely important not to confuse a data type with a data format. This section describes data types. A data format, on the other hand, would be a way of representing data used as part of a transmission or for storage. Examples of a data format would include XML, JavaScript Object Notation (JSON), and YAML Ain't Markup Language (YAML).
Union types
Unlike other data types such as int
or string
, it's important to note that there is no data type explicitly called union. Rather, when you see a reference to union types, what is meant is that PHP 8 introduces a new syntax that allows you to specify a union of types, instead of just one. Let's now have a look at the generic syntax for union types.
Union type syntax
The generic syntax for union types is as follows:
function ( type|type|type $var) {}
In place of type
, you would supply any of the existing data types (for example, float
or string
). There are a few restrictions, however, which for the most part make complete sense. This table summarizes the more important restrictions:
As you can see from this list of exceptions, defining a union type is primarily a matter of common sense.
Tip
Best practice: When using union types, type coercion (the process whereby PHP converts a data type internally to satisfy the requirements of the function) can be an issue if strict type checking is not enforced. Accordingly, it's a best practice to add the following at the top of any file where union types are used: declare(strict_types=1);
.
For more information, see the documentation reference here:
https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict
Union type example
For a simple illustration, let's return to the SingleChar
class used as an example in this chapter. One of the methods is colorAlloc()
. This method allocates a color from an image, leveraging the imagecolorallocate()
function. It accepts as arguments integer values that represent red, green, and blue.
For the sake of argument, let's say that the first argument could actually be an array representing three values—one each for red, green, and blue. In this case, the argument type for the first value cannot be int
otherwise, if an array were provided, an error would be thrown if strict type checking were to be turned on.
In earlier versions of PHP, the only solution would be to remove any type check from the first argument and to indicate that multiple types are accepted in the associated DocBlock. Here's how the method might appear in PHP 7:
/** * Allocates a color resource * * @param array|int $r * @param int $g * @param int $b] * @return int $color */ public function colorAlloc($r, $g = 0, $b = 0) { if (is_array($r)) { [$r, $g, $b] = $r; } return \imagecolorallocate($this->image, $r, $g, $b); }
The only indication of the data type for the first parameter, $r
, is the @param array|int $r
DocBlock annotation and the fact that there is no data type hint associated with that argument. In PHP 8, taking advantage of union types, notice the difference here:
#[description("Allocates a color resource")] #[param("int|array r")] #[int("g")] #[int("b")] #[returns("int")] public function colorAlloc( int|array $r, int $g = 0, int $b = 0) { if (is_array($r)) { [$r, $g, $b] = $r; } return \imagecolorallocate($this->image, $r, $g, $b); }
In the preceding example, in addition to the presence of attribute
that indicates the first argument can accept either an array
or an int
type, in the method signature itself, the int|array
union type makes this choice clear.
mixed type
mixed
is another new type introduced in PHP 8. Unlike a union type, mixed
is an actual data type that represents the ultimate union of types. It's used to indicate that any and all data types are accepted. In a certain sense, PHP already has this facility: simply omit the data type altogether, and it's an implied mixed
type!
Tip
You will see references to a mixed
type in the PHP documentation. PHP 8 formalizes this representation by making it an actual data type.
Why use a mixed type?
Hold for a second—you might be thinking at this point: why bother using a mixed
type at all? To put your mind at ease, this is an excellent question, and there is no compelling reason to use this type.
However, by using mixed
in a function or method signature, you clearly signal your intention for the use of this parameter. If you were to simply leave the data type blank, other developers later using or reviewing your code might think that you forgot to add the type. At the very least, they will be uncertain of the nature of the untyped argument.
The effect of a mixed type on inheritance
As a mixed
type represents the ultimate example of widening, it can be used to widen the data type definition when one class extends from another. Here is an example using a mixed
type, illustrating this principle:
- First, we define the parent class with the more restrictive data type of
object
, as follows:// /repo/ch01/php8_mixed_type.php declare(strict_types=1); class High { const LOG_FILE = __DIR__ . '/../data/test.log'; protected static function logVar(object $var) { $item = date('Y-m-d') . ':' . var_export($var, TRUE); return error_log($item, 3, self::LOG_FILE); } }
- Next, we define a
Low
class that extendsHigh
, as follows:class Low extends High { public static function logVar(mixed $var) { $item = date('Y-m-d') . ':' . var_export($var, TRUE); return error_log($item, 3, self::LOG_FILE); } }
Note in the
Low
class that the data type for thelogVar()
method has been widened intomixed
. - Finally, we create an instance of
Low
and execute it with test data. As you can see from the results shown in the following code snippet, everything works fine:if (file_exists(High::LOG_FILE)) unlink(High::LOG_FILE) $test = [ 'array' => range('A', 'F'), 'func' => function () { return __CLASS__; }, 'anon' => new class () { public function __invoke() { return __CLASS__; } }, ]; foreach ($test as $item) Low::logVar($item); readfile(High::LOG_FILE);
Here is the output from the preceding example:
2020-10-15:array ( 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', )2020-10-15:Closure::__set_state(array( ))2020-10-15:class@anonymous/repo/ch01/php8_mixed_type.php:28$1::__set_state(array())
The preceding code block logs a variety of different data types and then displays the contents of the log file. In the process, this shows us there are no inheritance issues in PHP 8 when a child class overrides a parent class method and substitutes a data type of mixed
in place of a more restrictive data type, such as object
.
Next, we have a look at using typed properties.
Tip
Best practice: Assign specific data types to all arguments when defining functions or methods. If a few different data types are acceptable, define a union type. Otherwise, if none of this applies, fall back to a mixed
type.
For information on union types, see this documentation page:
https://wiki.php.net/rfc/union_types_v2
For more information on a mixed
type, have a look here: https://wiki.php.net/rfc/mixed_type_v2.