PHP unlink() handling the exception
Asked Answered
C

9

46

Well, I have been wondering if I can handle the unlink() function properly. I dont want the unlink() function to throw some nasty error if it is unable to unlink the file (may be due to the File not found).

I tried something like

try { 
    unlink("secret/secret.txt"); 
} catch(Exception $e) { 
    print "whoops!"; 
    //or even leaving it empty so nothing is displayed
} 

But it is not working. I am no expert in PHP. I searched and found this exception handling code somewhere in the web. But as I can remember my school days, the same was used for Java. SO it should have worked. I dont know whats wrong with the code.

Or can I simply use a if..else statement like

if(unlink($file)){
  //leaving here empty must ensure that nothing is displayed
}else{
  //leaving here empty must ensure that nothing is displayed
}

But this code isnt working either. Where am I doing the mistake? What are the other ways to handle it properly?

Can the errors be hidden by manipulating with the error reporting (PHP) (Production and Development environment) ??

Chacon answered 10/3, 2013 at 2:28 Comment(3)
Exceptions are not universal in PHP. Only some APIs implement them, and unlink() will return FALSE if it can't remove the file. The if/else is the correct method.Oviparous
perhaps test with file_exists first before feeding to unlinkDrobman
Test for the existence of the file, that it is in fact a file, and that you have permissions to delete it. I don't love using @ to suppress errors, but I love errors even less. if ( is_file( $uri ) && is_writable( $uri ) ) { @unlink( $uri ); }Chancellery
R
29

unlink doesn't throw exceptions, in generates errors. The proper way to do this is check that the file exists before trying to call unlink on it. If you are merely worried about not having the errors output then you should just turn off display_errors which you should always do in a production environment anyway. Then they will just be logged.

Do not suppress errors with the @, its rarely advisable.

Can you be more descriptive about @

Im not sure what you mean exactly. But the documentation is here. As far as why you don't want to use it... That is because then you never know that code isn't working or is problematic. Even if the code still works from a functional perspective it's still got an issue and that issue could potentially make something else completely not work at some point. If you never have the error you'll probably waste a lot of time debugging.

Its fine to change your log level or disable the display of errors, but you never want to completely suppress them.

Rigdon answered 10/3, 2013 at 2:32 Comment(4)
Yea thank you very much, your answer also helped me with the concept of @. Can you be more descriptive about @ ?Chacon
With a high traffic site, if (file_exists($path)) unlink($path); will still throw errors, since another process has deleted the file in between the check/unlink.Pavid
the problem with checking if the file exists is that if the another process is using the file the warning will still show.Throckmorton
You can still use @, but you need to check for errors afterward and a FALSE return value.Germanic
M
51

NOTE: This probably won't work any longer. See Brian's comment

If you want to only suppress the error, you can do this:

@unlink('your_file_name');

Generally, in PHP, @ will suppress any error.

The better way is minimize the error probability. You've say that one of error possibility is caused by non-exist file. If I were you, I'll do this:

if(file_exists('your_file_name')){
    unlink('your_file_name');
}else{
    echo 'file not found';
}

Good luck :)

Microseism answered 10/3, 2013 at 2:30 Comment(10)
True that will surpress any error... and thats not a good thing... -1Rigdon
See my edited answer. Also, the question is just about surpressing the error. "Can the errors be hidden by manipulating with the error reporting (PHP) (Production and Development environment) ??"Microseism
yes, except suggesting @ is a bad idea. you'd rather code it properly than hide errors.Drobman
The problem is that there's a race condition, the file could exist when file_exists is called, but then another process removes the file before the unlink function is called. So using @ may be the best option.Legwork
my suggestion for people in the comments, find and replace @ with /*@*/ when debugging.Apiculate
@Apiculate - Better to write the code so it works well, both during development and production. See this answer. That gives a proper place to log / throw exception. And you can put a breakpoint on the failed branch during development.African
@African It's been a while since I used PHP and wrote that comment, but doesn't unlink already return false on failure, meaning there is already an easy way to handle the error? Also, wasn't the problem that @ had to be used to suppress the error when there is already an if statement to handle it (assuming that this is what you meant by "write the code so it works well")?Apiculate
@Apiculate - Yes, mostly. Instead of altering the code while debugging, write code that deals with the possibilities, without emitting the error. (If this weren't possible, then what you suggest would be a good work-around. Sorry if I sounded dismissive of your good suggestion.) In "See this answer" I linked to one possible solution to "write the code so it works well ...". That answer keeps the "@", but then re-tests whether the file is still there. This makes it possible to handle failed unlink, without the pesky error message. :)African
@African Ah OK, I guess we could say that this is a bad design choice on PHP's part to emit an error (that seems like it isn't an exception that can be caught) when the function already returns false to report the error, and the only way to suppress the error is to either disable all error reporting, put @ in front of it or use an extra if statement.Apiculate
Looks to me as if @unlink(..) doesn't suppress the unlink warning at all, at least in PHP 7.4.20. I'm fairly confident we didn't get a warning in the past.Peruse
R
29

unlink doesn't throw exceptions, in generates errors. The proper way to do this is check that the file exists before trying to call unlink on it. If you are merely worried about not having the errors output then you should just turn off display_errors which you should always do in a production environment anyway. Then they will just be logged.

Do not suppress errors with the @, its rarely advisable.

Can you be more descriptive about @

Im not sure what you mean exactly. But the documentation is here. As far as why you don't want to use it... That is because then you never know that code isn't working or is problematic. Even if the code still works from a functional perspective it's still got an issue and that issue could potentially make something else completely not work at some point. If you never have the error you'll probably waste a lot of time debugging.

Its fine to change your log level or disable the display of errors, but you never want to completely suppress them.

Rigdon answered 10/3, 2013 at 2:32 Comment(4)
Yea thank you very much, your answer also helped me with the concept of @. Can you be more descriptive about @ ?Chacon
With a high traffic site, if (file_exists($path)) unlink($path); will still throw errors, since another process has deleted the file in between the check/unlink.Pavid
the problem with checking if the file exists is that if the another process is using the file the warning will still show.Throckmorton
You can still use @, but you need to check for errors afterward and a FALSE return value.Germanic
L
26

This method may seem strange but I believe it is the most foolproof one, it accounts for "race conditions".

is_file

if(is_file($file) && @unlink($file)){
    // delete success
} else if (is_file ($file)) {
    // unlink failed.
    // you would have got an error if it wasn't suppressed
} else {
  // file doesn't exist
}

Why?

Firstly is_file is the correct method to check if a FILE exists not file_exists. file_exists checks for both directories and files so may return the TRUE for a directory with the same filename, you cannot remove a directory with unlink and doing so will throw an error.

Checking a file exists(is_file) before you unlink is the correct/best way to delete a file.

if(is_file($file) && unlink($file)){

But it is not a foolproof method as it is common for a file to be deleted in the small window between the is_file check and the unlink. I have experianced this several times when a caching method uses the filesystem.

But it is best method available.

So you can do everything right and still get an error!

Well at least the error tells you if it fails....well actually you can tell if it fails without the error

unlink

Returns TRUE on success or FALSE on failure.

If you have coded it correctly and can differentiate between a success and failed unlink then YES suppress the error, it does not benifit you or your code.

Whether the error is suppressed or not, This is the best method i can think of to prevent it from happening. By reducing the time between the check and the delete you will reduce the likeliness of it throwing an error.

EDIT: updated link URLs

Lakendra answered 25/11, 2015 at 9:59 Comment(3)
Don't need to use is_file twice. More straightforward: if (is_file($file) { if (@unlink($file)) { ...success... } else { ...unlink failed...} } else { ...file doesn't exist... }African
@African Ordinarily, you'd be right, but in this case I think the extra check is necessary because of the race condition that is mentioned in this answer and others. If unlink() fails, it might be because the file did exist when it was first checked, but was deleted in the short window before unlink() was then called. Therefore, calling is_file() again is necessary to ask if even after unlink() failed, does the file still exist, which would let us know if the file doesn't exist or if there was an error that should cause more concern.Gibbeon
@ChrisMorbitzer - I see your point. If you don't need/want to distinguish between "failed-but-file-gone-anyway" (meaning it disappeared while you were trying to delete it) and "file never existed", then the code in answer is good. Whereas my suggested code would say "unlink failed", even if is now gone - might not be good to say that. Best to comment the code carefully, to explain why it is being done that way. OTOH, if want to log that the race condition happened, then could add that second is_file into my version, in ...unlink failed... branch.African
S
7

You can use is_writable to test whether you have proper permissions to modify or delete a file.

http://php.net/manual/en/function.is-writable.php

try {
  if(!is_writable($file))
      throw new Exception('File not writable');

  unlink($file);
}
catch(Exception $e) { /* do what you want */ }
Solidarity answered 22/4, 2016 at 23:5 Comment(1)
While this is a nice idea, is_writable() only tells you whether the file itself is writable. On Linux (and I presume probably Windows), you need write permission on the actual folder containing the file to be able to remove it, not the file itself. In fact, on Linux you can happily remove files you cannot write (not sure about Windows) so long as you have write permission to the folder.Peruse
B
5

My experience says that calling file_exists() just before calling unlink() does NOT work, even if clearstatcache() was called just before calling file_exists().

There are many combinations of PHP versions and operating systems and the only way that I have found that always works (that is, avoids showing the warning message in case of error) is making my own function silent_unlink():

function silent_unlink( $filename )
{
  $old_er = error_reporting();
  error_reporting( $old_er & ~E_WARNING );
  $result = unlink( $filename );
  error_reporting( $old_er );
  return $result;
}

It disables the error reporting of warnings just for calling unlink() and restores the previous error_reporting() status.

Banner answered 31/7, 2018 at 15:21 Comment(2)
Isn't this essentially the same as putting "@" on unlink? Have you encountered situations where "@" didn't suppress the error? OTOH, one benefit I see of calling a custom function like this, is it makes it easy to do custom logging, or to set a breakpoint during development. But for production, why not make the body of the method one line: return @unlink( $filename );?African
No, @ did not work for me. It worked for the rest of functions but not for unlink. The last time that I checked that it did not work for me I was working with PHP 5.4 on Apache 2.4 Windows. As said, I tried a lot of things (using @ was my first option) and the mentioned function was the only way it worked. On every tested server.Banner
J
2

Handling "Resource Unavailable" error by unlink() as Exception using try catch

Even is_file() or file_exists() will check for file is exists or not, there are chances that file is being used by some applications that will prevent deletion and unlink() will display "Resource Unavailable" error.

So after trying many methods like: is_resource(), is_writable(), stream_get_meta_data()...etc, I reached the only best way to handle error while "deleting" a file that is either not exists or is exists but being used by some application

function delete_file($pFilename)
{
    if ( file_exists($pFilename) ) { 
        //  Added by [email protected]
        //  '@' will stop displaying "Resource Unavailable" error because of file is open some where.
        //  'unlink($pFilename) !== true' will check if file is deleted successfully.
        //  Throwing exception so that we can handle error easily instead of displaying to users.
        if( @unlink($pFilename) !== true )
            throw new Exception('Could not delete file: ' . $pFilename . ' Please close all applications that are using it.');
    }   
    return true;
}

=== USAGE ===

try {
    if( delete_file('hello_world.xlsx') === true )
        echo 'File Deleted';
}
catch (Exception $e) {
    echo $e->getMessage(); // will print Exception message defined above.
}
Judah answered 29/9, 2018 at 17:23 Comment(0)
I
1

Use PHP_Exceptionizer https://github.com/DmitryKoterov/php_exceptionizer/blob/master/lib/PHP/Exceptionizer.php

$exceptionizer = new PHP_Exceptionizer(E_ALL);
try {
        unlink($file)
    }  catch (E_WARNING $e) {
        return false;
    }
Imphal answered 15/10, 2016 at 15:35 Comment(0)
M
1
if (file_exists($file) && is_writable($file)) {
    return  unlink($file);
}
Monkery answered 31/10, 2021 at 10:3 Comment(0)
L
1

Error suppression operator is said to be costly, it has the potential drawback of hiding unexpected errors and the problem it solves almost always has better solutions.

In this case, though, we're already doing a non-trivial I/O operation (thus added processing time may not be a big deal), we're calling an individual builtin function (which limits the error masking issue) and there's no way to prevent the warning from triggering. If we're able to trap the warning message, perhaps we have a legit use case for @:

$success = @unlink($path);
$errorInfo = error_get_last();
if ($success === false) {
    $error = $errorInfo['message'] ?? 'unlink() failed';
    throw new \RuntimeException("Failed to delete file $path: $error");
}
Lieu answered 15/9, 2022 at 8:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.