PHP 8.0: @ Error Suppression operator does not silent fatal errors

Version8.0
TypeChange

PHP supports the @ error control operator (also called STFU operator with mixed feelings), that suppresses errors just for the expression that immediately follows.

For example, the unlink function emits a warning if the file does not exist, and calling it with the @ operator can suppress these errors.

@unlink('file-that-does-not-exist.oops');

This operator was historically used frequently to access array values (such as HTTP parameters), call certain functions that the outcome does not alter the control flow. While PHP is getting strict with better exception handling, and type checking for internal functions since PHP 8.0, the @ suppression operator is still useful in various areas, such as file system operations.

Related Articles

In PHP 8.0, the @ operator does not suppress certain types of errors that were silenced prior to PHP 8.0. This includes the following types of errors:

All of these errors, if raised, halts the rest of the application from being run. The difference in PHP 8.0 is that the error message is not silenced, which would have otherwise resulted in a silent error.

Related Changes in PHP 8.0

Note that the @ operator continue to silent warnings and notices.


Here is an example that this change in PHP 8.0 makes a difference.

function load_files() {
    require_once 'file-that-does-not-exist.oops';
}

@load_files();

Prior to PHP 8.0, this snippet would not have emitted any errors or notices (but stopped execution anyway). In PHP 8.0, this error (E_ERROR) is not suppressed, and the execution is stopped too.

Fatal error: Uncaught Error: Failed opening required 'file-that-does-not-exist.oops' (include_path='.:') in ...:...

Another example where this change is more pronounced is with the E_USER_ERROR error type.

function do_something() {
    trigger_error('Something went wrong', E_USER_ERROR);
}

@do_something();

While PHP versions prior to 8.0 suppresses this E_USER_ERROR, PHP 8.0 and later do not, and result in an error:

Fatal error: Something went wrong in ... on line ...

Error Type Error number @ effective
PHP < 8.0
@ effective
PHP >= 8.0
E_ERROR 1 Yes No
E_WARNING 2 Yes Yes
E_PARSE 4 Yes No
E_NOTICE 8 Yes Yes
E_CORE_ERROR 16 Yes No
E_CORE_WARNING 32 Yes Yes
E_COMPILE_ERROR 64 Yes No
E_COMPILE_WARNING 128 Yes Yes
E_USER_ERROR 256 Yes No
E_USER_WARNING 512 Yes Yes
E_USER_NOTICE 1024 Yes Yes
E_STRICT 2048 Yes Yes
E_RECOVERABLE_ERROR 4096 Yes No
E_DEPRECATED 8192 Yes Yes
E_USER_DEPRECATED 16384 Yes Yes
E_ALL 32767 Yes No

The @ operator does not suppress any exceptions in any PHP version. Several PHP versions now throw \ValueError and \TypeError exceptions. See PHP 8.0: Internal function warnings now throw TypeError and ValueError exceptions.

Error handlers set with set_error_handler function will continue to receive errors they subscribe to.

Impact on Error Handlers

The error_reporting function correctly returns the error reporting state in PHP 8.0.

When the @ suppressor operator is used, the error reporting value is temporarily lowered (but never increased) to a maximum value of:

E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE

The bitmask above evaluates to an integer value of 4437.

If the error reporting value is already configured to not report a certain type of errors, they will remain unreported.


Calling error_reporting function calls inside a callback that is called with the @ suppressor or the error handler will report the accurate error reporting value. Prior to PHP 8.0, it incorrectly reported that the value is always 0.

echo "Normal error reporting value: ", error_reporting();

function test(){
    echo "\nSuppressed error reporting value: ", error_reporting();
}

@test();

PHP < 8.0:

Normal error reporting value: 32767
Suppressed error reporting value: 0

PHP >= 8.0

Normal error reporting value: 32767
Suppressed error reporting value: 4437

Backwards Compatibility Impact

With this change in PHP 8.0, the @ operator might not suppress all the errors. This is a desired result, because all the error messages that are not silenced also cause the application to halt. Suppressing said errors would not allow the application to continue.

For recoverable errors that emit an E_USER_ERROR, it might be a good alternative to throw an exception instead, which makes it possible for the caller to catch it if possible.

If the pre-PHP 8.0 behavior is required:

  • Adjust the custom error handler to ignore the specific types of error. Error handlers receive the error number as its first argument.
  • Temporarily lower the error_reporting value to 0, before calling the potentially erroneous expression.

An example of a "safe" caller (shall be named "double shrug 🤷🏼‍♀️🤷🏼‍♂️ operator", but can take any name):

function 🤷🏼‍♀️🤷🏼‍♂️(callable $callable): mixed {
    $current_state = error_reporting();
    error_reporting(0);
    $return = $callable();
    error_reporting($current_state);
    return $return;
}

function load_files() {
    require_once 'file-that-does-not-exist.oops';
}

- @load_files();
+ 🤷🏼‍♀️🤷🏼‍♂️(function() {
    @load_files()
});

To reiterate, both of these approaches will stop the execution of the application; only the error will be hidden.


Implementation