- Saved searches
- Use saved searches to filter your results more quickly
- PHP 8 — Fatal error «Path cannot be empty» — file_get_contents(») at /inc/Engine/Optimization/AbstractOptimization.php #3422
- PHP 8 — Fatal error «Path cannot be empty» — file_get_contents(») at /inc/Engine/Optimization/AbstractOptimization.php #3422
- Comments
- file_get_contents => PHP Fatal error: Allowed memory exhausted
- Solution 2
- Handling I/O errors in PHP
- How file_get_contents fails
- Error handling in four steps
- Step 1: Detect that the file was not read
- Step 2: Suppress the warning
- Step 3: Get the reason for the failure
- Step 4: Add a fallback
- How not to handle errors
- Conclusion
- Related
- One Reply to “Handling I/O errors in PHP”
- Leave a Reply
- Recent Posts
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PHP 8 — Fatal error «Path cannot be empty» — file_get_contents(») at /inc/Engine/Optimization/AbstractOptimization.php #3422
PHP 8 — Fatal error «Path cannot be empty» — file_get_contents(») at /inc/Engine/Optimization/AbstractOptimization.php #3422
effort: [XS] < 1 day of estimated development time module: file optimization PHP8 priority: high Issues which should be resolved as quickly as possible type: bug Indicates an unexpected problem or unintended behavior
Comments
Before submitting an issue please check that you’ve completed the following steps:
- Made sure you’re on the latest version ✅
- Used the search feature to ensure that the bug hasn’t been reported before ✅
Describe the bug
In PHP 8, trying to get the content of a path that doesn’t exist throws a fatal error.
It is happening when we pass an empty path here for example:
https://github.com/wp-media/wp-rocket/blob/master/inc/Engine/Optimization/AbstractOptimization.php#L199
#0 /opt/httpd/htdocs/wp-admin/includes/class-wp-filesystem-direct.php(39): file_get_contents('') #1 /opt/httpd/htdocs/wp-content/plugins/wp-rocket/inc/Engine/Optimization/AbstractOptimization.php(199): WP_Filesystem_Direct->get_contents(false) #2 /opt/httpd/htdocs/wp-content/plugins/wp-rocket/inc/Engine/Optimization/Minify/CSS/Combine.php(296): WP_Rocket\\Engine\\Optimization\\AbstractOptimization->get_file_content(false) #3 /opt/httpd/htdocs/wp-content/plugins/wp-rocket/inc/Engine/Optimization/Minify/CSS/Combine.php(217): WP_Rocket\\Engine\\Optimization\\Minify\\CSS\\Combine->get_content('/opt/httpd/htdo. ') #4 /opt/httpd/htdocs/wp-content/plugins/wp-rocket/inc/Engine/Optimization/Minify/CSS/Combine.php(73): WP_Rocket\\Engine\\Optimization\\Minify\\CSS\\Combine->combine() #5 /opt/httpd/htdocs/wp-content/plugins/wp-rocket/inc/Engine/Optimization/Minify/CSS/Subscriber.php(53): WP_Rocket\\Engine\\Optimization\\Minify\\CSS\\Combine->optimize('. ') #6 /opt/httpd/htdocs/wp-includes/class-wp-hook.php(287): WP_Rocket\\Engine\\Optimization\\Minify\\CSS\\Subscriber->process('. ') #7 /opt/httpd/htdocs/wp-includes/plugin.php(212): WP_Hook->apply_filters('. ', Array)\n #8 /opt/httpd/htdocs/wp-content/plugins/wp-rocket/inc/classes/Buffer/class-optimization.php(104): apply_filters('rocket_buffer', '. ') #9 [internal function]: WP_Rocket\\Buffer\\Optimization->maybe_process_buffer('. ', 9)\n #10 /opt/httpd/htdocs/wp-includes/functions.php(4755): ob_end_flush() #11 /opt/httpd/htdocs/wp-includes/class-wp-hook.php(287): wp_ob_end_flush_all('') #12 /opt/httpd/htdocs/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters(NULL, Array) #13 /opt/httpd/htdocs/wp-includes/plugin.php(484):!WP_Hook->do_action(Array) #14 /opt/httpd/htdocs/wp-includes/load.php(1052): do_action('shutdown') #15 [internal function]: shutdown_action_hook() #16 thrown in /opt/httpd/htdocs/wp-admin/includes/class-wp-filesystem-direct.php on line 39
To Reproduce
Steps to reproduce the behavior:
- On a server running PHP 8 and WP Rocket
- Pass a path that doesn’t exist here:
- It will fail on line 199 with this error: https://snippi.com/s/ql270c6
Expected behavior
Per Remy’s comment here,
We can guard against it by adding an additional check, to prevent trying to get the content of a path that doesn’t exist
Backlog Grooming (for WP Media dev team use only)
- Reproduce the problem
- Identify the root cause
- Scope a solution
- Estimate the effort
The text was updated successfully, but these errors were encountered:
file_get_contents => PHP Fatal error: Allowed memory exhausted
Firstly you should understand that when using file_get_contents you’re fetching the entire string of data into a variable, that variable is stored in the hosts memory.
If that string is greater than the size dedicated to the PHP process then PHP will halt and display the error message above.
The way around this to open the file as a pointer, and then take a chunk at a time. This way if you had a 500MB file you can read the first 1MB of data, do what you will with it, delete that 1MB from the system’s memory and replace it with the next MB. This allows you to manage how much data you’re putting in the memory.
An example if this can be seen below, I will create a function that acts like node.js
function file_get_contents_chunked($file,$chunk_size,$callback) < try < $handle = fopen($file, "r"); $i = 0; while (!feof($handle)) < call_user_func_array($callback,array(fread($handle,$chunk_size),&$handle,$i)); $i++; >fclose($handle); > catch(Exception $e) < trigger_error("file_get_contents_chunked::" . $e->getMessage(),E_USER_NOTICE); return false; > return true; >
$success = file_get_contents_chunked("my/large/file",4096,function($chunk,&$handle,$iteration) < /* * Do what you will with the here * is passed in case you want to seek ** to different parts of the file * is the section of the file that has been read so * ($i * 4096) is your current offset within the file. */ >); if(!$success) < //It Failed >
One of the problems you will find is that you’re trying to perform regex several times on an extremely large chunk of data. Not only that but your regex is built for matching the entire file.
With the above method your regex could become useless as you may only be matching a half set of data. What you should do is revert to the native string functions such as
for matching the strings, I have added support in the callback so that the handle and current iteration are passed. This will allow you to work with the file directly within your callback, allowing you to use functions like fseek , ftruncate and fwrite for instance.
The way you’re building your string manipulation is not efficient whatsoever, and using the proposed method above is by far a much better way.
Solution 2
A pretty ugly solution to adjust your memory limit depending on file size:
$filename = "yourfile.txt"; ini_set ('memory_limit', filesize ($filename) + 4000000); $contents = file_get_contents ($filename);
The right solutuion would be to think if you can process the file in smaller chunks, or use command line tools from PHP.
If your file is line-based you can also use fgets to process it line-by-line.
Handling I/O errors in PHP
This blog post is all about how to handle errors from the PHP file_get_contents function, and others which work like it.
The file_get_contents function will read the contents of a file into a string . For example:
You can try this out on the command-line like so:
$ echo "hello" > hello.txt $ php test.php hello
This function is widely used, but I’ve observed that error handling around it is often not quite right. I’ve fixed a few bugs involving incorrect I/O error handling recently, so here are my thoughts on how it should be done.
How file_get_contents fails
For legacy reasons, this function does not throw an exception when something goes wrong. Instead, it will both log a warning, and return false .
Which looks like this when you run it:
$ php test.php PHP Warning: file_get_contents(not-a-real-file.txt): failed to open stream: No such file or directory in test.php on line 3 PHP Stack trace: PHP 1. () test.php:0 PHP 2. file_get_contents() test.php:3
Warnings are not very useful on their own, because the code will continue on without the correct data.
Error handling in four steps
If anything goes wrong when you are reading a file, your code should be throwing some type of Exception which describes the problem. This allows developers to put a try <> catch <> around it, and avoids nasty surprises where invalid data is used later.
Step 1: Detect that the file was not read
Any call to file_get_contents should be immediately followed by a check for that false return value. This is how you know that there is a problem.
This now gives both a warning and an uncaught exception:
$ php test.php PHP Warning: file_get_contents(not-a-real-file.txt): failed to open stream: No such file or directory in test.php on line 3 PHP Stack trace: PHP 1. () test.php:0 PHP 2. file_get_contents() test.php:3 PHP Fatal error: Uncaught Exception: File was not loaded in test.php:5 Stack trace: #0 thrown in test.php on line 5
Step 2: Suppress the warning
Warnings are usually harmless, but there are several good reasons to suppress them:
- It ensures that you are not depending on a global error handler (or the absence of one) for correct behaviour.
- The warning might appear in the middle of the output, depending on php.ini .
- Warnings can produce a lot of noise in the logs
Use @ to silence any warnings from a function call.
The output is now only the uncaught Exception :
$ php test.php PHP Fatal error: Uncaught Exception: File was not loaded in test.php:5 Stack trace: #0 thrown in test.php on line 5
Step 3: Get the reason for the failure
Unfortunately, we lost the “No such file or directory” message, which is pretty important information, which should go in the Exception . This information is retrieved from the old-style error_get_last method.
This function might just return empty data, so you should check that everything is set and non-empty before you try to use it.
This now embeds the failure reason directly in the message.
$ php test.php PHP Fatal error: Uncaught Exception: File 'not-a-real-file.txt' was not loaded. file_get_contents(not-a-real-file.txt): failed to open stream: No such file or directory in test.php:9 Stack trace: #0 thrown in test.php on line 9
Step 4: Add a fallback
The last time I introduced error_clear_last() / get_last_error() into a code-base, I learned out that HHVM does not have these functions.
Call to undefined function error_clear_last()
The fix for this is to write some wrapper code, to verify that each function exists.
echo $text; /** * Call error_clear_last() if it exists. This is dependent on which PHP runtime is used. */ function clearLastError() < if (function_exists('error_clear_last')) < error_clear_last(); >> /** * Retrieve the message from error_get_last() if possible. This is very useful for debugging, but it will not * always exist or return anything useful. */ function getLastErrorOrDefault(string $default) < if (function_exists('error_clear_last')) < $e = error_get_last(); if (isset($e) && isset($e['message']) && $e['message'] != "") < return $e['message']; >> return $default; >
This does the same thing as before, but without breaking other PHP runtimes.
$ php test.php PHP Fatal error: Uncaught Exception: Could not retrieve image data from 'not-a-real-file'. file_get_contents(not-a-real-file): failed to open stream: No such file or directory in test.php:7 Stack trace: #0 thrown in test.php on line 7
Since HHVM is dropping support for PHP, I expect that this last step will soon become unnecessary.
How not to handle errors
Some applications put a series of checks before each I/O operation, and then simply perform the operation with no error checking. An example of this would be:
if(!is_file($filename)) < throw new Exception("$filename is not a file"); >if(!is_readable($filename)) < throw new Exception("$filename cannot be read"); >// Assume that nothing can possibly go wrong.. $text = @file_get_contents($filename); echo $text;
You could probably make a reasonable-sounding argument that checks are a good idea, but I consider them to be misguided:
- If you skip any actual error handling, then your code is going to fail in more surprising ways when you encounter an I/O problem that could not be detected.
- If you do perform correct error handling as well, then the extra checks add nothing other than more branches to test.
Lastly, beware of false positives. For example, the above snippet will reject HTTP URL’s, which are perfectly valid for file_get_contents .
Conclusion
Most PHP code now uses try / catch / finally blocks to handle problems, but the ecosystem really values backwards compatibility, so existing functions are rarely changed.
The style of error reporting used in these I/O functions is by now a legacy quirk, and should be wrapped to consistently throw a useful Exception .
Related
One Reply to “Handling I/O errors in PHP”
you can use this library which is a wrapper aroud all php functions :
https://github.com/thecodingmachine/safe