Book Image

PHP 8 Programming Tips, Tricks and Best Practices

By : Doug Bierer
Book Image

PHP 8 Programming Tips, Tricks and Best Practices

By: Doug Bierer

Overview of this book

Thanks to its ease of use, PHP is a highly popular programming language used on over 78% of all web servers connected to the Internet. PHP 8 Programming Tips, Tricks, and Best Practices will help you to get up-to-speed with PHP 8 quickly. The book is intended for any PHP developer who wants to become familiar with the cool new features available in PHP 8, and covers areas where developers might experience backward compatibility issues with their existing code after a PHP 8 update. The book thoroughly explores best practices, and highlights ways in which PHP 8 enforces these practices in a much more rigorous fashion than its earlier versions. You'll start by exploring new PHP 8 features in the area of object-oriented programming (OOP), followed by enhancements at the procedural level. You'll then learn about potential backward compatible breaks and discover best practices for improving performance. The last chapter of the book gives you insights into PHP async, a revolutionary new way of programming, by providing detailed coverage and examples of asynchronous programming using the Swoole extension and Fibers. By the end of this PHP book, you'll not only have mastered the new features, but you'll also know exactly what to watch out for when migrating older PHP applications to PHP 8.
Table of Contents (17 chapters)
1
Section 1: PHP 8 Tips
6
Section 2: PHP 8 Tricks
11
Section 3: PHP 8 Best Practices

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:

Table 1.3 – Disallowed union types

Table 1.3 – Disallowed union types

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:

  1. 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);
        }
    }
  2. Next, we define a Low class that extends High, 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 the logVar()method has been widened into mixed.

  3. 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.