The most useful handler throws an ErrorException, whose getSeverity() keeps the original level. A missing file then stops the code path, runs finally blocks and reaches the same catch and logging as any other failure. Laravel 2,157 and Symfony 6,093 install such a handler at startup (Laravel).
<?php
set_error_handler(function (int $no, string $msg, string $file, int $line): bool {
if (!(error_reporting() & $no)) return false; // silenced by @ or masked out
throw new ErrorException($msg, 0, $no, $file, $line);
});
try {
$csv = file_get_contents('prices.csv');
} catch (ErrorException $e) {
echo $e::class, " (severity {$e->getSeverity()}): {$e->getMessage()}\n";
}
var_dump(@file_get_contents('prices.csv')); // @ still means "return false quietly"Output
ErrorException (severity 2): file_get_contents(prices.csv): Failed to open stream: No such file or directory bool(false)
Inside @, error_reporting() returns 4437, the fatal levels only, so the handler steps aside; without that test every @ in your libraries would throw. Convert E_DEPRECATED in tests, never in production, or a PHP upgrade becomes an outage.