Chapter 7: Avoiding Traps When Using PHP 8 Extensions

One of the main strengths of the PHP: Hypertext Preprocessor (PHP) language is its extensions. Changes to the PHP language introduced in PHP 8 also require extension development teams to update their extensions at the same time. In this chapter, you will learn which major changes to extensions have been made and how to avoid traps when updating an existing application to PHP 8.

Once you have finished reviewing the sample code and topics presented in this chapter, you will be able to prepare any existing PHP code for migration to PHP 8. In addition to learning about the changes to the various extensions, you will also gain deep insight into their operation. This ability will allow you to make informed decisions when using extensions in PHP 8.

Topics covered in this chapter include the following:

  • Understanding the shift from resources to objects
  • Learning about changes to Extensible Markup Language (XML) extensions
  • Avoiding problems with the updated mbstring extension
  • Dealing with changes to the gd extension
  • Discovering changes to the Reflection extension
  • Working with other extension gotchas

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 a Docker container like the one used to demonstrate the code used in this book. In this book, we refer to the directory in which you restored the sample code for this book as /repo.

The source code for this chapter is located here:

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

We can now begin our discussion by examining the overall trend in PHP 8 toward objects rather than resources.

Understanding the shift from resources to objects

The PHP language has always had an uneasy relationship with resources. Resources represent a connection to an external system such as a file handle or a connection to a remote web service using the client URL (cURL) extension. One big problem with resources, however, is that they defy attempts at data typing. There's no way to distinguish a file handle from a cURL connection—they're both identified as resources.

In PHP 8, a major effort has taken place to move away from resources and to replace them with objects. One of the earliest examples of this trend prior to PHP 8 is the PDO class. When you create a PDO instance, it automatically creates a database connection. Starting with PHP 8, many functions that previously produced a resource now produce an object instance instead. Let's start our discussion by having a look at extension functions that now produce objects rather than resources.

PHP 8 extension resource-to-object migration

It's important for you to be aware of which functions in PHP 8 now produce objects instead of resources. The good news is that the extension functions have also been rewritten to accommodate an object as an argument rather than a resource. The bad news is that there is a potential backward-compatible code break where you initialize the resource (now object) and test for success using the is_resource() function.

The following table summarizes the functions that formerly returned resources but now return object instances:

Table 7.1 – PHP 8 resource-to-object migration

Table 7.1 – PHP 8 resource-to-object migration

Table 7.1 serves as a valuable guide to functions that now produce objects rather than resources. Consult this table before you migrate any existing applications to PHP 8. The next section gives you a detailed look at a potential backward-compatible code break and guidelines on how to adjust problematic code, before moving on to the benefits.

Potential code break involving is_resource()

A problem you might face is that code written prior to PHP 8 assumes the functions listed in Table 7.1 return a resource. Accordingly, clever developers were in the habit of using is_resource() as a test to see if a connection was successfully established.

Although this was an extremely sensible way to check, this technique now introduces a backward-compatible code break after a PHP 8 upgrade. The following example demonstrates this issue.

In this code example, a cURL connection is initialized for an external website. The next few lines test for success using the is_resource() function:

// //repo/ch07/php7_ext_is_resource.php

$url = 'https://unlikelysource.com/';

$ch  = curl_init($url);

if (is_resource($ch))

    echo "Connection Established "

else

    throw new Exception('Unable to establish connection');

The following output from PHP 7 shows success:

root@php8_tips_php7 [ /repo/ch07 ]#

php php7_ext_is_resource.php

Connection Established

The output of the same code running in PHP 8 is not successful, as we can see here:

root@php8_tips_php8 [ /repo/ch07 ]#

php php7_ext_is_resource.php

PHP Fatal error:  Uncaught Exception: Unable to establish connection in /repo/ch07/php7_ext_is_resource.php:9

The output from PHP 8 is deceptive in that a connection has been established! Because the program code is checking to see if the cURL handle is a resource, however, the code throws an Exception error. The reason for the failure is because a CurlHandle instance is returned rather than a resource.

In this situation, you can avoid a code break and have the code run successfully in both PHP 8 and any earlier PHP version by substituting !empty() (not empty) in place of is_resource(), as shown here:

// //repo/ch07/php8_ext_is_resource.php

$url = 'https://unlikelysource.com/';

$ch  = curl_init($url);

if (!empty($ch))

    echo "Connection Established ";

else

    throw new Exception('Unable to establish connection');

var_dump($ch);

Here is the output of the code example running in PHP 7:

root@php8_tips_php7 [ /repo/ch07 ]#

php php8_ext_is_resource.php

Connection Established

/repo/ch07/php8_ext_is_resource.php:11:

resource(4) of type (curl)

Here is the same code example running in PHP 8:

root@php8_tips_php8 [ /repo/ch07 ]#

php php8_ext_is_resource.php

Connection Established

object(CurlHandle)#1 (0) {}

As you can see from both outputs, the code runs successfully: in PHP 7, $ch is a resource. In PHP 8, $ch is a CurlHandle instance. Now that you understand the potential issue regarding is_resource(), let's have a look at the advantages that stem from this change.

Advantages of objects over resources

Prior to PHP 8, there was no way to provide a data type when passing a resource into or returning a resource out of a function or method. A clear advantage in producing objects rather than resources is that you can take advantage of object type hints.

To illustrate this advantage, imagine a set of HyperText Transfer Protocol (HTTP) client classes that implement a strategy software design pattern. One strategy involves using the cURL extension to send a message. Another strategy uses PHP streams, as follows:

  1. We start by defining an Http/Request class. The class constructor parses the given URL into its component parts, as illustrated in the following code snippet:

    // /repo/src/Http/Request.php

    namespace Http;

    class Request {

        public $url      = '';

        public $method   = 'GET';

        // not all properties shown

        public $query    = '';

        public function __construct(string $url) {

            $result = [];

            $parsed = parse_url($url);

            $vars   = array_keys(get_object_vars($this));

            foreach ($vars as $name)

                $this->$name = $parsed[$name] ?? '';

            if (!empty($this->query))

                parse_str($this->query, $result);

            $this->query = $result;

            $this->url   = $url;

        }

    }

  2. Next, we define a CurlStrategy class that uses the cURL extension to send a message. Note that the __construct() method uses constructor-argument promotion. You might also note that we provide a CurlHandle data type for the $handle argument. This is a tremendous advantage only available in PHP 8, and it ensures that any program creating an instance of this strategy class must provide the correct resource data type. The code is illustrated in the following snippet:

    // /repo/src/Http/Client/CurlStrategy.php

    namespace HttpClient;

    use CurlHandle;

    use HttpRequest;

    class CurlStrategy {

        public function __construct(

            public CurlHandle $handle) {}

  3. We then define the actual logic used to send the message, as follows:

        public function send(Request $request) {

            // not all code is shown

            curl_setopt($this->handle,

                CURLOPT_URL, $request->url);

            if (strtolower($request->method) === 'post') {

                $opts = [CURLOPT_POST => 1,

                    CURLOPT_POSTFIELDS =>

                        http_build_query($request->query)];

                curl_setopt_array($this->handle, $opts);

            }

            return curl_exec($this->handle);

        }

    }

  4. We can then do the same thing with a StreamsStrategy class. Again, note in the following code snippet how we can use a class as a constructor-argument type hint to ensure proper usage of the strategy:

    // /repo/src/Http/Client/StreamsStrategy.php

    namespace HttpClient;

    use SplFileObject;

    use Exception;

    use HttpRequest;

    class StreamsStrategy {

        public function __construct(

            public ?SplFileObject $obj) {}

        // remaining code not shown

  5. We then define a calling program that invokes both strategies and delivers the results. After setting up autoloading, we create a new HttpRequest instance, supplying an arbitrary URL as an argument, as follows:

    // //repo/ch07/php8_objs_returned.php

    require_once __DIR__

        . '/../src/Server/Autoload/Loader.php';

    $autoload = new ServerAutoloadLoader();

    use HttpRequest;

    use HttpClient{CurlStrategy,StreamsStrategy};

    $url = 'https://api.unlikelysource.com/api

        ?city=Livonia&country=US';

    $request = new Request($url);

  6. Next, we define a StreamsStrategy instance and send the request, as follows:

    $streams  = new StreamsStrategy();

    $response = $streams->send($request);

    echo $response;

  7. We then define a CurlStrategy instance and send the same request, as illustrated in the following code snippet:

    $curl     = new CurlStrategy(curl_init());

    $response = $curl->send($request);

    echo $response;

The output from both strategies is identical. Partial output is shown here (note that this example can only be used in PHP 8!):

root@php8_tips_php8 [ /repo/ch07 ]#

php php8_objs_returned.php

CurlStrategy Results:

{"data":[{"id":"1227826","country":"US","postcode":"14487","city":"Livonia","state_prov_name":"New York","state_prov_code":"NY","locality_name":"Livingston","locality_code":"051","region_name":"","region_code":"","latitude":"42.8135","longitude":"-77.6635","accuracy":"4"},{"id":"1227827","country":"US","postcode":"14488","city":"Livonia Center","state_prov_name":"New York","state_prov_code":"NY","locality_name":"Livingston","locality_code":"051","region_name":"","region_code":"","latitude":"42.8215","longitude":"-77.6386","accuracy":"4"}]}

Let's now have a look at another aspect of resource-to-object migration: its effect on iteration.

Traversable to IteratorAggregate migration

The Traversable interface was first introduced in PHP 5. It has no methods and was mainly created to allow objects to iterate using a simple foreach() loop. As PHP development continues to evolve, a need often arises to obtain the inner iterator. Accordingly, in PHP 8, many classes that formerly implemented Traversable now implement IteratorAggregate instead.

This doesn't mean that the enhanced classes no longer support the abilities inherent in the Traversable interface. Quite the contrary: IteratorAggregate extends Traversable! This enhancement means that you can now call getIterator() on an instance of any of the affected classes. This is potentially of immense benefit as prior to PHP 8, there was no way to access the inner iterator used in the various extensions. The following table summarizes the extensions and classes affected by this enhancement:

Table 7.2 – Classes that now implement IteratorAggregate instead of Traversable

Table 7.2 – Classes that now implement IteratorAggregate instead of Traversable

In this section, you were introduced to a significant change introduced in PHP 8: the trend toward using objects rather than resources. One of the advantages you learned is that objects allow you greater control as compared to resources. Another advantage covered in this section is that the movement in PHP 8 toward IteratorAggregate allows access to built-in iterators that were previously inaccessible.

We now turn our attention to changes to XML-based extensions.

Learning about changes to XML extensions

XML version 1.0 was introduced as a World Wide Web Consortium (W3C) specification in 1998. XML bears some resemblance to HyperText Markup Language (HTML); however, the main purpose of XML is to provide a way to format data that's readable to both machines and humans. One of the reasons why XML is still widely used is because it's easily understandable and does a stellar job at representing tree-structured data.

PHP provides a number of extensions that allow you to both consume and produce XML documents. There have been a few changes introduced to many of these extensions in PHP 8. For the most part, these changes are minor; however, it's important to be aware of these changes if you wish to be a well-rounded and informed PHP developer.

Let's first have a look at changes to the XMLWriter extension.

Examining XMLWriter extension differences

All XMLWriter extension procedural functions now accept and return XMLWriter objects instead of resources. If you have a look at the official PHP documentation for the XMLWriter extension, however, you'll see no references to the procedural functions. The reason for this is twofold: first, the PHP language is slowly moving away from discrete procedural functions in favor of object-oriented programming (OOP).

The second reason is that XMLWriter procedural functions are in reality just wrappers for XMLWriter OOP methods! As an example, xmlwriter_open_memory() is a wrapper for XMLWriter::openMemory(), xmlwriter_text() is a wrapper for XMLWriter::text(), and so forth.

If you are really set on using the XMLWriter extension using procedural programming techniques, xmlwriter_open_memory() creates an XMLWriter instance in PHP 8 rather than a resource. Likewise, all XMLWriter extension procedural functions work with XMLWriter instances rather than resources.

As with any of the extensions mentioned in this chapter that now produce object instances rather than resources, a potential backward-compatible break is possible. An example of such a break would be where you are using XMLWriter procedural functions and is_resource() to check to see if a resource has been created. We do not show you an example here, as the problem and the solution are the same as described in the previous section: use !empty() instead of is_resource().

It is a best practice to use the XMLWriter extension OOP application programming interface (API) instead of the procedural API. Fortunately, the OOP API has been available since PHP 5.1. Here is a sample XML file to be used in the next example:

<?xml version="1.0" encoding="UTF-8"?>

<fruit>

    <item>Apple</item>

    <item>Banana</item>

</fruit>

The example shown here works in both PHP 7 and 8. The purpose of this example is to use the XMLWriter extension to build the XML document shown previously. Here are the steps to accomplish this:

  1. We start by creating an XMLWriter instance. We then open a connection to shared memory and initialize the XML document type, as follows:

    // //repo/ch07/php8_xml_writer.php

    $xml = new XMLWriter();

    $xml->openMemory();

    $xml->startDocument('1.0', 'UTF-8');

  2. Following this, we use startElement() to initialize the fruit root node, and add a child node item that has a value of Apple, as follows:

    $xml->startElement('fruit');

    $xml->startElement('item');

    $xml->text('Apple');

    $xml->endElement();

  3. Next, we add another child node item that has a value of Banana, as follows:

    $xml->startElement('item');

    $xml->text('Banana');

    $xml->endElement();

  4. Finally, we close the fruit root node and end the XML document. The last command in the following code snippet displays the current XML document:

    $xml->endElement();

    $xml->endDocument();

    echo $xml->outputMemory();

Here is the output of the example program running in PHP 7:

root@php8_tips_php7 [ /repo/ch07 ]# php php8_xml_writer.php

<?xml version="1.0" encoding="UTF-8"?>

<fruit><item>Apple</item><item>Banana</item></fruit>

As you can see, the desired XML document is produced. If we run the same program in PHP 8, the results are identical (not shown).

We now turn our attention to changes to the SimpleXML extension.

Working with changes to the SimpleXML extension

The SimpleXML extension is object-oriented and is widely used. Accordingly, it's vital that you learn about a couple of significant changes made to this extension in PHP 8. The good news is that you won't have to rewrite any code! The even better news is that the changes substantially improve SimpleXML extension functionality.

As of PHP 8, the SimpleXMLElement class now implements the Standard PHP Library (SPL) RecursiveIterator interface and includes the functionality of the SimpleXMLIterator class. In PHP 8, SimpleXMLIterator is now an empty extension of SimpleXMLElement. This seemingly simple update assumes major significance when you consider that XML is often used to represent complex tree-structured data.

As an example, have a look at a partial view of a family tree for the House of Windsor, shown here:

Figure 7.1 – Example of complex tree-structured data

Figure 7.1 – Example of complex tree-structured data

If we were to model this using XML, the document might look like this:

<?xml version="1.0" encoding="UTF-8"?>

<!-- /repo/ch07/tree.xml -->

<family>

  <branch name="Windsor">

    <descendent gender="M">George V</descendent>

    <spouse gender="F">Mary of Treck</spouse>

    <branch name="George V">

      <descendent gender="M">George VI</descendent>

      <spouse gender="F">Elizabeth Bowes-Lyon</spouse>

      <branch name="George VI">

        <descendent gender="F">Elizabeth II</descendent>

        <spouse gender="M">Prince Philip</spouse>

        <branch name="Elizabeth II">

          <descendent gender="M">Prince Charles</descendent>

          <spouse gender="F">Diana Spencer</spouse>

          <spouse gender="F">Camilla Parker Bowles</spouse>

          <branch name="Prince Charles">

            <descendent gender="M">William</descendent>

            <spouse gender="F">Kate Middleton</spouse>

          </branch>

          <!-- not all nodes are shown -->

        </branch>

      </branch>

    </branch>

  </branch>

</family>

We then develop code to parse the tree. In versions of PHP before PHP 8, however, we need to define a recursive function in order to parse the entire tree. To do so, we'll follow these next steps:

  1. We start by defining a recursive function that displays the descendant's name and spouse (if any), as illustrated in the following code snippet. This function also identifies the descendant's gender and checks to see if there are any children. If the latter is true, the function then calls itself:

    function recurse($branch) {

        foreach ($branch as $node) {

            echo $node->descendent;

            echo ($node->descendent['gender'] == 'F')

                 ? ', daughter of '

                 : ', son of ';

            echo $node['name'];

            if (empty($node->spouse)) echo " ";

            else echo ", married to {$node->spouse} ";

            if (!empty($node->branch))

                recurse($node->branch);

        }

    }

  2. We then create a SimpleXMLElement instance from the external XML file and call the recursive function, as follows:

    // //repo/ch07/php7_simple_xml.php

    $fn = __DIR__ . '/includes/tree.xml';

    $xml = simplexml_load_file($fn);

    recurse($xml);

    This code block works in both PHP 7 and PHP 8. Here is the output running in PHP 7:

    root@php8_tips_php7 [ /repo/ch07 ]# php php7_simple_xml.php

    George V, son of Windsor, married to Mary of Treck

    George VI, son of George V, married to Elizabeth Bowes-Lyon

    Elizabeth II, daughter of George VI, married to Philip

    Prince Charles, son of Elizabeth II, married to Diana Spencer

    William, son of Prince Charles, married to Kate Middleton

    Harry, son of Prince Charles, married to Meghan Markle

    Princess Anne, daughter of Elizabeth II, married to M.Phillips

    Princess Margaret, daughter of George VI, married to A.Jones

    Edward VIII, son of George V, married to Wallis Simpson

    Princess Mary, daughter of George V, married to H.Lascelles

    Prince Henry, son of George V, married to Lady Alice Montegu

    Prince George, son of George V, married to Princess Marina

    Prince John, son of George V

    In PHP 8, however, because SimpleXMLElement now implements RecursiveIterator, the code to produce the same results is simpler.

  3. As with the example shown earlier, we define a SimpleXMLElement instance from an external file. There is no need to define a recursive function, however— all we need to do is define a RecursiveIteratorIterator instance, as follows:

    // //repo/ch07/php8_simple_xml.php

    $fn = __DIR__ . '/includes/tree.xml';

    $xml = simplexml_load_file($fn);

    $iter = new RecursiveIteratorIterator($xml,

        RecursiveIteratorIterator::SELF_FIRST);

  4. After that, all we need is a simple foreach() loop, with the same internal logic as in the preceding example. There's no need to check to see if a branch node exists, nor is there a need for recursion—that's taken care of by the RecursiveIteratorIterator instance! The code you'll need is illustrated here:

    foreach ($iter as $branch) {

        if (!empty($branch->descendent)) {

            echo $branch->descendent;

            echo ($branch->descendent['gender'] == 'F')

                 ? ', daughter of '

                 : ', son of ';

            echo $branch['name'];

            if (empty($branch->spouse)) echo " ";

            else echo ", married to {$branch->spouse} ";

        }

    }

The output from this code example running in PHP 8 is shown here. As you can see, the output is exactly the same:

root@php8_tips_php8 [ /repo/ch07 ]# php php8_simple_xml.php

George V, son of Windsor, married to Mary of Treck

George VI, son of George V, married to Elizabeth Bowes-Lyon

Elizabeth II, daughter of George VI, married to Philip

Prince Charles, son of Elizabeth II, married to Diana Spencer

William, son of Prince Charles, married to Kate Middleton

Harry, son of Prince Charles, married to Meghan Markle

Princess Anne, daughter of Elizabeth II, married to M.Phillips

Princess Margaret, daughter of George VI, married to A.Jones

Edward VIII, son of George V, married to Wallis Simpson

Princess Mary, daughter of George V, married to H.Lascelles

Prince Henry, son of George V, married to Lady Alice Montegu

Prince George, son of George V, married to Princess Marina

Prince John, son of George V

Important note

Please note as you run these examples using the Docker containers that the output shown here has been slightly modified to fit the page width.

Let's now have a look at other XML extension changes.

Understanding other XML extension changes

There have been a number of changes to other PHP 8 XML extensions. For the most part, the changes are minor and do not present a significant potential for a backward-compatible code break. However, we would be remiss if we did not address these additional changes. We recommend that you go through the remaining changes present in this subsection so that your awareness is raised. Using these XML extensions will empower you to troubleshoot application code that is behaving inconsistently after a PHP 8 update.

Changes to the libxml extension

The libxml extension leverages the Expat C library, providing XML parsing functions used by the various PHP XML extensions (https://libexpat.github.io/).

There is a new requirement for the version of libxml installed on your server. The minimum version when running PHP 8 must be 2.9.0 (or above). One of the major benefits of this updated requirement is to increase protection against XML external entity (XXE) processing attacks.

The recommended minimum version of libxml disables the ability of PHP XML extensions that rely upon the libxml extension to load external XML entities by default. This, in turn, reduces the need for costly and time-consuming extra steps to protect against XXE attacks.

Tip

For more information on XXE attacks, consult the Open Web Application Security Project (OWASP) using this link: https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing.

Changes to the XMLReader extension

The XMLReader extension complements the XMLWriter extension. Where the XMLWriter extension is designed to produce an XML document, the XMLReader extension is designed to read.

Two methods, XMLReader::open() and XMLReader::xml(), are now defined as static methods. You can still create XMLReader instances, but if you extend XMLReader and override either of these methods, be sure to declare them as static.

Changes to the XMLParser extension

The XMLParser extension is one of the oldest PHP XML extensions. Accordingly, it almost entirely consists of procedural functions rather than classes and methods. In PHP 8, however, this extension follows a trend toward producing objects rather than resources. Thus, when you run xml_parser_create() or xml_parser_create_ns(), an XMLParser instance is created rather than a resource.

As mentioned in the Potential code break involving is_resource() section, all you need to do is to replace any checks using is_resource() with !empty() instead. Another side effect of resource-to-object migration is to make redundant the xml_parser_free() function. To deactivate the parser, simply use the XmlParser object.

Now that you have an understanding of the changes associated with XML extensions, this will help you to more efficiently parse and otherwise manage XML data. By taking advantage of the new features mentioned in this section, you can produce code that's much more efficient and that offers better performance than was possible prior to PHP 8. Let's now have a look at the mbstring extension.

Avoiding problems with the updated mbstring extension

The mbstring extension was first introduced in PHP 4 and has been an active part of the language ever since. The original purpose of this extension was to provide support for the various Japanese character-encoding systems. Since that time, support for a wide variety of other encodings has been added—most notably, support for encodings based upon Universal Coded Character Set 2 (UCS-2), UCS-4, Unicode Transformation Format 8 (UTF-8), UTF-16, UTF-32, Shift Japanese Industrial Standards (SJIS), and International Organization for Standardization 8859 (ISO-8859), among others.

If you aren't sure which encodings are supported on your server, just run the mb_list_encodings() command, as follows (partial output shown):

root@php8_tips_php7 [ /repo/ch07 ]#

php -r "var_dump(mb_list_encodings());"

Command line code:1:

array(87) {

  ... only selected output is shown ...

  [14] =>  string(7) "UCS-4BE"

  [16] =>  string(5) "UCS-2"

  [19] =>  string(6) "UTF-32"

  [22] =>  string(6) "UTF-16"

  [25] =>  string(5) "UTF-8"

  [26] =>  string(5) "UTF-7"

  [27] =>  string(9) "UTF7-IMAP"

  [28] =>  string(5) "ASCII"

  [29] =>  string(6) "EUC-JP"

  [30] =>  string(4) "SJIS"

  [31] =>  string(9) "eucJP-win"

  [32] =>  string(11) "EUC-JP-2004"

  [76] =>  string(6) "KOI8-R"

  [78] =>  string(9) "ArmSCII-8"

  [79] =>  string(5) "CP850"

  [80] =>  string(6) "JIS-ms"

  [81] =>  string(16) "ISO-2022-JP-2004"

  [86] =>  string(7) "CP50222"

}

As you can see from the preceding output, in the PHP 7.1 Docker container we use for the book, 87 encodings are supported. In the PHP 8.0 Docker container (output not shown), 80 encodings are supported. Let's now have a look at the changes introduced in PHP 8, starting with the mb_str*() functions.

Discovering needle-argument differences in mb_str*() functions

In Chapter 6, Understanding PHP 8 Functional Differences, you learned how PHP 8 introduced changes to needle-argument handling in the core str*pos(), str*str(), and str*chr() functions. The two primary needle-argument differences are the ability to accept an empty needle argument and strict type checking to ensure the needle argument is a string only. In order to maintain consistency, PHP 8 introduces the same changes in the corresponding mb_str*() functions.

Let's have a look at empty needle-argument handling first.

mb_str*() function empty needle-argument handling

To keep the mbstring extension in line with this change to the core string functions, the following mbstring extension functions now allow an empty needle argument. It's important to note that this doesn't mean the argument can be omitted or is optional! What is meant by this change is that any value supplied as the needle argument can now also include what is considered empty. A good, quick way to learn what PHP considers to be empty can be found in the documentation for the empty() function (https://www.php.net/empty). Here is a list of mbstring functions that now allow an empty needle-argument value:

  • mb_strpos()
  • mb_strrpos()
  • mb_stripos()
  • mb_strripos()
  • mb_strstr()
  • mb_stristr()
  • mb_strrchr()
  • mb_strrichr()

    Tip

    Each of the eight mbstring extension functions mentioned here exactly parallels its core PHP counterpart function. For more information on these functions, have a look at this reference documentation: https://www.php.net/manual/en/ref.mbstring.php.

The short code example that follows illustrates empty needle handling in the eight aforementioned functions. Here are the steps leading to this:

  1. First, we initialize a multi-byte text string. In the following example, this is a Thai language translation of The quick brown fox jumped over the fence. The needle argument is set to NULL, and an array of functions to test is initialized:

    // /repo/ch07/php8_mb_string_empty_needle.php

    $text   = 'สุนัขจิ้งจอกสีน้ำตาลกระโดดข้ามรั้วอย่างรวดเร็ว';

    $needle = NULL;

    $funcs  = ['mb_strpos',   'mb_strrpos', 'mb_stripos',

               'mb_strripos', 'mb_strstr', 'mb_stristr',

               'mb_strrchr',  'mb_strrichr'];

  2. We then define a printf() pattern, and loop through the functions to be tested. For each function call, we supply the text followed by an empty needle argument, as follows:

    $patt = "Testing: %12s : %s ";

    foreach ($funcs as $str)

        printf($patt, $str, $str($text, $needle));

The output from PHP 7 is shown here:

root@php8_tips_php7 [ /repo/ch07 ]#

php php8_mb_string_empty_needle.php

PHP Warning:  mb_strpos(): Empty delimiter in /repo/ch07/php8_mb_string_empty_needle.php on line 12

Testing:    mb_strpos :

Testing:   mb_strrpos :

PHP Warning:  mb_stripos(): Empty delimiter in /repo/ch07/php8_mb_string_empty_needle.php on line 12

Testing:   mb_stripos :

Testing:  mb_strripos :

PHP Warning:  mb_strstr(): Empty delimiter in /repo/ch07/php8_mb_string_empty_needle.php on line 12

Testing:    mb_strstr :

PHP Warning:  mb_stristr(): Empty delimiter in /repo/ch07/php8_mb_string_empty_needle.php on line 12

Testing:   mb_stristr :

Testing:   mb_strrchr :

Testing:  mb_strrichr :

As you can see, the output is blank, and, in some cases, a Warning message is issued. The output running in PHP 8 is radically different, as expected, as we can see here:

root@php8_tips_php8 [ /repo/ch07 ]#

php php8_mb_string_empty_needle.php

Testing:    mb_strpos : 0

Testing:   mb_strrpos : 46

Testing:   mb_stripos : 0

Testing:  mb_strripos : 46

Testing:    mb_strstr : สุนัขจิ้งจอกสีน้ำตาลกระโดดข้ามรั้วอย่างรวดเร็ว

Testing:   mb_stristr : สุนัขจิ้งจอกสีน้ำตาลกระโดดข้ามรั้วอย่างรวดเร็ว

Testing:   mb_strrchr :

Testing:  mb_strrichr :

It's interesting to note that when this code runs in PHP 8, an empty needle argument returns a value of integer 0 for mb_strpos() and mb_stripos(), and integer 46 for mb_strrpos() and mb_strripos(). In PHP 8, an empty needle argument is interpreted as either the beginning or end of the string in this case. The result for both mb_strstr() and mb_stristr() is the entire string.

mb_str*() function data type checking

To maintain alignment with the core str*() functions, the needle argument must be of type string in the corresponding mb_str*() functions. If you supply an American Standard Code for Information Interchange (ASCII) value instead of a string, the affected functions will now throw an ArgumentTypeError error. No example is shown in this subsection, as Chapter 6, Understanding PHP 8 Functional Differences, already provides an example of this difference in the core str*() functions.

Differences in mb_strrpos()

In earlier versions of PHP, you were allowed to pass a character encoding as a third argument to mb_strrpos() instead of an offset. This bad practice is no longer supported in PHP 8. Instead, you can either supply 0 as a third argument or consider using PHP 8 named arguments (discussed in Chapter 1, Introducing New PHP 8 OOP Features, in the Understanding named arguments section) to avoid having to supply a value as an optional parameter.

Let's now look at a code example that demonstrates the differences in handling between PHP 7 and PHP 8. Proceed as follows:

  1. We first define a constant to represent the character encoding we wish to use. A text string representing a Thai language translation for The quick brown fox jumped over the fence is assigned. We then use mb_convert_encoding() to ensure the correct encoding is used. The code is illustrated in the following snippet:

    // /repo/ch07/php7_mb_string_strpos.php

    define('ENCODING', 'UTF-8');

    $text    = 'สุนัขจิ้งจอกสีน้ำตาลกระโดดข้ามรั้วอย่างรวดเร็ว';

    $encoded = mb_convert_encoding($text, ENCODING);

  2. We then assign the Thai language translation of fence to $needle and echo the length of the string and the position of $needle in the text. We then invoke mb_strrpos() to find the last occurrence of $needle. Note in the following code snippet that we deliberately follow the bad practice of using the encoding as a third argument rather than an offset:

    $needle  = 'รั้ว';

    echo 'String Length: '

        . mb_strlen($encoded, ENCODING) . " ";

    echo 'Substring Pos: '

        . mb_strrpos($encoded, $needle, ENCODING) . " ";

The output of this code example works perfectly in PHP 7, as we can see here:

root@php8_tips_php7 [ /repo/ch07 ]#

php php7_mb_string_strpos.php

String Length: 46

Substring Pos: 30

As you can see from the preceding output, the length of the multi-byte string is 46, and the position of the needle is 30. In PHP 8, on the other hand, we end up with a fatal Uncaught TypeError message, as seen here:

root@php8_tips_php8 [ /repo/ch07 ]#

php php7_mb_string_strpos.php

String Length: 46

PHP Fatal error:  Uncaught TypeError: mb_strrpos(): Argument #3 ($offset) must be of type int, string given in /repo/ch07/php7_mb_string_strpos.php:14

As you can see from the PHP 8 output, the third argument for mb_strrpos() must be an offset value in the form of an integer. A simple way to rewrite this example would be to take advantage of PHP 8 named arguments. Here is the rewritten line of code:

echo 'Substring Pos: '

    . mb_strrpos($encoded, $needle, encoding:ENCODING) . " ";

The output is identical to the PHP 7 example and is not shown here. Let's now turn our attention to mbstring extension's regular expression (regex)-handling differences.

Examining changes to mb_ereg*() functions

The mb_ereg*() family of functions allows regex processing of strings encoded using multi-byte character sets. In contrast, the core PHP language provides the Perl Compatible Regular Expressions (PCRE) family of functions, with modern and more up-to-date functionality.

If you add a u (lowercase letter U) modifier to a regex pattern when using the PCRE functions, any UTF-8 encoded multi-byte character string is accepted. However, UTF-8 is the only multi-byte character encoding accepted. If you are dealing with other character encodings and wish to perform regex functionality, you will need to either convert to UTF-8 or use the mb_ereg*() family of functions. Let's now have a look at a number of changes to the mb_ereg*() family of functions.

Oniguruma library required in PHP 8

One change to this family of functions is in how your PHP installation is compiled. In PHP 8, your operating system must provide the libonig library. This library provides Oniguruma functionality. (See https://github.com/kkos/oniguruma for more information.) The older --with-onig PHP source-compile-configure option has been removed in favor of using pkg-config to detect libonig.

Changes to mb_ereg_replace()

Formerly, you were able to supply an integer as an argument to mb_ereg_replace(). This argument was interpreted as an ASCII code point. In PHP 8, such an argument is now typecast as string. If you need an ASCII code point, you need to use mb_chr() instead. As the typecast to string is done silently, there is a potential backward-compatible code break, in that you won't see any Notice or Warning messages.

The following program code example illustrates the differences between PHP 7 and PHP 8. We'll follow these next steps:

  1. First, we define the encoding to be used and assign the Thai translation of Two quick brown foxes jumped over the fence as a multi-byte string to $text. Next, we use mb_convert_encoding() to ensure that the proper encoding is used. We then set mb_ereg* to the chosen encoding, using mb_regex_encoding(). The code is illustrated in the following snippet:

    // /repo/ch07/php7_mb_string_strpos.php

    define('ENCODING', 'UTF-8');

    $text = 'สุนัขจิ้งจอกสีน้ำตาล 2 ตัวกระโดดข้ามรั้ว';

    $str  = mb_convert_encoding($text, ENCODING);

    mb_regex_encoding(ENCODING);

  2. We then call mb_ereg_replace() and supply as a first argument an integer value 50, and replace it with the string "3". Both the original and modified strings are echoed. You can view the code here:

    $mod1 = mb_ereg_replace(50, '3', $str);

    echo "Original: $str ";

    echo "Modified: $mod1 ";

Note that the first argument for mb_ereg_replace() should be a string, but we supply an integer instead. In versions of the mbstring extension prior to PHP 8, if an integer is supplied as the first argument, it's treated as an ASCII code point.

If we run this code example in PHP 7, the number 50 is interpreted as the ASCII code point value for "2", as expected, as we can see here:

root@php8_tips_php7 [ /repo/ch07 ]#

php php7_mb_string_ereg_replace.php

Original: สุนัขจิ้งจอกสีน้ำตาล 2 ตัวกระโดดข้ามรั้ว

Modified: สุนัขจิ้งจอกสีน้ำตาล 3 ตัวกระโดดข้ามรั้ว

As you can see from the preceding output, the number 2 is replaced by the number 3. In PHP 8, however, the number 50 is typecast into a string. As this source string doesn't contain the number 50, no replacements are made, as we can see here:

root@php8_tips_php8 [ /repo/ch07 ]#

php php7_mb_string_ereg_replace.php

Original: สุนัขจิ้งจอกสีน้ำตาล 2 ตัวกระโดดข้ามรั้ว

Modified: สุนัขจิ้งจอกสีน้ำตาล 2 ตัวกระโดดข้ามรั้ว

The danger here is that if your code relies upon this silent interpretation process, your application might either fail or exhibit inconsistent behavior. You'll also note a lack of Notice or Warning messages. PHP 8 relies upon the developer to supply the correct arguments!

The best practice, if you do actually need to use an ASCII code point, is to use mb_chr() to produce the desired search string. The modified code example might look like this:

$mod1 = mb_ereg_replace(mb_chr(50), '3', $str);

You now have an idea about what changed in the mbstring extension. Without this information, you could easily end up writing faulty code. Developers who are unaware of this information might end up making mistakes in PHP 8, such as assuming the mbstring aliases are still in place. Such mistaken understanding can easily cause hours of lost time tracking down errors in program code following a PHP 8 migration.

It's now time to have a look at another extension with major changes: the GD extension.

Dealing with changes to the GD extension

The GD extension is an image-manipulation extension that leverages the GD library. GD originally stood for GIF Draw. Oddly, the GD library had to withdraw support for the Graphics Interchange Format (GIF) after Unisys revoked the open source license for the compression technology used when generating GIFs. After 2004, however, the Unisys patent on this technology expired and GIF support was restored. As it stands today, the PHP GD extension offers support for the Joint Photographic Experts Group (JPEG or JPG), Portable Network Graphic (PNG), GIF, X BitMap (XBM), X PixMap (XPM), Wireless Bitmap (WBMP), WebP, and Bitmap (BMP) formats.

Tip

For more information on the GD library, see https://libgd.github.io/.

Let's now have a look at the impact of resource-to-object migration on the GD extension.

GD extension resource-to-object migration

As with other PHP extensions that previously used resources, the GD extension has also primarily migrated from resource to object. As mentioned in the PHP 8 extension resource-to-object migration section, all of the imagecreate*() functions now produce GdImage objects rather than resources.

For an example of how this might present a code break after a PHP 8 migration, run these examples in two different browser tabs (on your local computer) and compare the difference. First, we run the PHP 7 example using this URL: http://172.16.0.77/ch07/php7_gd_is_resource.php. Here is the result:

Figure 7.2 – PHP 7 GD image resource

Figure 7.2 – PHP 7 GD image resource

As you can see from the preceding output, a resource extension is identified, but there's no descriptive information. Now, let's run the PHP 8 example using this URL: http://172.16.0.88/ch07/php8_gd_is_resource.php. Here is the result:

Figure 7.3 – PHP 8 GD image object instance

Figure 7.3 – PHP 8 GD image object instance

The output from PHP 8 not only identifies the return type as a GdImage instance but also displays descriptive information below the image.

We now turn our attention to other GD extension changes.

GD extension compile flag changes

The GD extension not only leverages the GD library but a number of supporting libraries as well. These libraries are needed to provide support for the various graphics formats. Previously, when compiling a custom version of PHP from the source code, you needed to specify the location of the libraries for JPEG, PNG, XPM, and VPX formats. In addition, as compression is an important aspect in reducing the overall final file size, the location of ZLIB was needed as well.

When compiling PHP 8 from source, there are a number of significant configuration flag changes that were first introduced in PHP 7.4 and subsequently carried into PHP 8. The primary change is that you no longer need to specify the directory where libraries are located. PHP 8 now locates libraries using the pkg-config operating system equivalent.

The following table summarizes compile flag changes. These flags are used with the configure utility just prior to the actual compile process itself:

Table 7.3 – GD compile option changes

Table 7.3 – GD compile option changes

You will note from the table that for the most part, most --with-*-dir options are replaced with --with-*. Also, PNG and ZLIB support is now automatic; however, you must have libpng and zlib installed on your operating system.

We will now have a look at other minor changes to the GD extension.

Other GD extension changes

Aside from the major changes described in the previous section, a number of other minor changes have taken place, in the form of function signature changes and a new function. Let's start this discussion by having a look at the imagecropauto() function.

Here is the old function signature for imagecropauto():

imagecropauto(resource $image , int $mode = -1,

              float $threshold = .5 , int $color = -1 )

In PHP 8, the $image parameter is now of type GdImage. The $mode parameter now defaults to an IMG_CROP_DEFAULT predefined constant.

Another change affects the imagepolygon(), imageopenpolygon(), and imagefilledpolygon() functions. Here is the old function signature for imagepolygon():

imagepolygon(resource $image, array $points,

             int $num_points, int $color)

In PHP 8, the $num_points parameter is now optional. If omitted, the number of points is calculated as follows: count($points)/2. However, this means that the number of elements in the $points array must be an even number!

The last significant change is the addition of a new function, imagegetinterpolation(). Here is its function signature:

imagegetinterpolation(GdImage $image) : int

The return value is an integer that, in and of itself, isn't very useful. However, if you examine the documentation for the imagesetinterpolation() function (https://www.php.net/manual/en/function.imagesetinterpolation.php), you will see a list of interpolation method code along with an explanation.

You now have an idea of which changes were introduced in the GD extension. We next examine changes to the Reflection extension.

Discovering changes to the Reflection extension

The Reflection extension is used to perform introspection on objects, classes, methods, and functions, among other things. ReflectionClass and ReflectionObject produce information on a class or an object instance respectively. ReflectionFunction provides information on procedural-level functions. In addition, the Reflection extension has a set of secondary classes produced by the main classes mentioned just now. These secondary classes include ReflectionMethod, produced by ReflectionClass::getMethod(), ReflectionProperty, produced by ReflectionClass::getProperty(), and so forth.

You might wonder: Who uses this extension? The answer is: any application that needs to perform analysis on an external set of classes. This might include software that performs automated code generation, testing, or documentation generation. Classes that perform hydration (populating objects from arrays) also benefit from the Reflection extension.

Tip

We do not have enough room in the book to cover every single Reflection extension class and method. If you wish to get more information, please have a look at the documentation reference here: https://www.php.net/manual/en/book.reflection.php.

Let's now have a look at a Reflection extension usage example.

Reflection extension usage

We will now show a code example that demonstrates how the Reflection extension might be used to generate docblocks (a docblock is a PHP comment that uses a special syntax to denote the purpose of a method, its incoming parameters, and return value). Here are the steps leading to this:

  1. We first define a __construct() method that creates a ReflectionClass instance of the target class, as follows:

    // /repo/src/Services/DocBlockChecker.php

    namespace Services;

    use ReflectionClass;

    class DocBlockChecker {

        public $target = '';    // class to check

        public $reflect = NULL; // ReflectionClass instance

        public function __construct(string $target) {

            $this->target = $target;

            $this->reflect = new ReflectionClass($target);

        }

  2. We then define a check() method that grabs all the class methods, returning an array of ReflectionMethod instances, as follows:

        public function check() {

            $methods = [];

            $list = $this->reflect->getMethods();

  3. We then loop through all the methods and use getDocComment() to check to see if a docblock already exists, as follows:

          foreach ($list as $refMeth) {

              $docBlock = $refMeth->getDocComment();

  4. If a docblock does not already exist, we start a new one and then call getParameters(), which returns an array of ReflectionParameter instances, as illustrated in the following code snippet:

              if (!$docBlock) {

                  $docBlock = "/** * "

                      . $refMeth->getName() . " ";

                  $params = $refMeth->getParameters();

  5. If we do have parameters, we gather information for display, as follows:

                if ($params) {

                  foreach ($params as $refParm) {

                    $type = $refParm->getType()

                          ?? 'mixed';

                    $type = (string) $type;

                    $name = $refParm->getName();

                    $default = '';

                    if (!$refParm->isVariadic()

                     && $refParm->isOptional()) {

                      $default=$refParm->getDefaultValue(); }

                    if ($default === '') {

                      $default = "(empty string)"; }

                    $docBlock .= " * @param $type "

                      . "${$name} : $default ";

                  }

              }

  6. We then set the return type and assign the docblock to a $methods array that is then returned, as follows:

               if ($refMeth->isConstructor())

                   $return = 'void';

                else

                    $return = $refMeth->getReturnType()

                              ?? 'mixed';

                $docBlock .= " * @return $return ";

                $docBlock .= " */ ";

            }

            $methods[$refMeth->getName()] = $docBlock;

        }

        return $methods;

      }

    }

  7. Now the new docblock checking class is complete, we define a calling program, as shown in the following code snippet. The calling program targets a /repo/src/Php7/Reflection/Test.php class (not shown here). This class has a mixture of methods with parameters and return values:

    // //repo/ch07/php7_reflection_usage.php

    $target = 'Php7ReflectionTest';

    require_once __DIR__

        . '/../src/Server/Autoload/Loader.php';

    use ServerAutoloadLoader;

    use ServicesDocBlockChecker;

    |$autoload = new Loader();

    $checker = new DocBlockChecker($target);

    var_dump($checker->check());

The output of the calling program is shown here:

root@php8_tips_php7 [ /repo/ch07 ]#

php php7_reflection_usage.php

/repo/ch07/php7_reflection_usage.php:10:

array(4) {

  '__construct' =>  string(75)

"/**

* __construct

* @param PDO $pdo : (empty string)

* @return void

*/"

  'fetchAll' =>  string(41)

"/**

* fetchAll

* @return Generator

*/"

  'fetchByName' =>  string(80)

"/**

* fetchByName

* @param string $name : (empty string)

* @return array

*/"

  'fetchLastId' =>  string(38)

"/**

* fetchLastId

* @return int

*/"

}

As you can see, this class forms the basis of potentially automatic documentation or a code-generation application.

Let's now have a look at Reflection extension improvements.

Learning about Reflection extension improvements

There have also been a number of improvements to the Reflection extension that might be important for you to know about. Please bear in mind that, although there are a limited number of developers who use the Reflection extension, you might one day find yourself in a situation where you are working with code that uses this extension. If you notice odd behavior after a PHP 8 upgrade, the material covered in this section gives you a head start in the troubleshooting process.

ReflectionType modifications

The ReflectionType class is now abstract in PHP 8. When you use the ReflectionProperty::getType() or ReflectionFunction::getReturnType() methods, you might note that a ReflectionNamedType instance is returned. This change does not affect the normal functioning of your program code, unless you are relying upon a ReflectionType instance being returned. However, ReflectionNamedType extends ReflectionType, so any instanceof operations will not be affected.

It's also worth noting that the isBuiltIn() method has been moved from ReflectionType to ReflectionNamedType. Again, since ReflectionNamedType extends ReflectionType, this should not present any backward-compatible break in your current code.

ReflectionParameter::*DefaultValue* methods enhanced

In earlier versions of PHP, ReflectionParameter methods pertaining to default values were unable to reflect internal PHP functions. This has changed in PHP 8. The following ReflectionParameter methods are now also able to return default value information from internal functions:

  • getDefaultValue()
  • getDefaultValueConstantName()
  • isDefaultValueAvailable()
  • isDefaultValueConstant()

As you can see from the list, the method names are self-explanatory. We'll now show a code example that makes use of these enhancements. Here are the steps leading to this:

  1. First, we define a function that accepts a ReflectionParameter instance and returns an array with the parameter name and default value, as follows:

    // /repo/ch07/php8_reflection_parms_defaults.php

    $func = function (ReflectionParameter $parm) {

        $name = $parm->getName();

        $opts = NULL;

        if ($parm->isDefaultValueAvailable())

            $opts = $parm->getDefaultValue();

  2. Next, we define a switch() statement to sanitize the options, as follows:

        switch (TRUE) {

            case (is_array($opts)) :

                $tmp = '';

                foreach ($opts as $key => $val)

                    $tmp .= $key . ':' . $val . ',';

                $opts = substr($tmp, 0, -1);

                break;

            case (is_bool($opts)) :

                $opts = ($opts) ? 'TRUE' : 'FALSE';

                break;

            case ($opts === '') :

                $opts = "''";

                break;

            default :

                $opts = 'No Default';

        }

        return [$name, $opts];

    };

  3. We then determine which function to reflect and pull its parameters. In the following example, we reflect setcookie():

    $test = 'setcookie';

    $ref = new ReflectionFunction($test);

    $parms = $ref->getParameters();

  4. We then loop through the array of ReflectionParameter instances and produce an output, as follows:

    $patt = "%18s : %s ";

    foreach ($parms as $obj)

        vprintf($patt, $func($obj));

Here is the output running in PHP 7:

root@php8_tips_php7 [ /repo/ch07 ]#

php php8_reflection_parms_defaults.php

Reflecting on setcookie

         Parameter : Default(s)

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

              name : No Default

             value : No Default

           expires : No Default

              path : No Default

            domain : No Default

            secure : No Default

          httponly : No Default

The result is always No Default because, in PHP 7 and earlier, the Reflection extension is unable to read defaults for internal PHP functions. The PHP 8 output, on the other hand, is much more accurate, as we can see here:

root@php8_tips_php8 [ /repo/ch07 ]#

php php8_reflection_parms_defaults.php

Reflecting on setcookie

         Parameter : Default(s)

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

              name : No Default

             value : ''

expires_or_options : No Default

              path : ''

            domain : ''

            secure : FALSE

          httponly : FALSE

As you can see from the output, the Reflection extension in PHP 8 is able to accurately report on internal function default values!

Let's now have a look at other Reflection extension changes.

Other Reflection extension changes

In PHP versions prior to PHP 8, ReflectionMethod::isConstructor() and ReflectionMethod::isDestructor() were unable to reflect magic methods defined in interfaces. In PHP 8, these two methods now return TRUE for the corresponding magic methods defined in interfaces.

When using the ReflectionClass::getConstants() or ReflectionClass::getReflectionConstants() methods, a new $filter parameter has now been added. The parameter allows you to filter the results by visibility level. Accordingly, the new parameter can accept any of the following newly added predefined constants:

  • ReflectionClassConstant::IS_PUBLIC
  • ReflectionClassConstant::IS_PROTECTED
  • ReflectionClassConstant::IS_PRIVATE

You now have an idea of how to use the Reflection extension and what to expect after a PHP 8 migration. It's time to have a look at a number of other extensions that saw changes in PHP 8.

Working with other extension gotchas

PHP 8 introduced a number of other noteworthy changes to several PHP extensions other than the ones already discussed in this chapter. As we have stressed time and again in this book, it's extremely important for your future career as a PHP developer to be aware of these changes.

Let's first have a look at changes to database extensions.

New database extension operating system library requirements

Any developer using MySQL, MariaDB, PostgreSQL, or PHP Data Objects (PDO) needs to be aware of new requirements for supporting operating system libraries. The following table summarizes the new minimum versions required in PHP 8:

Table 7.4 – PHP 8 database library requirements

Table 7.4 – PHP 8 database library requirements

As you can see from the preceding table, there are two main library changes. libpq affects both the PostgreSQL extension and the driver for the PDO extension. libmysqlclient is the library used by both the MySQL Improved (MySQLi) extension and as the MySQL driver for the PDO extension. It should also be noted that if you are using MariaDB, a popular open source version of MySQL, the new minimum MySQL library requirement applies to you as well.

Now that you are aware of database extension changes, we next turn our attention to the ZIP extension.

Reviewing changes to the ZIP extension

The ZIP extension is used to programmatically create and manage compressed archive files, leveraging the libzip operating system library. Other compression extensions exist, such as Zlib, bzip2, LZF, PHP Archive Format (phar), and Roshal Archive Compressed (RAR); however, none of the other extensions offers the rich range of functionality offered by the ZIP extension. Also, for the most part, the other extensions are special-purpose and are generally unsuitable for generic ZIP file management.

Let's first have a look at the most notable change to this extension.

Dealing with ZIP extension OOP migration

The biggest change to the ZIP extension is one that presents a potentially massive backward-compatible code break down the road. As of PHP 8, the procedural API (all procedural functions) has been deprecated! Although this does not affect any code at present, all ZIP extension functions will eventually be removed from the language.

The best practice is to migrate any ZIP extension procedural code over to the OOP API using the ZipArchive class. The following code example illustrates how to migrate from procedural code to object code, opening a test.zip file and producing a list of entries:

// /repo/ch07/php7_zip_functions.php

$fn  = __DIR__ . '/includes/test.zip';

$zip = zip_open($fn);

$cnt = 0;

if (!is_resource($zip)) exit('Unable to open zip file');

while ($entry = zip_read($zip)) {

    echo zip_entry_name($entry) . " ";

    $cnt++;

}

echo "Total Entries: $cnt ";

Here is the output running in PHP 7:

root@php8_tips_php7 [ /repo/ch07 ]#

php php7_zip_functions.php

ch07/includes/

ch07/includes/test.zip

ch07/includes/tree.xml

ch07/includes/test.png

ch07/includes/kitten.jpg

ch07/includes/reflection.html

ch07/php7_ext_is_resource.php

ch07/php7_gd_is_resource.php

... not all entries shown ...

ch07/php8_simple_xml.php

ch07/php8_xml_writer.php

ch07/php8_zip_oop.php

Total Entries: 27

As you can see from the preceding output, a total of 27 entries are found. (Also, note that not all ZIP file entries are shown.) If we try the same code example in PHP 8, however, we get a very different result, as we can see here:

root@php8_tips_php8 [ /repo/ch07 ]#

php php7_zip_functions.php

PHP Deprecated:  Function zip_open() is deprecated in /repo/ch07/php7_zip_functions.php on line 5

PHP Deprecated:  Function zip_read() is deprecated in /repo/ch07/php7_zip_functions.php on line 8

PHP Deprecated:  Function zip_entry_name() is deprecated in /repo/ch07/php7_zip_functions.php on line 9

ch07/includes/

Deprecated: Function zip_entry_name() is deprecated in /repo/ch07/php7_zip_functions.php on line 9

... not all entries shown ...

ch07/php8_zip_oop.php

PHP Deprecated:  Function zip_read() is deprecated in /repo/ch07/php7_zip_functions.php on line 8

Total Entries: 27

As you can see from the preceding PHP 8 output, the code example works, but a series of deprecation Notice messages are issued.

Here is how you need to write the same code example in PHP 8:

// /repo/ch07/php8_zip_oop.php

$fn  = __DIR__ . '/includes/test.zip';

$obj = new ZipArchive();

$res = $obj->open($fn);

if ($res !== TRUE) exit('Unable to open zip file');

for ($i = 0; $entry = $obj->statIndex($i); $i++) {

    echo $entry['name'] . " ";

}

echo "Total Entries: $i ";

The output (not shown) is exactly the same as for the previous example. Interestingly, the rewritten example also works in PHP 7! It's also worth noting that in PHP 8, you can get a count of the total number of entries (per directory) using ZipArchive::count(). You may also have noticed that to check to see if the ZIP archive is opened properly, in PHP 8 you can no longer use is_resource().

New ZipArchive class methods

In addition to resource-to-object migration, a number of improvements have been made to the ZipArchive class. One such improvement is that the following new methods have been added:

  • setMtimeName()
  • setMtimeIndex()
  • registerProgressCallback()
  • registerCancelCallback()
  • replaceFile()
  • isCompressionMethodSupported()
  • isEncryptionMethodSupported()

The method names are self-explanatory. Mtime refers to modification time.

New options for addGlob() and addPattern()

The ZipArchive::addGlob() and ZipArchive::addPattern() methods have a new set of options. These two methods are similar in that both are used to add files to the archive. The difference is that addGlob() uses the same file pattern as the core PHP glob() command, whereas addPattern() filters files using a regex. The new set of options is summarized here:

  • flags: Lets you combine the appropriate class constants using bitwise operators
  • comp_method: Specifies the compression method using any of the ZipArchive::CM_* constants as an argument
  • comp_flags: Specifies compression flags using the desired ZipArchive::FL_* constant(s)
  • enc_method: Lets you specify the desired character encoding (using any of the ZipArchive::FL_ENC_* flags)
  • enc_password: Lets you specify the encryption password if it is set for this ZIP archive

It's also worth mentioning here that the remove_path option prior to PHP 8 had to be a valid directory path. As of PHP 8, this option is a simple string that represents characters to be removed. This allows you to remove filename prefixes as well as undesired directory paths.

While we are still examining options, it's worth noting that two new encoding method class constants have been added: ZipArchive::EM_UNKNOWN and ZipArchive::EM_TRAD_PKWARE. Also, a new lastId property has been added so that you are able to determine the index value of the last ZIP archive entry.

Other ZipArchive method changes

In addition to the changes mentioned earlier, a few other ZipArchive methods have changed in PHP 8. In this section, we summarize other ZipArchive method changes, as follows:

  • ZipArchive::extractTo() previously used the current date and time for the modification time. As of PHP 8, this method restores the original file modification time.
  • ZipArchive::getStatusString() returns results even after ZipArchive::close() has been invoked.
  • ZipArchive::addEmptyDir(), ZipArchive::addFile(), and ZipArchive::addFromString() methods all have a new flags argument. You can use any of the appropriate ZipArchive::FL_* class constants, combined using bitwise operators.
  • ZipArchive::open() can now open an empty (zero-byte) file.

Now you have an idea of the changes and improvements that have been introduced to the ZIP extension, let's examine changes in the area of regular expressions.

Examining PCRE extension changes

The PCRE extension contains a number of functions designed to perform pattern matching using regular expressions. The term regular expression is commonly shortened to regex. A regex is a string that describes another string. Here are some changes to note in the PCRE extension.

Invalid escape sequences in patterns are no longer interpreted as literals. In the past, you could use an X modifier; however, that modifier is now ignored in PHP 8. Happily, to assist you with internal PCRE pattern-analysis errors, a new preg_last_error_msg() function has been added that returns a human-readable message when PCRE errors are encountered.

The preg_last_error() function allows you to determine whether or not an internal PCRE error occurred during pattern analysis. This function only returns an integer, however. Prior to PHP 8, it was up to the developer to look up the code and to figure out the actual error.

Tip

A list of error codes returned by preg_last_error() can be found here:

https://www.php.net/manual/en/function.preg-last-error.php#refsect1-function.preg-last-error-returnvalues

A brief code example follows that illustrates the aforementioned issues. Here are the steps leading to this:

  1. First, we define a function that performs a match and checks to see if any errors have occurred, as follows:

    $pregTest = function ($pattern, $string) {

        $result  = preg_match($pattern, $string);

        $lastErr = preg_last_error();

        if ($lastErr == PREG_NO_ERROR) {

            $msg = 'RESULT: ';

            $msg .= ($result) ? 'MATCH' : 'NO MATCH';

        } else {

            $msg = 'ERROR : ';

            if (function_exists('preg_last_error_msg'))

                $msg .= preg_last_error_msg();

            else

                $msg .= $lastErr;

        }

        return "$msg ";

    };

  2. We then create a pattern that deliberately contains a 8+ invalid escape sequence, as follows:

    $pattern = '/8+/';

    $string  = 'test 8';

    echo $pregTest($pattern, $string);

  3. Next, we define a pattern that deliberately causes PCRE to exceed its backtrace limit, as follows:

    $pattern = '/(?:D+|<d+>)*[!?]/';

    $string  = 'test ';

    echo $pregTest($pattern, $string);

Here is the output in PHP 7.1:

root@php8_tips_php7 [ /repo/ch07 ]# php php7_pcre.php

RESULT: MATCH

ERROR : 2

As you can see from the preceding output, the invalid pattern is treated as a literal value 8. Because 8 exists in the string, a match is considered found. As for the second pattern, the backtrace limit is exceeded; however, PHP 7.1 is unable to report the problem, forcing you to look it up.

The output in PHP 8 is quite different, as seen here:

root@php8_tips_php8 [ /repo/ch07 ]# php php7_pcre.php

PHP Warning:  preg_match(): Compilation failed: reference to non-existent subpattern at offset 1 in /repo/ch07/php7_pcre.php on line 5

ERROR : Internal error

ERROR : Backtrack limit exhausted

As you can see from the preceding output, PHP 8 produces a Warning message. You can also see that preg_last_error_msg() produces a useful message. Let's now have a look at the Internationalization (Intl) extension.

Working with Intl extension changes

The Intl extension consists of several classes that handle a number of application aspects that might change depending on the locale. The various classes handle such tasks as internationalized number and currency formatting, text parsing, calendar generation, time and date formatting, and character-set conversion, among other things.

The main change introduced to the Intl extension in PHP 8 is the following new date formats:

  • IntlDateFormatter::RELATIVE_FULL
  • IntlDateFormatter::RELATIVE_LONG
  • IntlDateFormatter::RELATIVE_MEDIUM
  • IntlDateFormatter::RELATIVE_SHORT

A code example follows, showing the new formats. Here are the steps leading to this:

  1. First, we define a DateTime instance and an array containing the new format codes, as follows:

    $dt = new DateTime('tomorrow');

    $pt = [IntlDateFormatter::RELATIVE_FULL,

        IntlDateFormatter::RELATIVE_LONG,

        IntlDateFormatter::RELATIVE_MEDIUM,

        IntlDateFormatter::RELATIVE_SHORT

    ];

  2. We then loop through the formats and echo the output, as follows:

    foreach ($pt as $fmt)

        echo IntlDateFormatter::formatObject($dt, $fmt)." ";

This example doesn't work in PHP 7. Here is the output from PHP 8:

root@php8_tips_php8 [ /repo/ch07 ]#

php php8_intl_date_fmt.php

tomorrow at 12:00:00 AM Coordinated Universal Time

tomorrow at 12:00:00 AM UTC

tomorrow, 12:00:00 AM

tomorrow, 12:00 AM

As you can see, the new relative date formats work quite well! We now briefly return to the cURL extension.

Understanding cURL extension changes

The cURL extension leverages libcurl (http://curl.haxx.se/) to provide powerful and highly efficient HTTP client capabilities. In PHP 8, you must have libcurl version 7.29 (or above) installed on your server's operating system.

Another difference in PHP 8 is that this extension now uses objects rather than resources. This change was described earlier in this chapter, in Table 7.1, PHP 8 resource-to-object migration. An example was shown in the Potential code break involving is_resource() section. A side effect of this change is that any of the curl*close() functions are redundant, as the connection is closed when the object is unset or otherwise goes out of scope.

Let's now have a look at changes to the COM extension.

Reviewing COM extension changes

Component Object Model (COM) is a Windows-only extension that enables programming code written in one language to call and interoperate with code written in any other COM-aware programming language. This information is important for any PHP developers who plan to develop PHP applications that run on Windows servers.

The most significant change to the COM extension is that case sensitivity is now automatically enforced. Accordingly, you can no longer import from type libraries any constants that are case-insensitive. In addition, you can no longer specify $case_insensitive, the second argument to the com_load_typelib() function, as FALSE.

Along these lines, COM extension php.ini settings that deal with case sensitivity have been altered. These include the following:

  • com.autoregister_casesensitive: Permanently enabled in PHP 8.
  • com.typelib_file: Any type libraries whose names end with either #cis or #case_insensitive no longer cause the constants to be treated as case-insensitive.

One change is a new php.ini setting, com.dotnet_version. This setting sets the .NET version to use for dotnet objects. We now examine other extension changes of note.

Examining other extension changes

There are a few other changes to other PHP extensions that deserve a mention. Table 7.5, shown next, summarizes these changes:

Table 7.5 – PHP 8 database library requirements

Table 7.5 – PHP 8 database library requirements

You now have an idea about changes to extensions in PHP 8. This wraps up the chapter. Now, it's time for a recap!

Summary

One of the most important concepts you learned in this chapter is a general trend away from resources and toward objects. You learned where this trend is noticeable in the various PHP extensions covered in this chapter, and how to develop workarounds to avoid problems in code that relies upon resources. You also learned how to detect and develop code to address changes in XML extensions, especially in the SimpleXML and XMLWriter extensions.

Another important extension with significant changes covered in this chapter is the mbstring extension. You learned to detect code that relies upon changed mbstring functionality. As you learned, changes to the mbstring extension for the most part mirror changes made to the equivalent core PHP string functions.

You also learned about major changes to the GD, Reflection, and ZIP extensions. In this chapter, you also learned about changes to a number of database extensions, as well as a medley of additional extension changes to note. All in all, with the awareness you will have gained by reading this chapter and by studying the examples, you are now in a better position to prevent your applications from failing after you have performed a PHP 8 upgrade.

In the next chapter, you will learn about functionality that has been deprecated or removed in PHP 8.

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

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