Chapter 8: Learning about PHP 8's Deprecated or Removed Functionality

This chapter walks you through functionality that has been deprecated or removed in PHP Hypertext Preprocessor 8 (PHP 8). This information is extremely important for any developer to know. Any code that uses removed functionality must be rewritten before an upgrade to PHP 8. Likewise, any deprecation is a clear signal to you that you must rewrite any code that depends upon such functionality, or risk problems in the future.

After you have read the material in this chapter and followed the example application code, you can detect and rewrite code that has been deprecated. You can also develop workarounds for functionality that has been removed and learn how to refactor code that uses removed functionality involving extensions. Another important skill you will learn from this chapter is how to improve application security by rewriting code depending on removed functions.

Topics covered in this chapter include the following:

  • Discovering what has been removed from the core
  • Examining core deprecations
  • Working with removed functionality in PHP 8 extensions
  • Dealing with deprecated or removed security-related functionality

Technical requirements

To examine and run the code examples provided in this chapter, the minimum recommended hardware is outlined here:

  • x86_64-based desktop PC or laptop
  • 1 gigabyte (GB) free disk space
  • 4 GB of random-access memory (RAM)
  • 500 kilobits per second (Kbps) or faster internet connection

In addition, you will need to install the following software:

  • Docker
  • Docker Compose

Please refer to the Technical requirements section of Chapter 1, Introducing New PHP 8 OOP Features, for more information on Docker and Docker Compose installation, as well as how to build the Docker container used to demonstrate the code explained in this book. In this book, we refer to the directory in which you restored the sample code for the book as /repo.

The source code for this chapter is located here:

https://github.com/PacktPublishing/PHP-8-Programming-Tips-Tricks-and-Best-Practices

We can now begin our discussion by examining the core functionality removed in PHP 8.

Discovering what has been removed from the core

In this section, we consider not only functions and classes that have been removed from PHP 8, but we will also have a look at usage that has been removed as well. We will then have a look at class methods and functions that still exist but no longer serve any useful purpose due to other changes in PHP 8. Knowing which functions have been removed is extremely important in order to protect against a potential code break following a PHP 8 migration.

Let's start by examining functions removed in PHP 8.

Examining functions removed in PHP 8

There are a number of functions in the PHP language that have only been retained thus far in order to maintain backward compatibility. However, maintenance of such functions drains resources away from core language development. Further, for the most part, such functions have been superseded by better programming constructs. Accordingly, there has been a slow process whereby such commands have been slowly dropped from the language as evidence has mounted that they are no longer being used.

Tip

The PHP core team occasionally runs statistical analysis on PHP repositories based on GitHub. In this way, they are able to determine the frequency of usage of the various commands in the PHP core.

The table shown next summarizes the functions that have been removed in PHP 8 and what to use in their place:

Table 8.1 – PHP 8 removed functions and suggested replacements

Table 8.1 – PHP 8 removed functions and suggested replacements

For the remainder of this section, we cover a few of the more important removed functions and give you suggestions on how to refactor your code to achieve the same results. Let's start by examining each().

Working with each()

each() was introduced in PHP 4 as a way of walking through an array, producing key/value pairs upon each iteration. The syntax and usage of each() is extremely simple and is oriented toward procedural usage. We'll show a short code example that demonstrates each() usage, as follows:

  1. In this code example, we first open a connection to a data file containing city data from the GeoNames (https://geonames.org) project, as follows:

    // /repo/ch08/php7_each.php

    $data_src = __DIR__

        . '/../sample_data/cities15000_min.txt';

    $fh       = fopen($data_src, 'r');

    $pattern  = "%30s : %20s ";

    $target   = 10000000;

    $data     = [];

  2. We then use the fgetcsv() function to pull a row of data into $line, and pack latitude and longitude information into a $data array. Note in the following code snippet that we filter out rows of data on cities with a population less than $target (in this case, less than 10 million):

    while ($line = fgetcsv($fh, '', " ")) {

        $popNum = $line[14] ?? 0;

        if ($popNum > $target) {

            $city = $line[1]  ?? 'Unknown';

            $data[$city] = $line[4]. ',' . $line[5];

        }

    }

  3. We then close the file handle and sort the array by city name. To present the output, we use each() to walk through the array, producing key/value pairs, where the city is the key, and latitude and longitude is the value. The code is illustrated in the following snippet:

    fclose($fh);

    ksort($data);

    printf($pattern, 'City', 'Latitude/Longitude');

    printf($pattern, '----', '--------------------');

    while ([$city, $latLon] = each($data)) {

        $city = str_pad($city, 30, ' ', STR_PAD_LEFT);

        printf($pattern, $city, $latLon);

    }

Here is the output as it appears in PHP 7:

root@php8_tips_php7 [ /repo/ch08 ]# php php7_each.php

                          City :   Latitude/Longitude

                          ---- : --------------------

                       Beijing :    39.9075,116.39723

                  Buenos Aires :  -34.61315,-58.37723

                         Delhi :    28.65195,77.23149

                         Dhaka :     23.7104,90.40744

                     Guangzhou :      23.11667,113.25

                      Istanbul :    41.01384,28.94966

                       Karachi :      24.8608,67.0104

                   Mexico City :   19.42847,-99.12766

                        Moscow :    55.75222,37.61556

                        Mumbai :    19.07283,72.88261

                         Seoul :      37.566,126.9784

                      Shanghai :   31.22222,121.45806

                      Shenzhen :    22.54554,114.0683

                    São Paulo :   -23.5475,-46.63611

                       Tianjin :   39.14222,117.17667

This code example won't work in PHP 8, however, because each() has been removed. The best practice is to move toward an object-oriented programming (OOP) approach: use ArrayIterator instead of each(). The next code example produces exactly the same results as previously but uses object classes instead of procedural functions:

  1. Instead of using fopen(), we instead create an SplFileObject instance. You'll also notice in the following code snippet that instead of creating an array, we create an ArrayIterator instance to hold the final data:

    // /repo/ch08/php8_each_replacements.php

    $data_src = __DIR__

        . '/../sample_data/cities15000_min.txt';

    $fh       = new SplFileObject($data_src, 'r');

    $pattern  = "%30s : %20s ";

    $target   = 10000000;

    $data     = new ArrayIterator();

  2. We then loop through the data file using the fgetcsv() method to retrieve a line and offsetSet() to append to the iteration, as follows:

    while ($line = $fh->fgetcsv(" ")) {

        $popNum = $line[14] ?? 0;

        if ($popNum > $target) {

            $city = $line[1]  ?? 'Unknown';

            $data->offsetSet($city, $line[4]. ',' .             $line[5]);

        }

    }

  3. Finally, we sort by key, rewind to the top, and loop while the iteration still has more values. We use key() and current() methods to retrieve key/value pairs, as follows:

    $data->ksort();

    $data->rewind();

    printf($pattern, 'City', 'Latitude/Longitude');

    printf($pattern, '----', '--------------------');

    while ($data->valid()) {

        $city = str_pad($data->key(), 30, ' ', STR_PAD_LEFT);

        printf($pattern, $city, $data->current());

        $data->next();

    }

This code example will actually work in any version of PHP, from PHP 5.1 up to and including PHP 8! The output is exactly as shown in the preceding PHP 7 output and is not duplicated here.

Let's now have a look at create_function().

Working with create_function()

Prior to PHP 5.3, the only way to assign a function to a variable was to use create_function(). Starting with PHP 5.3, the preferred approach is to define an anonymous function. Anonymous functions, although technically part of the procedural programming application programming interface (API), are actually instances of the Closure class, and thus also belong to the realm of OOP.

Tip

If the functionality you need can be condensed into a single expression, in PHP 8 you also have the option of using an arrow function.

When the function defined by create_function() was executed, PHP executed the eval() function internally. The result of this architecture, however, is awkward syntax. Anonymous functions are equivalent in performance and more intuitive to use.

The following example demonstrates create_function() usage. The objective of this example is to scan a web-server access log and sort the results by Internet Protocol (IP) address:

  1. We start by recording the start time in microseconds. Later, we use this value to determine performance. Here's the code you'll need:

    // /repo/ch08/php7_create_function.php

    $start = microtime(TRUE);

  2. Next, use create_function() to define a callback that reorganizes the IP address at the start of each line into uniform segments of exactly three digits each. We need to do this in order to perform a proper sort (defined later). The first argument to create_function() is a string the represents the parameters. The second argument is the actual code to be executed. The code is illustrated in the following snippet:

    $normalize = create_function(

        '&$line, $key',

        '$split = strpos($line, " ");'

        . '$ip = trim(substr($line, 0, $split));'

        . '$remainder = substr($line, $split);'

        . '$tmp = explode(".", $ip);'

        . 'if (count($tmp) === 4)'

        . '    $ip = vsprintf("%03d.%03d.%03d.%03d", $tmp);'

        . '$line = $ip . $remainder;'

    );

    Note the extensive use of strings. This awkward syntax can easily lead to syntax or logic errors, as most code editors make no effort to interpret commands embedded in a string.

  3. Next, we define a sorting callback to be used with usort(), as follows:

    $sort_by_ip = create_function(

        '$line1, $line2',

        'return $line1 <=> $line2;' );

  4. We then pull the contents of the access log into an array using the file() function. We also move $sorted to a file to hold the sorted access log entries. The code is illustrated in the following snippet:

    $orig   = __DIR__ . '/../sample_data/access.log';

    $log    = file($orig);

    $sorted = new SplFileObject(__DIR__

        . '/access_sorted_by_ip.log', 'w');

  5. We are then able to normalize the IP address using array_walk() and perform a sort using usort(), as follows:

    array_walk($log, $normalize);

    usort($log, $sort_by_ip);

  6. Finally, we write the sorted entries to the alternate log file and display the time difference between start and stop, as follows:

    foreach ($log as $line) $sorted->fwrite($line);

    $time = microtime(TRUE) - $start;

    echo "Time Diff: $time ";

We are not showing the completed alternate access log as it's far too lengthy to be included in the book. Instead, here are a dozen lines pulled out from the middle of the listing to give you an idea of the output:

094.198.051.136 - - [15/Mar/2021:10:05:06 -0400]    "GET /courses HTTP/1.0" 200 21530

094.229.167.053 - - [21/Mar/2021:23:38:44 -0400]

   "GET /wp-login.php HTTP/1.0" 200 34605

095.052.077.114 - - [10/Mar/2021:22:45:55 -0500]

   "POST /career HTTP/1.0" 200 29002

095.103.103.223 - - [17/Mar/2021:15:48:39 -0400]

   "GET /images/courses/php8_logo.png HTTP/1.0" 200 9280

095.154.221.094 - - [25/Mar/2021:11:43:52 -0400]

   "POST / HTTP/1.0" 200 34546

095.154.221.094 - - [25/Mar/2021:11:43:52 -0400]

   "POST / HTTP/1.0" 200 34691

095.163.152.003 - - [14/Mar/2021:16:09:05 -0400]

   "GET /images/courses/mongodb_logo.png HTTP/1.0" 200 11084

095.163.255.032 - - [13/Apr/2021:15:09:40 -0400]

   "GET /robots.txt HTTP/1.0" 200 78

095.163.255.036 - - [18/Apr/2021:01:06:33 -0400]

   "GET /robots.txt HTTP/1.0" 200 78

In PHP 8, to accomplish the same task, we define anonymous functions instead of using create_function(). Here is how the rewritten code example might appear in PHP 8:

  1. Again, we start by recording the start time, as with the PHP 7 code example just described. Here's the code you'll need to accomplish this:

    // /repo/ch08/php8_create_function.php

    $start = microtime(TRUE);

  2. Next, we define a callback that normalizes the IP address into four blocks of three digits each. We use exactly the same logic as in the previous example; however, this time, we define commands in the form of an anonymous function. This takes advantage of code editor helpers, and each line is viewed by the code editor as an actual PHP command. The code is illustrated in the following snippet:

    $normalize = function (&$line, $key) {

        $split = strpos($line, ' ');

        $ip = trim(substr($line, 0, $split));

        $remainder = substr($line, $split);

        $tmp = explode(".", $ip);

        if (count($tmp) === 4)

            $ip = vsprintf("%03d.%03d.%03d.%03d", $tmp);

        $line = $ip . $remainder;

    };

    Because each line in the anonymous function is treated exactly as if you were defining a normal PHP function, you are less likely to have typos or syntax errors.

  3. In a similar manner, we define the sort callback in the form of an arrow function, as follows:

    $sort_by_ip = fn ($line1, $line2) => $line1 <=> $line2;

The remainder of the code example is exactly the same as described earlier and is not shown here. Likewise, the output is exactly the same. The performance time is also approximately the same.

We now turn our attention to money_format().

Working with money_format()

The money_format() function, first introduced in PHP 4.3, is designed to display monetary values using international currencies. If you are maintaining an international PHP-based website that has any financial transactions, you might be affected by this change after a PHP 8 update.

The latter was introduced in PHP 5.3, and should thus not cause your code to break. Let's have a look at a simple example involving money_format() and how it can be rewritten to work in PHP 8, as follows:

  1. We first assign an amount to a $amt variable. We then set the monetary locale to en_US (United States, or US) and echo the value using money_format(). We use the %n format code for national formatting, followed by the %i code for international rendering. In the latter case, the International Organization for Standardization (ISO) currency code (US Dollars, or USD) is displayed. The code is illustrated in the following snippet:

    // /repo/ch08/php7_money_format.php

    $amt = 1234567.89;

    setlocale(LC_MONETARY, 'en_US');

    echo "Natl: " . money_format('%n', $amt) . " ";

    echo "Intl: " . money_format('%i', $amt) . " ";

  2. We then change the monetary locale to de_DE (Germany) and echo the same amount in both national and international formats, as follows:

    setlocale(LC_MONETARY, 'de_DE');

    echo "Natl: " . money_format('%n', $amt) . " ";

    echo "Intl: " . money_format('%i', $amt) . " ";

Here is the output in PHP 7.1:

root@php8_tips_php7 [ /repo/ch08 ]# php php7_money_format.php

Natl: $1,234,567.89

Intl: USD 1,234,567.89

Natl: 1.234.567,89 EUR

Intl: 1.234.567,89 EUR

You might note from the output that money_format() did not render the Euro symbol, only the ISO code (EUR). It did, however, properly format the amounts, using a comma for the thousands separator, a period for the decimal separator for the en_US locale, and the reverse for the de_DE locale.

A best practice is to replace any usage of money_format() with NumberFormatter::formatCurrency(). Here is the preceding example, rewritten to work in PHP 8. Please note that the same example will also work in any version of PHP from 5.3 onward! We'll proceed as follows:

  1. First, we assign the amount to $amt and create a NumberFormatter instance. In creating this instance, we supply arguments that indicate the locale and type of number—in this case, currency. We then use the formatCurrency() method to produce the national representation of this amount, as illustrated in the following code snippet:

    // /repo/ch08/php8_number_formatter_fmt_curr.php

    $amt = 1234567.89;

    $fmt = new NumberFormatter('en_US',

        NumberFormatter::CURRENCY );

    echo "Natl: " . $fmt->formatCurrency($amt, 'USD') . " ";

  2. In order to produce the ISO currency code—in this case, USD—we need to use the setSymbol() method. Otherwise, the default is to produce the $ currency symbol instead of the USD ISO code. We then use the format() method to render the output. Note the trailing space after USD in the following code snippet. This is to prevent the ISO code from running into the number when echoed!:

    $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL,'USD ');

    echo "Intl: " . $fmt->format($amt) . " ";

  3. We then format the same amount using the de_DE locale, as follows:

    $fmt = new NumberFormatter( 'de_DE',

        NumberFormatter::CURRENCY );

    echo "Natl: " . $fmt->formatCurrency($amt, 'EUR') . " ";

    $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, 'EUR');

    echo "Intl: " . $fmt->format($amt) . " ";

Here is the output from PHP 8:

root@php8_tips_php8 [ /repo/ch08 ]#

php php8_number_formatter_fmt_curr.php

Natl: $1,234,567.89

Intl: USD 1,234,567.89

Natl: 1.234.567,89 €

Intl: 1.234.567,89 EUR

As you can see from the output, the comma decimal is reversed between the en_US and de_DE locales, as expected. You also see that both the currency symbols, as well as the ISO currency codes, are correctly rendered.

Now that you have an idea of how to replace money_format(), let's have a look at other programming code usage that has been removed in PHP 8.

Discovering other PHP 8 usage changes

There are a number of program code usage changes that you need to be aware of in PHP 8. We'll start with a look at two typecasts that are no longer allowed.

Removed typecasts

Developers often use forced typecasts in order to ensure the data type of a variable is appropriate for a particular usage. As an example, when processing a HyperText Markup Language (HTML) form submission, for the sake of argument, let's say one of the form elements represents a monetary amount. A quick and easy way to sanitize this data element is to typecast it to a float data type, as follows:

$amount = (float) $_POST['amount'];

However, rather than typecast to float, some developers prefer to use real or double. Interestingly, all three produce exactly the same result! In PHP 8, the typecast to real has been removed. If your code uses this typecast, a best practice is to change it to float.

The unset typecast has also been removed. The purpose of this typecast is to unset a variable. In the following code snippet, the value of $obj becomes NULL:

$obj = new ArrayObject();

/* some code (not shown) */

$obj = (unset) $obj;

A best practice in PHP 8 is to use either of the following:

$obj = NULL;

// or this:

unset($obj);

Let's now turn our attention to anonymous functions.

Changes in generating anonymous functions from class methods

In PHP 7.1, a new Closure::fromCallable() method was added that allows you to return a class method as a Closure instance (for example, an anonymous function). ReflectionMethod::getClosure() was also introduced and is also able to convert a class method into an anonymous function.

To illustrate this, we define a class that returns Closure instances able to perform hashing using different algorithms. We'll proceed as follows:

  1. First, we define a class and a public $class property, as follows:

    // /repo/src/Services/HashGen.php

    namespace Services;

    use Closure;

    class HashGen {

        public $class = 'HashGen: ';

  2. We then define a method that produces one of three callbacks, each designed to produce a different type of hash, as follows:

        public function makeHash(string $type) {

            $method = 'hashTo' . ucfirst($type);

            if (method_exists($this, $method))

                return Closure::fromCallable(

                    [$this, $method]);

            else

                return Closure::fromCallable(

                    [$this, 'doNothing']);

            }

        }

  3. Next, we define three different methods, each producing a different form of hash (not shown): hashToMd5(), hashToSha256(), and doNothing().
  4. In order to make use of the class, a calling program is devised that first includes the class file and creates an instance, as follows:

    // /repo/ch08/php7_closure_from_callable.php

    require __DIR__ . '/../src/Services/HashGen.php';

    use ServicesHashGen;

    $hashGen = new HashGen();

  5. The callback is then executed followed by var_dump() to view information about the Closure instance, as illustrated in the following code snippet:

    $doMd5 = $hashGen->makeHash('md5');

    $text  = 'The quick brown fox jumped over the fence';

    echo $doMd5($text) . " ";

    var_dump($doMd5);

  6. To end this example, we create and bind an anonymous class to the Closure instance, as illustrated in the following code snippet. Theoretically, the output display should start with Anonymous if the anonymous class were truly bound to $this:

    $temp = new class() { public $class = 'Anonymous: '; };

    $doMd5->bindTo($temp);

    echo $doMd5($text) . " ";

    var_dump($doMd5);

Here is the output of this code example running in PHP 8:

root@php8_tips_php8 [ /repo/ch08 ]#

php php7_closure_from_callable.php

HashGen: b335d9cb00b899bc6513ecdbb2187087

object(Closure)#2 (2) {

  ["this"]=>  object(ServicesHashGen)#1 (1) {

    ["class"]=>    string(9) "HashGen: "

  }

  ["parameter"]=>  array(1) {

    ["$text"]=>    string(10) "<required>"

  }

}

PHP Warning:  Cannot bind method ServicesHashGen::hashToMd5() to object of class class@anonymous in /repo/ch08/php7_closure_from_callable.php on line 16

HashGen: b335d9cb00b899bc6513ecdbb2187087

object(Closure)#2 (2) {

  ["this"]=>  object(ServicesHashGen)#1 (1) {

    ["class"]=>    string(9) "HashGen: "

  }

  ["parameter"]=>  array(1) {

    ["$text"]=>    string(10) "<required>"

  }

As you can see from the output, Closure simply ignored the attempt to bind another class and produced the expected output. In addition, a Warning message was generated, notifying you of the illegal bind attempt.

Let's now have a look at differences in comment handling.

Differences in comment handling

PHP has traditionally supported a number of symbols to denote comments. One such symbol is the hash sign (#). Due to the introduction of a new language construct known as Attributes, however, the hash sign immediately followed by an opening square bracket (#[) is no longer allowed to denote a comment. Support for the hash sign not immediately followed by an opening square bracket continues to serve as a comment delimiter.

Here is a brief example that works in PHP 7 and earlier, but not in PHP 8:

// /repo/ch08/php7_hash_bracket_ comment.php

test = new class() {

    # This works as a comment

    public $works = 'OK';

    #[ This does not work in PHP 8 as a comment]

    public $worksPhp7 = 'OK';

};

var_dump($test);

When we run this example in PHP 7, the output is as expected, as we can see here:

root@php8_tips_php7 [ /repo/ch08 ]#

php php7_hash_bracket_comment.php

/repo/ch08/php7_hash_bracket_comment.php:10:

class class@anonymous#1 (2) {

  public $works =>  string(2) "OK"

  public $worksPhp7 =>  string(2) "OK"

}

The same example in PHP 8, however, throws a fatal Error message, as illustrated here:

root@php8_tips_php8 [ /repo/ch08 ]#

php php7_hash_bracket_comment.php

PHP Parse error:  syntax error, unexpected identifier "does", expecting "]" in /repo/ch08/php7_hash_bracket_comment.php on line 7

Note that the example might have accidentally worked in PHP 8 if we had formulated the Attribute instance correctly. However, since the syntax used was in line with the syntax for a comment, the code failed.

Now that you have an idea about functions and usage that have been removed from PHP 8, we now examine core deprecations.

Examining core deprecations

In this section, we examine functions and usage that are deprecated in PHP 8. As the PHP language continues to mature, the PHP community is able to suggest to the PHP core development team that certain functions, classes, or even language usage should be removed. If two-thirds of the PHP development team vote in favor of a proposal, it's adopted for inclusion in a future release of the language.

In the case of functionality to be removed, it is not immediately taken out of the language. Instead, the function, class, method, or usage generates a Deprecation notice. This notice serves as a means to notify developers that this function, class, method, or usage will be disallowed in an as-yet-unspecified release of PHP. Accordingly, you must pay close attention to Deprecation notices. Failure to do so inevitably causes a code break in the future.

Tip

Starting with PHP 5.3, an official Request for Comments (RFC) process was initiated. The status of any proposal can be viewed at https://wiki.php.net/rfc.

Let's start by examining deprecated usage in parameter order.

Deprecated usage in parameter order

The term usage refers to how you call functions and class methods in your application code. You will discover that in PHP 8, older usages were allowed that are now considered bad practices. Understanding how PHP 8 enforces best practices in code usage helps you to write better code.

If you define a function or method with a mixture of mandatory and optional parameters, most PHP developers agree that the optional parameters should follow the mandatory parameters. In PHP 8, this usage best practice, if not followed, will result in a Deprecation notice. The rationale behind the decision to deprecate this usage is to avoid potential logic errors.

This simple example demonstrates this usage difference. In the following example, we define a simple function that accepts three arguments. Note that the $op optional parameter is sandwiched between two mandatory parameters, $a and $b:

// /repo/ch08/php7_usage_param_order.php

function math(float $a, string $op = '+', float $b) {

    switch ($op) {

        // not all cases are shown

        case '+' :

        default :

            $out = "$a + $b = " . ($a + $b);

    }

    return $out . " ";

}

If we echo the results of the add operation in PHP 7, there is no problem, as we can see here:

root@php8_tips_php7 [ /repo/ch08 ]#

php php7_usage_param_order.php

22 + 7 = 29

In PHP 8, however, there is a Deprecation notice, after which the operation is allowed to continue. Here is the output running in PHP 8:

root@php8_tips_php8 [ /repo/ch08 ]#

php php7_usage_param_order.php

PHP Deprecated:  Required parameter $b follows optional parameter $op in /repo/ch08/php7_usage_param_order.php on line 4

22 + 7 = 29

A Deprecation notice is a signal to the developer that this usage is considered a bad practice. In this case, a best practice would be to modify the function signature and list all mandatory parameters first.

Here is the rewritten example, acceptable to all versions of PHP:

// /repo/ch08/php8_usage_param_order.php

function math(float $a, float $b, string $op = '+') {

    // remaining code is the same

}

It's important to note that the following usage is still allowed in PHP 8:

function test(object $a = null, $b) {}

However, a better way to write the same function signature and still stay within the best practice of listing mandatory parameters first would be to rewrite this signature, as follows:

function test(?object $a, $b) {}

You now know about features removed from the PHP 8 core. Let's now have a look at removed functionality in PHP 8 extensions.

Working with removed functionality in PHP 8 extensions

In this section, we will have a look at removed functionality in PHP 8 extensions. This information is extremely important in order to avoid writing code that does not work in PHP 8. Further, an awareness of removed functionality helps you prepare existing code for a PHP 8 migration.

The following table summarizes removed functionality in extensions:

Table 8.2 – Functions removed from PHP 8 extensions

Table 8.2 – Functions removed from PHP 8 extensions

The preceding table provides a useful list of removed functions. Use this list to check against your existing code prior to a PHP 8 migration.

Let's now have a look at a potentially serious change to the mbstring extension.

Discovering mbstring extension changes

The mbstring extension has had two major changes that have massive potential for a backward-compatible code break. The first change is that a significant number of convenience aliases have been removed. The second major change is that support for the mbstring PHP function overloading capability has been removed. Let's first have a look at removed aliases.

Handling mbstring extension removed aliases

At the request of a number of developers, the PHP development team responsible for this extension graciously created a series of aliases, replacing mb_*() with mb*(). The exact rationale for granting this request has been lost in time. The burden of supporting such a massive number of aliases, however, wastes a tremendous amount of time every time the extension needs to be updated. Accordingly, the PHP development team voted to remove these aliases from the mbstring extension in PHP 8.

The following table provides a list of the aliases removed, as well as which function to use in their place:

Table 8.3 – Removed mbstring aliases

Table 8.3 – Removed mbstring aliases

Let's now have a look at another major change in string handling, pertaining to function overloading.

Working with mbstring extension function overloading

The function overloading feature allows standard PHP string functions (for example, substr()) to be silently replaced with their mbstring extension equivalences (for example, mb_substr()) if the php.ini directive mbstring.func_overload is assigned a value. The value assigned to this directive takes the form of a bitwise flag. Depending on the setting of this flag, the mail(), str*(), substr(), and split() functions could be subject to overloading. This feature was deprecated in PHP 7.2 and has been removed in PHP 8.

In addition, three mbstring extension constants related to this feature have also been removed. The three constants are MB_OVERLOAD_MAIL, MB_OVERLOAD_STRING, and MB_OVERLOAD_REGEX.

Tip

For more information on this feature, visit the following link:

https://www.php.net/manual/en/mbstring.overload.php

Any code that relies upon this functionality will break. The only way to avoid serious application failure is to rewrite the affected code and replace the silently substituted PHP core string functions with the intended mbstring extension functions.

In the following example, when mbstring.func_overload is enabled, PHP 7 reports the same values for both strlen() and mb_strlen():

// /repo/ch08/php7_mbstring_func_overload.php

$str  = '';

$len1 = strlen($str);

$len2 = mb_strlen($str);

echo "Length of '$str' using 'strlen()' is $len1 ";

echo "Length of '$str' using 'mb_strlen()' is $len2 ";

Here is the output in PHP 7:

root@php8_tips_php7 [ /repo/ch08 ]#

php php7_mbstring_func_overload.php

Length of '' using 'strlen()' is 45

Length of '' using 'mb_strlen()' is 15

root@php8_tips_php7 [ /repo/ch08 ]#

echo "mbstring.func_overload=7" >> /etc/php.ini

root@php8_tips_php7 [ /repo/ch08 ]#

php php7_mbstring_func_overload.php

Length of '' using 'strlen()' is 15

Length of '' using 'mb_strlen()' is 15

As you can see from the preceding output, once the mbstring.func_overload setting is enabled in the php.ini file, the results reported by strlen() and mb_strlen() are identical. This is because calls to strlen() are silently diverted to mb_strlen() instead. In PHP 8, the output (not shown) shows the results in both cases because the mbstring.func_overload setting is ignored. strlen() reports a length of 45, and mb_strlen() reports a length of 15.

To determine if your code is vulnerable to this backward-compatible break, check your php.ini file and see if the mbstring.func_overload setting has a value other than zero.

You now have an idea of where to look for potential code breaks pertaining to the mbstring extension. At this time, we turn our attention to changes in the Reflection extension.

Reworking code that uses Reflection*::export()

In the Reflection extension, a critical difference between PHP 8 and earlier versions is that all of the Reflection*::export() methods have been removed! The primary reason for this change is that simply echoing the Reflection object produces exactly the same results as using export().

If you have any code that currently uses any of the Reflection*::export() methods, you need to rewrite the code to use the __toString() method instead.

Discovering other deprecated PHP 8 extension functionality

In this section, we review a number of other significant deprecated functionality of note in PHP 8 extensions. First, we look at XML-RPC.

Changes to the XML-RPC extension

In versions of PHP prior to PHP 8, the XML-RPC extension was part of the core and always available. Starting with PHP 8, this extension has quietly been moved to the PHP Extension Community Library (PECL) (http://pecl.php.net/) and is no longer included in a standard PHP distribution by default. You can still install and use this extension. This change is easily confirmed by scanning the list of extensions in the PHP core here: https://github.com/php/php-src/tree/master/ext.

This will not present a backward-compatible code break. However, if you perform a standard PHP 8 installation and then migrate code that contains references to XML-RPC, your code might generate a fatal Error message and display a message that XML-RPC classes and/or functions are not defined. In this situation, simply install the XML-RPC extension using pecl or any other method normally used to install non-core extensions.

We now turn our attention to the DOM extension.

Changes made to the DOM extension

Since PHP 5, the Document Object Model (DOM) extension included a number of classes in its source code repository that were never implemented. In PHP 8, a decision was made to support DOM as a living standard (much like with HTML 5). A living standard is one that does not feature a set series of releases, but rather incorporates a continuous set of releases in an effort to keep up with web technology.

Tip

For more information on the proposed DOM living standard, have a look at this reference: https://dom.spec.whatwg.org/. For a good discussion on moving the PHP DOM extension onto a living standard basis, have a look at the Working with interfaces and traits section of Chapter 9, Mastering PHP 8 Best Practices.

Mainly due to the move toward a living standard, the following unimplemented classes have been removed from the DOM extension as of PHP 8:

  • DOMNameList
  • DOMImplementationList
  • DOMConfiguration
  • DOMError
  • DOMErrorHandler
  • DOMImplementationSource
  • DOMLocator
  • DOMUserDataHandler
  • DOMTypeInfo

These classes were never implemented, which means that your source code will not suffer any backward-compatibility breaks.

Let's now have a look at deprecations in the PHP PostgreSQL extension.

Changes made to the PostgreSQL extension

Aside from the deprecated functionality indicated in Table 8.5Deprecated functionality in PHP 8 extensions (shown later), you need to be aware that a couple of dozen aliases have been deprecated in the PHP 8 PostgreSQL extension. As with the aliases removed from the mbstring extension, the aliases we cover in this section are without underscore characters in the latter part of the alias name.

This table summarizes the aliases removed, and which functions to call in their place:

Table 8.4 – Deprecated functionality in PostgreSQL extension

Table 8.4 – Deprecated functionality in PostgreSQL extension

Please note that it's often difficult to find documentation on deprecations. In this case, you can consult the PHP 7.4 to PHP 8 migration guide here: https://www.php.net/manual/en/migration80.deprecated.php#migration80.deprecated.pgsql. Otherwise, you can always look in the C source code docblocks for @deprecation annotations here: https://github.com/php/php-src/blob/master/ext/pgsql/pgsql.stub.php. Here is an example:

/**

* @alias pg_last_error

* @deprecated

*/

function pg_errormessage(

    ?PgSqlConnection $connection = null): string {}

In the last part of this section, we summarize deprecated functionality in PHP 8 extensions.

Deprecated functionality in PHP 8 extensions

Finally, in order to make it easier for you to identify deprecated functionality in PHP 8 extensions, we provide a summary. The following table summarizes functionality deprecated in PHP 8 extensions:

Table 8.5 – Deprecated functionality in PHP 8 extensions

Table 8.5 – Deprecated functionality in PHP 8 extensions

We will use the PostgreSQL extension to illustrate deprecated functionality. Before running the code example, you need to perform a small bit of setup inside the PHP 8 Docker container. Proceed as follows:

  1. Open a command shell into the PHP 8 Docker container. From the command shell start PostgreSQL running using this command:

    /etc/init.d/postgresql start

  2. Next, switch to the su postgres user.
  3. The prompt changes to bash-4.3$. From here, type psql to enter the PostgreSQL interactive terminal.
  4. Next, from the PostgreSQL interactive terminal, issue the following set of commands to create and populate a sample database table:

    CREATE DATABASE php8_tips;

    c php8_tips;

    i /repo/sample_data/pgsql_users_create.sql

  5. Here is the entire chain of commands replayed:

    root@php8_tips_php8 [ /repo/ch08 ]# su postgres

    bash-4.3$ psql

    psql (10.2)

    Type "help" for help.

    postgres=# CREATE DATABASE php8_tips;

    CREATE DATABASE

    postgres=# c php8_tips;

    You are now connected to database "php8_tips"

        as user "postgres".

    php8_tips=# i /repo/sample_data/pgsql_users_create.sql

    CREATE TABLE

    INSERT 0 4

    CREATE ROLE

    GRANT

    php8_tips=# q

    bash-4.3$ exit

    exit

    root@php8_tips_php8 [ /repo/ch08 ]#

  6. We now define a short code example to illustrate the deprecation concepts just discussed. Notice in the following code example that we create a Structured Query Language (SQL) statement for a non-existent user:

    // /repo/ch08/php8_pgsql_changes.php

    $usr = 'php8';

    $pwd = 'password';

    $dsn = 'host=localhost port=5432 dbname=php8_tips '

          . ' user=php8 password=password';

    $db  = pg_connect($dsn);

    $sql = "SELECT * FROM users WHERE user_name='joe'";

    $stmt = pg_query($db, $sql);

    echo pg_errormessage();

    $result = pg_fetch_all($stmt);

    var_dump($result);

  7. Here is the output from the preceding code example:

    root@php8_tips_php8 [ /repo/ch08 ]# php php8_pgsql_changes.php Deprecated: Function pg_errormessage() is deprecated in /repo/ch08/php8_pgsql_changes.php on line 22

    array(0) {}

The two main things to notice from the output are the fact that pg_errormessage() is deprecated and that when no results are returned from a query, instead of a FALSE Boolean, an empty array is returned instead. Don't forget to stop the PostgreSQL database using this command:

/etc/init.d/postgresql stop

Now that you have an idea about deprecated functionality in the various PHP 8 extensions, we turn our attention to security-related deprecations.

Dealing with deprecated or removed security-related functionality

Any changes to functionality that affect security are extremely important to note. Ignoring these changes can very easily lead not only to breaks in your code but can also open your websites to potential attackers. In this section, we cover a variety of security-related changes in functionality present in PHP 8. Let's start the discussion by examining filters.

Examining PHP 8 stream-filter changes

PHP input/output (I/O) operations depend upon a subsystem known as streams. One of the interesting aspects of this architecture is the ability to append a stream filter to any given stream. The filters you can append can be either custom-defined stream filters, registered using stream_filter_register(), or predefined filters included with your PHP installation.

An important change of which you need to be aware is that in PHP 8, all mcrypt.* and mdecrypt.* filters have been removed, as well as the string.strip_tags filter. If you're not sure which filters are included in your PHP installation, you can either run phpinfo() or, better yet, stream_get_filters().

Here's the stream_get_filters() output running in the PHP 7 Docker container used with this book:

root@php8_tips_php7 [ /repo/ch08 ]#

php -r "print_r(stream_get_filters());"

Array (

    [0] => zlib.*

    [1] => bzip2.*

    [2] => convert.iconv.*

    [3] => mcrypt.*

    [4] => mdecrypt.*

    [5] => string.rot13

    [6] => string.toupper

    [7] => string.tolower

    [8] => string.strip_tags

    [9] => convert.*

    [10] => consumed

    [11] => dechunk

)

Here's the same command running in the PHP 8 Docker container:

root@php8_tips_php8 [ /repo/ch08 ]# php -r "print_r(stream_get_filters());"

Array (

    [0] => zlib.*

    [1] => bzip2.*

    [2] => convert.iconv.*

    [3] => string.rot13

    [4] => string.toupper

    [5] => string.tolower

    [6] => convert.*

    [7] => consumed

    [8] => dechunk

)

You'll notice from the PHP 8 output that the filters mentioned earlier have all been removed. Any code that uses any of the three filters listed will break after a PHP 8 migration. We now look at changes made to custom error handling.

Dealing with custom error-handling changes

Starting with PHP 7.0, most errors are now thrown. The exception to this are situations where the PHP engine is unaware that there is an error condition, such as running out of memory, exceeding the time limit, or if a segmentation fault occurs. Another exception is when the program deliberately triggers an error using the trigger_error() function.

Using the trigger_error() function to trap errors is not a best practice. A best practice would be to develop object-oriented code and place it inside a try/catch construct. However, if you are assigned to manage an application that does make use of this practice, there is a change in what gets passed to the custom error handler.

In versions prior to PHP 8, the data passed to the custom error handler's fifth argument, $errorcontext, was information about arguments passed to the function. In PHP 8, this argument is ignored. To illustrate the difference, have a look at the simple code example shown next. Here are the steps leading to this:

  1. First, we define a custom error handler, as follows:

    // /repo/ch08/php7_error_handler.php

    function handler($errno, $errstr, $errfile,

        $errline, $errcontext = NULL) {

        echo "Number : $errno ";

        echo "String : $errstr ";

        echo "File   : $errfile ";

        echo "Line   : $errline ";

        if (!empty($errcontext))

            echo "Context: "

                . var_export($errcontext, TRUE);

        exit;

    }

  2. We then define a function that triggers an error, sets the error handler, and invokes the function, as follows:

    function level1($a, $b, $c) {

        trigger_error("This is an error", E_USER_ERROR);

    }

    set_error_handler('handler');

    echo level1(TRUE, 222, 'C');

Here's the output running in PHP 7:

root@php8_tips_php7 [ /repo/ch08 ]#

php php7_error_handler.php

Number : 256

String : This is an error

File   : /repo/ch08/php7_error_handler.php

Line   : 17

Context:

array (

  'a' => true,

  'b' => 222,

  'c' => 'C',

)

As you can see from the preceding output, $errorcontext provides information about the arguments received by the function. In contrast, have a look at the output produced by PHP 8, shown here:

root@php8_tips_php8 [ /repo/ch08 ]#

php php7_error_handler.php

Number : 256

String : This is an error

File   : /repo/ch08/php7_error_handler.php

Line   : 17

As you can see, the output is identical except for a lack of information coming into $errorcontext. Let's now have a look at generating backtraces.

Dealing with changes to backtraces

Amazingly, before PHP 8, it was possible to change function arguments through a backtrace. This was possible because traces produced by either debug_backtrace() or Exception::getTrace() provided access to function arguments by reference.

This is an extremely bad practice because it allows your program to continue to operate despite potentially being in an error state. Further, when reviewing such code, it's not clear how the argument data is being provided. Accordingly, in PHP 8, this practice is no longer allowed. Both debug_backtrace() or Exception::getTrace() still operate as before. The only difference is that they no longer pass argument variables by reference.

Let's now have a look at changes to PDO error handling.

PDO error-handling mode default changed

For many years, novice PHP developers were mystified when their database applications using the PDO extension failed to produce results. The reason for this problem, in many cases, was a simple SQL syntax error that was not reported. This was due to the fact that in PHP versions prior to PHP 8, the default PDO error mode was PDO::ERRMODE_SILENT.

SQL errors are not PHP errors. Accordingly, such errors are not captured by normal PHP error handling. Instead, PHP developers had to specifically set the PDO error mode to either PDO::ERRMODE_WARNING or PDO::ERRMODE_EXCEPTION. PHP developers can now breathe a sigh of relief because, as of PHP 8, the PDO default error-handling mode is now PDO::ERRMODE_EXCEPTION.

In the following example, PHP 7 allows the incorrect SQL statement to silently fail:

// /repo/ch08/php7_pdo_err_mode.php

$dsn = 'mysql:host=localhost;dbname=php8_tips';

$pdo = new PDO($dsn, 'php8', 'password');

$sql = 'SELEK propertyKey, hotelName FUM hotels '

     . "WARE country = 'CA'";

$stm = $pdo->query($sql);

if ($stm)

    while($hotel = $stm->fetch(PDO::FETCH_OBJ))

        echo $hotel->name . ' ' . $hotel->key . " ";

else

    echo "No Results ";

In PHP 7, the only output is No Results, which is both deceptive and unhelpful. It might lead the developer to believe there were no results when in fact, the problem is a SQL syntax error.

The output running in PHP 8, shown here, is much more helpful:

root@php8_tips_php8 [ /repo/ch08 ]# php php7_pdo_err_mode.php

PHP Fatal error:  Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'SELEK propertyKey, hotelName FUM hotels WARE country = 'CA'' at line 1 in /repo/ch08/php7_pdo_err_mode.php:10

As you can see from the preceding PHP 8 output, the actual problem is clearly identified.

TIP

For more information about this change, see this RFC:

https://wiki.php.net/rfc/pdo_default_errmode

We next examine the track_errors php.ini directive.

Examining the track_errors php.ini setting

As of PHP 8, the track_errors php.ini directive has been removed. This means that the $php_errormsg automatically created variable is no longer available. For the most part, anything that caused an error prior to PHP 8 has now been converted to throw an Error message instead. However, for versions of PHP prior to PHP 8, you can still use the error_get_last() function instead.

In the following simple code example, we first set the track_errors directive on. We then call strpos() without any arguments, deliberately causing an error. We then rely on $php_errormsg to reveal the true error:

// /repo/ch08/php7_track_errors.php

ini_set('track_errors', 1);

@strpos();

echo $php_errormsg . " ";

echo "OK ";

Here is the output in PHP 7:

root@php8_tips_php7 [ /repo/ch08 ]# php php7_track_errors.php

strpos() expects at least 2 parameters, 0 given

OK

As you can see from the preceding output, $php_errormsg reveals the error, and the code block is allowed to continue. In PHP 8, of course, we are not allowed to call strpos() without any arguments. Here is the output:

root@php8_tips_php8 [ /repo/ch08 ]# php php7_track_errors.php PHP Fatal error:  Uncaught ArgumentCountError: strpos() expects at least 2 arguments, 0 given in /repo/ch08/php7_track_errors.php:5

As you can see, PHP 8 throws an Error message. A best practice is to use a try/catch block and trap any Error messages that might be thrown. You can also use the error_get_last() function. Here is a rewritten example that works in both PHP 7 and PHP 8 (output not shown):

// /repo/ch08/php8_track_errors.php

try {

    strpos();

    echo error_get_last()['message'];

    echo " OK ";

} catch (Error $e) {

    echo $e->getMessage() . " ";

}

You now have an idea about PHP functionality that has been deprecated or removed in PHP 8. That concludes this chapter.

Summary

In this chapter, you learned about deprecated and removed PHP functionality. The first section in this chapter dealt with core functionality that has been removed. The rationale for the change was explained, and you learned that the main reason for removing the functionality described in this chapter is not only to move you toward code that follows best practices but to have you use PHP 8 functionality that is faster and more efficient.

In the next section, you learned about deprecated functionality. The key theme in this section was to highlight how the deprecated functions, classes, and methods lead to bad practices and bug-ridden code. You also were given guidance on functionality that has been either removed or deprecated in a number of key PHP 8 extensions.

You learned how to locate and rewrite code that has been deprecated, and how to develop workarounds for functionality that has been removed. Another skill you learned in this chapter included how to refactor code using removed functionality involving extensions, and last, but not least, you learned how to improve application security by rewriting code depending on removed functions.

In the next chapter, you will learn how to gain greater efficiency and performance in your PHP 8 code by mastering best practices.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.131.110.169