PHP uses unlink() and unset() to remove things, but they work in completely different places. One deletes a file from the server. The other removes a variable or part of a variable from the current PHP scope.
The similar names have caused more than a few developers to pause before deleting something. That pause is healthy when one of the options can permanently remove a file.
Quick answer
Use unlink() to delete a file from the filesystem. Use unset() to remove a variable, array element, or accessible object property.
<?php
$filePath = __DIR__ . '/temporary-report.txt';
if (is_file($filePath)) {
unlink($filePath);
}
$user = [
'name' => 'David',
'temporary_token' => 'abc123'
];
unset($user['temporary_token']);
?>
After this code runs:
- The
temporary-report.txtfile no longer exists. - The
temporary_tokenarray element no longer exists. - The rest of the
$userarray remains unchanged.
unset() does not delete a file or clear its contents. Passing a file path stored in a variable to unset() only removes that PHP variable. The file itself remains untouched.
Difference between unlink() and unset()
| Behaviour | unlink() | unset() |
|---|---|---|
| Works with | Files in the filesystem | Variables, array elements and object properties |
| Main purpose | Deletes a file | Removes a variable from its current scope |
| Return value | true on success and false on failure |
No value |
| Failure behaviour | Raises an E_WARNING |
Unsetting an undefined variable is normally harmless |
| Can delete a directory? | No. Use rmdir() for an empty directory |
No |
| Typical example | unlink($filePath) |
unset($items[$key]) |
The PHP unlink() documentation defines it as a filesystem function that deletes a file. The PHP unset() documentation describes unset() as a language construct that destroys specified variables.
For a broader introduction to creating, reading, writing and deleting server files, see this PHP file handling guide.
How to delete a file with unlink()
The unlink() function deletes the file identified by the supplied path.
<?php
$filePath = __DIR__ . '/storage/old-report.txt';
if (!is_file($filePath)) {
echo 'The file does not exist.';
return;
}
if (!unlink($filePath)) {
echo 'The file could not be deleted.';
return;
}
echo 'The file was deleted successfully.';
?>
unlink() returns true when PHP deletes the file successfully. It returns false and raises a warning when the operation fails.
A deletion can fail for several reasons:
- The file does not exist.
- PHP does not have permission to modify the directory.
- The supplied path points to a directory instead of a file.
- Another process or an operating system restriction prevents deletion.
Checking the path before calling unlink() makes expected errors easier to handle. It does not guarantee success, however. The file could disappear or its permissions could change between the check and the deletion attempt. You should still check the return value from unlink().
Do not hide unlink() warnings with @
Older PHP examples often place the error control operator before the function:
@unlink($filePath);
This suppresses the warning, but it also hides information that may help diagnose an incorrect path or permission problem. Handle expected conditions and check the function result instead.
<?php
$filePath = __DIR__ . '/storage/old-report.txt';
if (!file_exists($filePath)) {
echo 'Nothing to delete.';
return;
}
if (!is_file($filePath) && !is_link($filePath)) {
echo 'The path is not a file.';
return;
}
if (!unlink($filePath)) {
error_log('Unable to delete file: ' . $filePath);
echo 'The file could not be deleted.';
return;
}
echo 'The file was deleted.';
?>
unlink() removes files, not directories
unlink() is not the correct function for deleting a directory. Use rmdir() to remove an empty directory. The PHPpot guide to PHP directory functions covers creating, reading and removing directories.
<?php
$directoryPath = __DIR__ . '/storage/empty-directory';
if (!is_dir($directoryPath)) {
echo 'The directory does not exist.';
return;
}
if (!rmdir($directoryPath)) {
echo 'The directory could not be removed.';
return;
}
echo 'The directory was removed.';
?>
A non-empty directory must be emptied before it can be removed. Avoid building a recursive deletion routine unless the application genuinely needs one. A small path validation mistake in such a routine can become a very efficient disaster.
What happens when the path is a symbolic link?
When unlink() receives the path of a symbolic link, it removes the link itself rather than the file or directory it points to.
<?php
$linkPath = __DIR__ . '/storage/latest-report.txt';
if (!is_link($linkPath)) {
echo 'The symbolic link does not exist.';
return;
}
if (!unlink($linkPath)) {
echo 'The symbolic link could not be removed.';
return;
}
echo 'The symbolic link was removed.';
?>
This distinction matters when an application uses symbolic links to point to shared files. Removing the link does not delete its target.
Safely delete user-selected files
Never pass an untrusted filename directly to unlink(). A value containing a directory traversal sequence such as ../ may point outside the intended upload or storage directory.
The following example accepts only a base filename and verifies that the resolved file remains inside the allowed directory.
<?php
$storageDirectory = realpath(__DIR__ . '/storage');
if ($storageDirectory === false) {
echo 'The storage directory is unavailable.';
return;
}
$requestedName = $_POST['filename'] ?? '';
$safeName = basename($requestedName);
if ($requestedName === '' || $requestedName !== $safeName) {
echo 'Invalid filename.';
return;
}
$filePath = $storageDirectory . DIRECTORY_SEPARATOR . $safeName;
$resolvedPath = realpath($filePath);
if ($resolvedPath === false || !is_file($resolvedPath)) {
echo 'The file does not exist.';
return;
}
$allowedPrefix = $storageDirectory . DIRECTORY_SEPARATOR;
if (!str_starts_with($resolvedPath, $allowedPrefix)) {
echo 'The file is outside the allowed directory.';
return;
}
if (!unlink($resolvedPath)) {
echo 'The file could not be deleted.';
return;
}
echo 'The file was deleted successfully.';
?>
This path check helps protect application-managed files, but access control is still required. Confirm that the current user owns the file or has permission to delete it before calling unlink(). A safe path is not automatically an authorised path.
How to remove variables with unset()
The unset() construct removes one or more variables from the current scope. It can also remove individual array elements and accessible object properties.
<?php
$status = 'pending';
unset($status);
var_dump(isset($status));
?>
The output is:
bool(false)
After unset($status), the variable is no longer defined in that scope.
unset() is a PHP language construct rather than a regular function. It does not return a value, so code such as the following is invalid:
<?php
$result = unset($status);
?>
Remove an array element
A common use of unset() is removing one element without affecting the rest of the array.
<?php
$user = [
'name' => 'David',
'email' => 'david@example.com',
'temporary_token' => 'abc123'
];
unset($user['temporary_token']);
print_r($user);
?>
The resulting array contains only the remaining elements:
Array
(
[name] => David
[email] => david@example.com
)
Unsetting a missing array key does not normally raise an error.
<?php
$user = [
'name' => 'David'
];
unset($user['temporary_token']);
?>
This makes unset() convenient when a key may or may not exist.
Numeric array indexes are not automatically rebuilt
When you remove an element from a numeric array, PHP preserves the remaining indexes.
<?php
$colours = ['red', 'green', 'blue'];
unset($colours[1]);
print_r($colours);
?>
The result contains a gap:
Array
(
[0] => red
[2] => blue
)
Use array_values() when the application needs sequential numeric indexes after removing an element.
<?php
$colours = ['red', 'green', 'blue'];
unset($colours[1]);
$colours = array_values($colours);
print_r($colours);
?>
The indexes are now rebuilt:
Array
(
[0] => red
[1] => blue
)
This matters when the array is later converted to JSON. A numeric PHP array with missing indexes may be encoded as a JSON object instead of a JSON array. The PHP json_encode() documentation explains that only sequential numeric arrays are encoded as JSON arrays. See converting a PHP array to JSON for more examples using array_values() and json_encode().
<?php
$items = ['one', 'two', 'three'];
unset($items[1]);
echo json_encode($items);
?>
The output is:
{"0":"one","2":"three"}
Reindex the values before encoding when the API response must contain a JSON array.
<?php
$items = ['one', 'two', 'three'];
unset($items[1]);
echo json_encode(array_values($items));
?>
["one","three"]
Remove several variables at once
unset() accepts multiple arguments.
<?php
$username = 'david';
$password = 'temporary-password';
$token = 'abc123';
unset($password, $token);
?>
The $username variable remains available, while $password and $token are removed from the current scope.
Remove an object property
You can unset a public property or another property that is accessible from the current scope.
<?php
class User
{
public string $name = 'David';
public ?string $temporaryToken = 'abc123';
}
$user = new User();
unset($user->temporaryToken);
var_dump(isset($user->temporaryToken));
?>
For a typed property without a default value, unsetting it returns the property to an uninitialised state. Reading it afterwards causes an error until a value is assigned again.
<?php
class Report
{
public string $status = 'draft';
}
$report = new Report();
unset($report->status);
// $report->status is now uninitialised.
$report->status = 'published';
echo $report->status;
?>
Use this behaviour carefully. Setting a nullable property to null is often clearer than unsetting it when the application expects the property to remain readable.
unset() and variable scope
unset() affects the variable in the scope where it is executed. Unsetting a local variable inside a function does not remove a variable with the same name outside that function. This follows PHP’s normal variable scope rules.
<?php
$message = 'Available outside the function';
function clearLocalMessage(): void
{
$message = 'Available inside the function';
unset($message);
}
clearLocalMessage();
echo $message;
?>
The outer $message still exists because it belongs to a different scope.
A global variable accessed with the global keyword behaves differently. Unsetting the local alias does not remove the value stored in the $GLOBALS array.
<?php
$status = 'active';
function clearStatusAlias(): void
{
global $status;
unset($status);
}
clearStatusAlias();
echo $GLOBALS['status'];
?>
The output is still active. To remove the actual global entry, unset it through $GLOBALS.
<?php
$status = 'active';
unset($GLOBALS['status']);
?>
unset() does not immediately guarantee memory release
Developers sometimes use unset() expecting PHP to return the variable’s memory to the operating system immediately. That is not what unset() promises.
It removes the variable name from the current scope. PHP can release the underlying value when nothing else refers to it, but memory management and garbage collection are handled internally.
<?php
$firstList = range(1, 1000);
$secondList = $firstList;
unset($firstList);
echo count($secondList);
?>
Removing $firstList does not remove $secondList. The second variable still contains the data.
Unsetting a reference breaks only that variable binding
When two variables are references to the same value, unsetting one variable does not unset the other.
<?php
$primaryStatus = 'active';
$linkedStatus =& $primaryStatus;
unset($primaryStatus);
echo $linkedStatus;
?>
The output is:
active
unset($primaryStatus) removes that variable name. It does not destroy the value still accessible through $linkedStatus.
Common unlink() and unset() mistakes
Using unset() to delete a file
Unsetting a variable that contains a file path removes only the variable.
<?php
$filePath = __DIR__ . '/storage/report.txt';
unset($filePath);
?>
The report.txt file remains on the server. Use unlink() when the intention is to delete it.
<?php
$filePath = __DIR__ . '/storage/report.txt';
if (is_file($filePath) && !unlink($filePath)) {
echo 'The file could not be deleted.';
}
?>
Calling unlink() with an empty or incorrect path
A dynamically built path may be empty or point somewhere unexpected. Validate it before deletion.
<?php
$filePath = trim($_POST['file_path'] ?? '');
if ($filePath === '') {
echo 'A file path is required.';
return;
}
if (!is_file($filePath)) {
echo 'The requested file was not found.';
return;
}
if (!unlink($filePath)) {
echo 'The file could not be deleted.';
}
?>
This example demonstrates basic validation, but applications should not normally accept unrestricted filesystem paths from a request. Use an application-controlled directory and a validated file identifier instead.
Checking only file_exists() before unlink()
file_exists() returns true for both files and directories. If the application expects a regular file, use is_file().
<?php
$filePath = __DIR__ . '/storage/report.txt';
if (!is_file($filePath)) {
echo 'The path is not a regular file.';
return;
}
if (!unlink($filePath)) {
echo 'The file could not be deleted.';
}
?>
Symbolic links may need separate handling with is_link(), depending on whether the application allows them.
Assuming unset() changes another copied variable
A normal assignment creates a separate variable. Unsetting the original variable does not unset the copy.
<?php
$originalName = 'report.pdf';
$copiedName = $originalName;
unset($originalName);
echo $copiedName;
?>
The output remains report.pdf.
Using array_splice() when unset() is clearer
For an associative array, unset() is usually the clearest way to remove a known key.
<?php
$settings = [
'theme' => 'dark',
'debug' => true,
'timezone' => 'UTC'
];
unset($settings['debug']);
?>
array_splice() is more suitable when removing a range from a numerically indexed array. It also reindexes the remaining numeric elements.
<?php
$steps = ['create', 'review', 'approve', 'publish'];
array_splice($steps, 1, 1);
print_r($steps);
?>
The result is:
Array
(
[0] => create
[1] => approve
[2] => publish
)
unset() versus assigning null
unset($value) and $value = null are not identical.
<?php
$firstValue = 'example';
$secondValue = 'example';
unset($firstValue);
$secondValue = null;
var_dump(isset($firstValue));
var_dump(isset($secondValue));
var_dump(array_key_exists('secondValue', get_defined_vars()));
?>
Both variables return false with isset(), because PHP isset() also returns false for a variable containing null.
The difference is that $firstValue no longer exists, while $secondValue still exists and contains null.
The distinction is especially useful with arrays:
<?php
$data = [
'removed_value' => 'temporary',
'nullable_value' => 'temporary'
];
unset($data['removed_value']);
$data['nullable_value'] = null;
var_dump(isset($data['removed_value']));
var_dump(isset($data['nullable_value']));
var_dump(array_key_exists('removed_value', $data));
var_dump(array_key_exists('nullable_value', $data));
?>
array_key_exists() returns false for the removed key and true for the key whose value is null.
Use unset() when the key or variable should no longer exist. Assign null when its presence still has meaning, but no value is currently available.
When to use unlink() and unset()
The choice is straightforward once you identify what must be removed.
- Use
unlink()when a file must be deleted from the filesystem. - Use
unset()when a variable, array element, or accessible object property must stop existing in the current scope. - Use
rmdir()when an empty directory must be removed. - Assign
nullwhen a variable or array key should remain present but have no value.
<?php
$filePath = __DIR__ . '/storage/report.txt';
$user = [
'name' => 'David',
'temporary_token' => 'abc123'
];
if (is_file($filePath)) {
unlink($filePath);
}
unset($user['temporary_token']);
$user['last_login'] = null;
?>
In this example, the file is deleted, the temporary token key is removed, and the last_login key remains available with a null value.
Developer FAQ
Does unset() delete a file?
No. unset() removes a PHP variable or part of a variable. It does not affect a file stored on the server.
Can unlink() delete a variable?
No. unlink() expects a filesystem path. Use unset() to remove a variable.
Does unlink() permanently delete a file?
Yes, from the application’s point of view. PHP does not move the file to a recycle bin. Recovery depends on the operating system, storage system, or available backups.
Why does unlink() return a permission denied warning?
The PHP process may not have permission to modify the directory containing the file. On most systems, deleting a file depends mainly on directory permissions, not only the file’s own permissions.
Should I use file_exists() before unlink()?
You can use it to provide a clearer response when the file is missing. For regular files, is_file() is often more precise. You must still check the return value from unlink() because the deletion can fail after the check.
Does unset() reindex an array?
No. Numeric indexes are preserved. Use array_values() when you need sequential indexes after removing an element.
Is unset() the same as assigning null?
No. unset() removes the variable or key. Assigning null keeps it present with a null value.
Conclusion
unlink() and unset() solve different problems. unlink() deletes a filesystem entry, while unset() removes a variable, array element, or accessible property from the current PHP scope.
The important distinction is not their syntax. It is the destination of the deletion. Before writing either function, ask whether you are removing data from PHP memory or deleting a file from storage. That small check prevents a surprisingly large class of mistakes.
Excellent article. The best of the best!
Thank you Jadeja.