Chapter 3: Taking Advantage of Error-Handling Enhancements

If you've been a PHP developer for any length of time, you will have noticed that as the language continues to mature, more safeguards are put into place that ultimately enforce good coding practices. Along these lines, one of the key improvements in PHP 8 is its advanced error-handling capabilities. In this chapter, you will learn which Notices have been upgraded to Warnings, and which Warnings have been upgraded to Errors.

This chapter gives you an excellent understanding of the background and intent of the security enhancements, allowing you to better control the use of your code. In addition, it's critical to be aware of error conditions that formerly only generated Warnings but now also generate Errors, in order to take measures to prevent your applications from failing following an upgrade to PHP 8.

The following topics are covered in this chapter:

  • Understanding PHP 8 error handling
  • Dealing with warnings that are now errors
  • Understanding notices promoted to warnings
  • Handling the @ error control operator

Technical requirements

To examine and run the code examples provided in this chapter, the minimum recommended hardware is listed 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. Throughout 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

We can now begin our discussion by examining new PHP 8 operators.

Understanding PHP 8 error handling

Historically, many PHP error conditions were assigned an error level far below their actual severity. This gave developers a false sense of security as seeing only a Notice led them to believe that their code was not deficient. Many situations only formerly generated a Notice or a Warning when in fact their seriousness merited greater attention.

In this section, we look at a number of error-handling enhancements in PHP 8 that continue with the overall trend of enforcing good coding practices. The discussion in this chapter will help you to re-examine your code, with an eye toward greater efficiency and fewer maintenance issues down the road.

In the next several subsections, we have a look at changes to certain Notice and Warning error conditions that could have an impact on your code. Let's first have a look at changes in how PHP 8 handles undefined variables.

Undefined variable handling

One notorious feature of PHP is how it treats undefined variables. Have a look at this simple block of code. Note that the $a and $b variables have not been defined:

// /repo/ch03/php8_undef_var.php

$c = $a + $b;

var_dump($c);

Running under PHP 7, here's the output:

PHP Notice:  Undefined variable: a in

/repo/ch03/php7_undef_var.php on line 3

PHP Notice:  Undefined variable: b in /repo/ch03/php7_undef_var.php on line 3

int(0)

As you can see from the output, PHP 7 emits a Notice, letting us know we are using variables that have not been defined. If we run exactly the same code using PHP 8, you can quickly see that what was previously a Notice has been promoted to a Warning, as illustrated here:

PHP Warning:  Undefined variable $a in /repo/ch03/php8_undef_var.php on line 3

PHP Warning:  Undefined variable $b in /repo/ch03/php8_undef_var.php on line 3

int(0)

The reasoning behind this error-level promotion in PHP 8 is that the use of undefined variables, thought by many developers to be a harmless practice, is actually quite dangerous! Why?, you might ask. The answer is that PHP silently, without your explicit direction, assigns a value of NULL to any undefined variable. Effectively, your program is relying upon a default behavior of PHP that could change in future upgrades to the language.

We cover other error-level promotions in the next few sections of this chapter. Please note, however, that situations where Notices are promoted to Warnings will not affect the functioning of your code. It might serve to bring more potential problems to your attention, however, and if so, serves the purpose of producing better code. Unlike undefined variables, undefined constants' errors have now been even further promoted, as you'll see in the next subsection.

Undefined constant handling

The treatment of undefined constants has changed when running under PHP 8. However, in this case, what was previously a Warning is now an Error in PHP 8. Have a look at this innocuous-looking block of code:

// /repo/ch03/php7_undef_const.php

echo PHP_OS . " ";

echo UNDEFINED_CONSTANT . " ";

echo "Program Continues ... ";

The first line echoes a PHP_OS pre-defined constant that identifies the operating system. In PHP 7, a Notice is generated; however, the last line of output is Program Continues ..., as shown here:

PHP Notice:  Use of undefined constant UNDEFINED_CONSTANT - assumed 'UNDEFINED_CONSTANT' in /repo/ch03/php7_undef_const.php on line 6

Program Continues ...

The same code now produces a fatal error when running in PHP 8, as shown here:

PHP Fatal error:  Uncaught Error: Undefined constant "UNDEFINED_CONSTANT" in /repo/ch03/php8_undef_const.php:6

Accordingly, any bad code you have lying around that fails to first define any constants before use will crash and burn in PHP 8! A good habit is to assign a default value to all variables at the start of your application code. If you plan to use constants, it's also a good idea to define them as soon as possible, preferably in one place.

Important note

One idea is to define all constants in an included file. If this is the case, be sure that any program script using such constants has loaded the file containing the constant definition.

Tip

Best practice: Assign default values to all variables at the beginning of your program code before use. Be sure to define any custom constants before they are used. If this is the case, be sure that any program script using such constants has loaded the file containing the constant definition.

Error-level defaults

It's useful to note that the error-level defaults assigned to the php.ini file error_reporting directive have been updated in PHP 8. In PHP 7, the default error_reporting level was as follows:

error_reporting=E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED

In PHP 8, the new level is much simpler, as you can see here:

error_reporting=E_ALL

It's also worth noting that the php.ini file setting display_startup_errors is now enabled by default. This might be an issue for production servers, as your website might start to unintentionally reveal error information upon PHP startup.

The key takeaway from this section is that in the past, PHP has allowed you to get away with certain bad practices by only issuing Notices or Warnings. As you've learned in this section, however, the danger in not addressing the issues behind the Warning or Notice generation lies in the actions PHP silently takes on your behalf. Not relying upon PHP to make decisions on your behalf leads to fewer hidden logic errors. Following good coding practices, such as that of assigning defaults to all variables before they are used, helps you to avoid such errors. Let's now have a closer look at error situations where Warnings have been promoted to Errors in PHP 8.

Dealing with warnings that are now errors

In this section, we look at upgraded PHP 8 error handling pertaining to objects, arrays, and strings. We also examine situations where, in the past, PHP issued a Warning but where PHP 8 now throws an Error. It is critical that you become aware of any of the potential error situations addressed in this section. The reason is simple: if you fail to address the situations described in this section, when your server is upgraded to PHP 8 your code will break.

Developers are often pressed for time. It could be that there's a massive queue of new features or other changes that must be made. In other cases, resources have been pulled away to other projects, meaning fewer developers are available to perform maintenance. Warnings are often ignored because the application continues to run, so many developers simply turn off the error display and hope for the best.

Over the years, mountains upon mountains of badly written code have accumulated. Unfortunately, the PHP community is now paying the price, in the form of mysterious runtime errors that take hours to track down. By promoting to Error certain dangerous practices previously raising only a Warning, bad coding practices quickly become apparent in PHP 8 as Errors are fatal and cause the application to stop running.

Let's start by examining error promotion in object error handling.

Important note

As a general rule, in PHP 8, Warnings are promoted to Errors when an attempt is made to write data. On the other hand, for the same general circumstance (for example, attempting to read/write properties of non-existent objects), a Notice is promoted to a Warning in PHP 8 when an attempt is made to read data. The overall rationale is that write attempts could result in loss or corruption of data, whereas read attempts do not.

Promoted warnings in object error handling

Here is a brief summation of Warnings that are now Errors pertaining to the treatment of objects. PHP 8 will throw an Error if you attempt to do the following:

  • Increment/decrement a property of a non-object
  • Modify a property of a non-object
  • Assign a value to a property of a non-object
  • Create a default object from an empty value

Let's have a look at a simple example. In the following code snippet, a value is assigned to a non-existent object, $a. This value is then incremented:

// /repo/ch03/php8_warn_prop_nobj.php

$a->test = 0;

$a->test++;

var_dump($a);

Here is the PHP 7 output:

PHP Warning:  Creating default object from empty value in /repo/ch03/php8_warn_prop_nobj.php on line 4

class stdClass#1 (1) {

  public $test =>

  int(1)

}

As you can see, in PHP 7 a stdClass() instance is silently created and a Warning is issued, but the operation is allowed to continue. If we run the same code under PHP 8, notice here the difference in output:

PHP Fatal error:  Uncaught Error: Attempt to assign property "test" on null in /repo/ch03/php8_warn_prop_nobj.php:4

The good news is in PHP 8 the Error is thrown, which means we could easily catch it by implementing a try()/catch() block. As an example, here's how the code shown previously might be rewritten:

try {

    $a->test = 0;

    $a->test++;

    var_dump($a);

} catch (Error $e) {

    error_log(__FILE__ . ':' . $e->getMessage());

}

As you can see, any problems with the three lines are now wrapped safely inside a try()/catch() block, meaning that recovery is possible. We now turn our attention to array error-handling enhancements.

Promoted warnings in array handling

A number of bad practices regarding arrays, allowed in PHP 7 and earlier versions, now throw an Error. As discussed in the previous subsection, PHP 8 array error-handling changes serve to give you a more forceful response to the error situations we describe here. The ultimate goal of these enhancements is to nudge developers toward good coding practices.

Here is brief list of array-handling warnings promoted to errors:

  • Cannot add an element to the array as the next element is already occupied
  • Cannot unset the offset in a non-array variable
  • Only array and Traversable types can be unpacked
  • Illegal offset types

Let's now examine each of the error conditions on this list, one by one.

Next element already occupied

In order to illustrate one possible scenario where the next array element cannot be assigned as it's already occupied, have a look at this simple code example:

// ch03/php8_warn_array_occupied.php

$a[PHP_INT_MAX] = 'This is the end!';

$a[] = 'Off the deep end';

Assume that, for some reason, an assignment is made to an array element whose numeric key is the largest-sized integer possible (represented by the PHP_INT_MAX pre-defined constant). If we subsequently attempt to assign a value to the next element, we have a problem!

Here is the result of running this block of code in PHP 7:

PHP Warning:  Cannot add element to the array as the next element is already occupied in

/repo/ch03/php8_warn_array_occupied.php on line 7

array(1) {

  [9223372036854775807] =>

  string(16) "This is the end!"

}

In PHP 8, however, the Warning has been promoted to an Error, with this output as a result:

PHP Fatal error:  Uncaught Error: Cannot add element to the

array as the next element is already occupied in

/repo/ch03/php8_warn_array_occupied.php:7

Next, we turn our attention to the use of offsets in non-array variables.

Offsets in non-array variables

Treating a non-array variable as an array can produce unexpected results, with the exception of certain object classes that implement a Traversable (ArrayObject or ArrayIterator as examples). A case in point is using array-style offsets on a string.

Accessing string characters using array syntax can be useful in some cases. One example is checking to see if a Uniform Resource Locator (URL) ends with a trailing comma or slash. In the following code example, we check to see if a URL ends with a trailing slash. If so, we chop it off using substr():

// ch03/php8_string_access_using_array_syntax.php

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

if ($url[-1] == '/')

    $url = substr($url, 0, -1);

echo $url;

// returns: "https://unlikelysource.com"

In the example shown previously, the $url[-1] array syntax gives you access to the last character in the string.

Tip

You could also use the new PHP 8 str_ends_with() function to do the same thing!

However, strings are definitely not arrays and should not be treated as such. In order to avoid bad code potentially leading to unexpected results, minor abuse of the ability to reference string characters using array syntax has been curtailed in PHP 8.

In the following code example, we attempt to use unset() on a string:

// ch03/php8_warn_array_unset.php

$alpha = 'ABCDEF';

unset($alpha[2]);

var_dump($alpha);

The preceding code example will actually generate a fatal error in both PHP 7 and 8. Likewise, do not use a non-array (or non-Traversable object) as an argument to a foreach() loop. In the example shown next, a string is supplied as an argument to foreach():

// ch03/php8_warn_array_foreach.php

$alpha = 'ABCDEF';

foreach ($alpha as $letter) echo $letter;

echo "Continues ... ";

In PHP 7 and earlier versions, a Warning is generated but the code continues. Here is the output when running in PHP 7.1:

PHP Warning:  Invalid argument supplied for foreach() in /repo/ch03/php8_warn_array_foreach.php on line 6

Continues ...

Interestingly, PHP 8 also allows the code to continue, but the Warning message is slightly more detailed, as shown here:

PHP Warning:  foreach() argument must be of type array|object, string given in /repo/ch03/php8_warn_array_foreach.php on line 6

Continues ...

Next, we have a look at situations where in the past you could get away with unpacking non-array/non-Traversable types.

Array unpacking

After seeing this sub-section title, you may well ask: what is array unpacking? Much like the concept of de-referencing, unpacking an array is simply a term for extracting values from an array into discrete variables. As an example, consider the following simple code:

  1. We start the example by defining a simple function that adds two numbers, as follows:

    // ch03/php8_array_unpack.php

    function add($a, $b) { return $a + $b; }

  2. For the sake of the following illustration, assume that the data is in the form of an array of number pairs, each to be added:

    $vals = [ [18,48], [72,99], [11,37] ];

  3. In a loop, we use the variadics operator (...) to unpack the array pairs in the call to the add() function, as follows:

    foreach ($vals as $pair) {

        echo 'The sum of ' . implode(' + ', $pair) .

             ' is ';

        echo add(...$pair);

    }

The example just shown demonstrates how a developer can force unpacking by using the variadics operator. However, many PHP array functions perform an unpacking operation internally. Consider the following example:

  1. First, we define an array whose elements comprise letters of the alphabet. If we echo the return value of array_pop() we see the letter Z as output, as illustrated in the following code snippet:

    // ch03/php8_warn_array_unpack.php

    $alpha = range('A','Z');

    echo array_pop($alpha) . " ";

  2. We can achieve the same result using implode() to flatten the array into a string, and use string de-referencing to return the last letter, as illustrated in the following code snippet:

    $alpha = implode('', range('A','Z'));

    echo $alpha[-1];

  3. However, if we attempt to use array_pop() on a string as shown here, in PHP 7 and earlier versions we get a Warning:

    echo array_pop($alpha);

  4. Here is the output when running under PHP 7.1:

    ZZPHP Warning:  array_pop() expects parameter 1 to be array, string given in /repo/ch03/php8_warn_array_unpack.php on line 14

  5. And here is the output from the same code file but when running under PHP 8:

    ZZPHP Fatal error:  Uncaught TypeError: array_pop(): Argument #1 ($array) must be of type array, string given in /repo/ch03/php8_warn_array_unpack.php:14

As we have mentioned, here is yet another example of where a situation formerly resulting in a Warning now results in TypeError in PHP 8. However, both sets of output also illustrate the fact that although you can de-reference a string as you would an array, strings cannot be unpacked in the same manner as arrays.

Next, we examine illegal offset types.

Illegal offset types

According to the PHP documentation (https://www.php.net/manual/en/language.types.array.php), an array is an ordered list of key/value pairs. The array keys, also called indices or offsets, can be one of two data types: integer or string. If an array consists only of integer keys, it is often referred to as a numeric array. An associative array, on the other hand, is a term used where string indices are used. An illegal offset would be where the array key is of a data type other than integer or string.

Important note

Interestingly, the following code snippet does not generate a Warning or an Error: $x = (float) 22/7; $arr[$x] = 'Value of Pi';. The value of $x is first converted to an integer, truncating any decimal component, before the array assignment is made.

As an example, have a look at this code fragment. Note that the index key for the last array element is an object:

// ch03/php8_warn_array_offset.php

$obj = new stdClass();

$b = ['A' => 1, 'B' => 2, $obj => 3];

var_dump($b);

The output running under PHP 7 produces the var_dump() output with a Warning, as illustrated here:

PHP Warning:  Illegal offset type in /repo/ch03/php8_warn_array_offset.php on line 6

array(2) {

  'A' =>  int(1)

  'B' =>  int(2)

}

In PHP 8, however, var_dump() is never executed as a TypeError is thrown, as shown here:

PHP Fatal error:  Uncaught TypeError: Illegal offset type in /repo/ch03/php8_warn_array_offset.php:6

The same principle regarding illegal array offsets is present when using unset(), as illustrated in this code example:

// ch03/php8_warn_array_offset.php

$obj = new stdClass();

$b = ['A' => 1, 'B' => 2, 'C' => 3];

unset($b[$obj]);

var_dump($b);

The stricter control of array index keys is also seen when using illegal offsets in empty() or isset(), as shown in this code fragment:

// ch03/php8_warn_array_empty.php

$obj = new stdClass();

$obj->c = 'C';

$b = ['A' => 1, 'B' => 2, 'C' => 3];

$message =(empty($b[$obj])) ? 'NOT FOUND' : 'FOUND';

echo "$message ";

In both of the previous code examples, in PHP 7 and earlier the code example completes with a Warning, whereas in PHP 8 an Error is thrown. Unless the Error is caught, the code example will not complete.

Tip

Best practice: When initializing an array, be sure that the array index data type is either an integer or a string.

Next, we have a look at error promotions in string handling.

Promoted warnings in string handling

The same discussion about promoted warnings pertaining to objects and arrays also applies to PHP 8 string error handling. In this subsection, we examine two string-handling Warnings promoted to Errors, outlined here:

  • Offset not contained in the string
  • Empty string offset
  • Let's start by examining offsets not contained in a string.

Offset not contained in the string.

As an example of the first situation, have a look at the following code sample. Here, we start with a string assigned all letters of the alphabet. We then use strpos() to return the position of the letter Z, starting at offset 0. On the next line, we do the same thing; however, the offset of 27 is off the end of the string:

// /repo/ch03/php8_error_str_pos.php

$str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

echo $str[strpos($str, 'Z', 0)];

echo $str[strpos($str, 'Z', 27)];

In PHP 7, as expected, an output of Z is returned, with a Warning from strpos() and a Notice that an offset cast (more on that in the next section) occurred. Here is the PHP 7 output:

Z

PHP Warning:  strpos(): Offset not contained in string in /repo/ch03/php8_error_str_pos.php on line 7

PHP Notice:  String offset cast occurred in /repo/ch03/php8_error_str_pos.php on line 7

In PHP 8, however, a fatal ValueError is thrown, as seen here:

Z

PHP Fatal error:  Uncaught ValueError: strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) in /repo/ch03/php8_error_str_pos.php:7

The key point we need to convey in this situation is that allowing such bad coding to remain was marginally acceptable in the past. Following a PHP 8 upgrade, however, as you can clearly see from the output, your code will fail. Now, let's have a look at empty string offsets.

Empty string offset error handling

Believe it or not, in versions of PHP prior to PHP 7, developers were allowed to remove characters from a string by assigning an empty value to the target offset. As an example, have a look at this block of code:

// /repo/ch03/php8_error_str_empty.php

$str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

$str[5] = '';

echo $str . " ";

The intent of this code example is to remove the letter F from the string represented by $str. Amazingly, in PHP 5.6, you can see from this screenshot that the attempt is entirely successful:

Figure 3.1 – PHP 5.6 output showing successful character removal

Figure 3.1 – PHP 5.6 output showing successful character removal

Please note that the virtual environments we use to demonstrate code in this book allow access to both PHP 7.1 and PHP 8. In order to properly demonstrate how PHP 5 behaved, we mounted a PHP 5.6 Docker image and took a screenshot of the result.

In PHP 7, however, this practice is prohibited and a Warning is issued, as seen here:

PHP Warning:  Cannot assign an empty string to a string offset in /repo/ch03/php8_error_str_empty.php on line 5

ABCDEFGHIJKLMNOPQRSTUVWXYZ

As you can see from the preceding output, the script is allowed to execute; however, the attempt to remove the letter F is unsuccessful. In PHP 8, as we have discussed, the Warning is promoted to an Error and the entire script aborts, as shown here:

PHP Fatal error:  Uncaught Error: Cannot assign an empty string to a string offset in /repo/ch03/php8_error_str_empty.php:5

We next examine situations where former Notices are promoted to Warnings in PHP 8.

Understanding notices promoted to warnings

There are a number of situations that are considered less critical to the stability of the PHP engine during runtime that were underrated in versions of PHP prior to PHP 7. Unfortunately, it was customary for new (or perhaps lazy!) PHP developers to simply ignore Notices in their rush to get their code into production.

PHP standards have dramatically tightened over the years, leading the PHP core team to upgrade certain error conditions from Notice to Warning. Neither error reporting level will cause the code to stop working. However, it is felt by the PHP core team that the Notice-to-Warning promotion will make bad programming practices keenly visible. Warnings are much less likely to be ignored, ultimately leading to better code.

Here is a brief list of error conditions leading to a Notice being issued in earlier versions of PHP, where the same condition now generates a Warning in PHP 8:

  • Non-existent object property access attempts
  • Non-existent static property access attempts
  • Attempt to access an array element using a non-existent key
  • Misusing a resource as an array offset
  • Ambiguous string offset cast
  • Non-existent or uninitialized string offset

Let's first have a look at Notice promotions in cases involving objects.

Non-existent object property access handling

In earlier versions of PHP, a Notice was issued when attempting to access non-existent properties. The only exception is when it's a custom class where you defined the magic __get() and/or __set() methods.

In the following code example, we define a Test class with two properties, one being marked static:

// /repo/ch03/php8_warn_undef_prop.php

class Test {

    public static $stat = 'STATIC';

    public $exists = 'NORMAL';

}

$obj = new Test();

We then attempt to echo existing and non-existent properties, as follows:

echo $obj->exists;

echo $obj->does_not_exist;

Unsurprisingly, the output in PHP 7 returns a Notice when the non-existent property echo attempt is made, as shown here:

NORMAL

PHP Notice:  Undefined property: Test::$does_not_exist in

/repo/ch03/php8_warn_undef_prop.php on line 14

The same code file, in PHP 8, now returns a Warning, as seen here:

NORMAL

PHP Warning:  Undefined property: Test::$does_not_exist in /repo/ch03/php8_warn_undef_prop.php on line 14

Important note

The Test::$does_not_exist error message does not mean we attempted static access. It simply means that a $does_not_exist property is associated with a Test class.

We now add lines of code attempting to access a non-existent static property, as follows:

try {

    echo Test::$stat;

    echo Test::$does_not_exist;

} catch (Error $e) {

    echo __LINE__ . ':' . $e;

}

Interestingly, both PHP 7 and PHP 8 now issue a fatal error, as seen here:

STATIC

22:Error: Access to undeclared static property Test::$does_not_exist in /repo/ch03/php8_warn_undef_prop.php:20

Anytime a block of code that previously issued a Warning now issues an Error is cause for concern. If possible, scan your code for static references to static class properties and make sure they are defined. Otherwise, after a PHP 8 upgrade, your code will fail.

Let's now have a look at non-existent offset handling.

Non-existent offset handling

As mentioned in the previous section, in general, Notices have been promoted to Warnings where data is read, whereas Warnings have been promoted to Errors where data is written (and could conceivably result in lost data). The handling of non-existent offsets follows this logic.

In the following example, an array key is drawn from a string. In both cases, the offset doesn't exist:

// /repo/ch03/php8_warn_undef_array_key.php

$key  = 'ABCDEF';

$vals = ['A' => 111, 'B' => 222, 'C' => 333];

echo $vals[$key[6]];

In PHP 7, the result is a Notice, as seen here:

PHP Notice:  Uninitialized string offset: 6 in /repo/ch03/php8_warn_undef_array_key.php on line 6

PHP Notice:  Undefined index:  in /repo/ch03/php8_warn_undef_array_key.php on line 6

In PHP 8, the result is a Warning, as shown here:

PHP Warning:  Uninitialized string offset 6 in /repo/ch03/php8_warn_undef_array_key.php on line 6

PHP Warning:  Undefined array key "" in /repo/ch03/php8_warn_undef_array_key.php on line 6

This example further illustrates the general rationale behind PHP 8 error handling enhancements: if your code writes data to a non-existent offset, what was previously a Warning is an Error in PHP 8. The preceding output shows where an attempt made to read data from a non-existent offset in PHP 8, a Warning is now issued. The next Notice promotion to examine deals with misuse of resource IDs.

Misusing resource IDs as array offsets

A resource is generated when creating a connection to a service external to your application code. A classic example of this data type would be a file handle. In the following code example, we open a file handle (thus creating resource) to a gettysburg.txt file:

// /repo/ch03/php8_warn_resource_offset.php

$fn = __DIR__ . '/../sample_data/gettysburg.txt';

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

echo $fh . " ";

Note that we echo the resource directly in the last line. This reveals the resource ID number. If we now try to use the resource ID as an array offset, however, PHP 7 generates a Notice, as shown here:

Resource id #5

PHP Notice:  Resource ID#5 used as offset, casting to integer (5) in /repo/ch03/php8_warn_resource_offset.php on line 9

PHP 8, as expected, generates a Warning, as shown here:

Resource id #5

PHP Warning:  Resource ID#5 used as offset, casting to integer (5) in /repo/ch03/php8_warn_resource_offset.php on line 9

Note that in PHP 8, many functions that formerly produced a resource now produce an object instead. This topic is covered in Chapter 7, Avoiding Traps When Using PHP 8 Extensions.

Tip

Best practice: Do not use resource IDs as array offsets!

We now turn our attention to string-related Notices promoted to Warnings in the case of ambiguous string offsets.

Ambiguous string offset cast

Turning our attention to string handling, we once again revisit the idea of identifying a single character in a string using array syntax. An ambiguous string offset cast might occur if PHP has to perform an internal type cast in order to evaluate a string offset, but where in that type-cast is not clear.

In this very simple example, we define a string that contains all the letters of the alphabet. We then define an array of keys with these values: NULL; a Boolean, TRUE; and a float, 22/7 (the approximate value of Pi). We then loop through the keys and attempt to use the key as a string offset, as illustrated here:

// /repo/ch03/php8_warn_amb_offset.php

$str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

$ptr = [ NULL, TRUE, 22/7 ];

foreach ($ptr as $key) {

    var_dump($key);

    echo $str[$key];

}

As you might have anticipated, the output running in PHP 7 produces the output A, B, and D, along with a series of Notices, shown here:

NULL

PHP Notice:  String offset cast occurred in /repo/ch03/php8_warn_amb_offset.php on line 8

A

/repo/ch03/php8_warn_amb_offset.php:7:

bool(true)

PHP Notice:  String offset cast occurred in /repo/ch03/php8_warn_amb_offset.php on line 8

B

/repo/ch03/php8_warn_amb_offset.php:7:

double(3.1428571428571)

PHP Notice:  String offset cast occurred in /repo/ch03/php8_warn_amb_offset.php on line 8

D

PHP 8 consistently produces the same results, but here, a Warning has taken the place of the Notice:

NULL

PHP Warning:  String offset cast occurred in /repo/ch03/php8_warn_amb_offset.php on line 8

A

bool(true)

PHP Warning:  String offset cast occurred in /repo/ch03/php8_warn_amb_offset.php on line 8

B

float(3.142857142857143)

PHP Warning:  String offset cast occurred in /repo/ch03/php8_warn_amb_offset.php on line 8

D

Let's now have a look at non-existent offset handling.

Uninitialized or non-existent string offsets

This type of error is designed to trap access to strings using offsets, where the offset is out of bounds. Here's a very simple code example that illustrates this situation:

// /repo/ch03/php8_warn_un_init_offset.php

$str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

echo $str[27];

Running this code in PHP 7 results in a Notice. Here's the output from PHP 7:

PHP Notice:  Uninitialized string offset: 27 in /repo/ch03/php8_warn_un_init_offset.php on line 5

Predictably, the output from PHP 8 produces a Warning, as seen here:

PHP Warning:  Uninitialized string offset 27 in /repo/ch03/php8_warn_un_init_offset.php on line 5

All of the examples in this section confirm the general trend in PHP 8 toward enforcing best coding practices.

Tip

For more information on promoted Notices and Warnings, have a look at this article: https://wiki.php.net/rfc/engine_warnings.

Now, we turn our attention to the (infamous) @ warning suppressor.

Handling the @ error control operator

For years and years, many PHP developers have used the @ error control operator to mask errors. This was especially true when using unreliable PHP libraries with badly written code. Unfortunately, the net effect of this usage only serves to propagate bad code!

Many PHP developers are exercising wishful thinking, believing that when they use the @ operator to prevent errors from being displayed, this makes it seem as if the problem has magically gone away! Trust me when I say this: it hasn't! In this section, we first examine traditional use of the @ operator, after which we examine @ operator changes in PHP 8.

Tip

For more information on traditional @ operator syntax and usage, have a look at this documentation reference page: https://www.php.net/manual/en/language.operators.errorcontrol.php.

@ operator usage

Before presenting a code example, once again it's extremely important to emphasize that we are not promoting the usage of this mechanism! On the contrary—you should avoid this usage in every case. If an error message appears, the best solution is to fix the error, not to silence it!

In the following code example, two functions are defined. The bad() function deliberately triggers an error. The worse() function includes a file in which there is a parse error. Note that when the functions are called, the @ symbol precedes the function name, causing the error output to be suppressed:

// /repo/ch03/php8_at_silencer.php

function bad() {

    trigger_error(__FUNCTION__, E_USER_ERROR);

}

function worse() {

    return include __DIR__ .  '/includes/

                               causes_parse_error.php';

}

echo @bad();

echo @worse();

echo " Last Line ";

In PHP 7, there's simply no output at all, as shown here:

root@php8_tips_php7 [ /repo/ch03 ]# php php8_at_silencer.php

root@php8_tips_php7 [ /repo/ch03 ]#

What's interesting to note is that the program is actually not allowed to continue in PHP 7: we never saw the Last Line output. This is because, although masked, a fatal error was nonetheless generated, causing the program to fail. In PHP 8, however, the fatal error is not masked, as seen here:

root@php8_tips_php8 [ /repo/ch03 ]# php8 php8_at_silencer.php

PHP Fatal error:  bad in /repo/ch03/php8_at_silencer.php on line 5

Let's now have a look at another difference in PHP 8 regarding the @ operator.

@ operator and error_reporting()

The error_reporting() function is normally used to override the error_reporting directive set in the php.ini file. Another use of this function, however, is to return the latest error code. However, an odd exception was present in versions of PHP prior to PHP 8, in that error_reporting() returned a value of 0 if the @ operator was used.

In the following code example, we define an error handler that reports on the received error number and string when it's invoked. In addition, we also display the value returned by error_reporting():

// /repo/ch03/php8_at_silencer_err_rep.php

function handler(int $errno , string $errstr) {

    $report = error_reporting();

    echo 'Error Reporting : ' . $report . " ";

    echo 'Error Number    : ' . $errno . " ";

    echo 'Error String    : ' . $errstr . " ";

    if (error_reporting() == 0) {

        echo "IF statement works! ";

    }

}

As before, we define a bad() function that deliberately triggers an error, and then call the function using the @ operator, as follows:

function bad() {

    trigger_error('We Be Bad', E_USER_ERROR);

}

set_error_handler('handler');

echo @bad();

In PHP 7, you'll note that error_reporting() returns 0, thus causing IF statement works! to appear in the output, as illustrated here:

root@root@php8_tips_php7 [ /repo/ch03 ] #

php php8_at_silencer_err_rep.php

Error Reporting : 0

Error Number    : 256

Error String    : We Be Bad

IF statement works!

Running in PHP 8, on the other hand, error_reporting() returns the value of the last error—in this case, 4437. Also, of course, the if() expression fails, causing no additional output. Here is the result of the same code running in PHP 8:

root@php8_tips_php8 [ /repo/ch03 ] #

php php8_at_silencer_err_rep.php

Error Reporting : 4437

Error Number    : 256

Error String    : We Be Bad

This concludes consideration of the differences in @ operator usage in PHP 8.

Tip

Best practice: Do not use the @ error control operator! The intent of the @ operator is to suppress the display of an error message, but you need to consider why this error message is appearing in the first place. By using the @ operator, you are only avoiding providing a solution to a problem!

Summary

In this chapter, you received an overview of major changes in error handling in PHP 8. You were also given examples of situations where error conditions might arise, and now have an idea of how to properly manage errors in PHP 8. You now have a solid path toward refactoring code that under PHP 8 now produces errors. If your code could potentially lead to any of the conditions described where former Warnings are now Errors, you risk having your code break.

In a like manner, although the second set of error conditions described only produced Notices in the past, these same conditions now cause a Warning. The new set of Warnings gives you a chance to adjust faulty code and prevent having your application devolve into a seriously unstable condition.

Finally, you learned how use of the @ operator is strongly discouraged. In PHP 8, this syntax will no longer mask fatal errors. In the next chapter, you will learn how to create C-language structures and make direct calls to C-language functions 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.137.218.215