is_link
Tells whether a given path is a symbolic link, without following it.
is_link(string $filename): bool- Added in PHP 4
Parameters
| Parameter | Type | Description |
|---|---|---|
$filename | string | Path to check. The link itself is examined; it is not resolved to its target. |
Return value
booltrue if the path exists and is a symbolic link, false otherwise.
Errors
Throws a ValueError if the path contains a null byte; emits E_WARNING when the path cannot be checked, for example under an open_basedir restriction.
is_link is the one member of the is_* family that does not follow symbolic links — it asks about the entry itself. Everything else (is_file, is_dir, file_exists, filesize) resolves the link and reports on the target.
When to reach for it
Three situations matter. Deletion: a symlink to a directory satisfies is_dir, but rmdir fails on it — unlink is what removes a link. Check is_link first in any recursive delete, or you will follow the link and wipe out the target's contents instead.
Detecting broken links: if the target is gone, file_exists and is_file both return false while is_link returns true. That combination is the definition of a dangling link.
Atomic deploys: capistrano-style layouts point a current symlink at a release directory; is_link plus readlink tells you which release is live.
What trips people up
A path can be a link and a directory at the same time — both is_link and is_dir return true — so order your checks with is_link first. Hard links are not symbolic links and always report false; look at nlink in stat for those. Results come from the stat cache, and on Windows only NTFS symlinks and junctions are recognised.
Example
When to use it
- Stop a recursive delete from descending into a symlinked directory and destroying the target.
- Find dangling links whose target was removed, where file_exists reports false but the entry is still there.
- Read which release directory a deploy's current symlink currently points at.
Frequently asked questions
What is the difference between is_link and is_file?
is_link reports on the entry itself and is true only for symbolic links, while is_file follows the link and reports on its target. A symlink to a file makes both true; a symlink to a directory makes is_link true and is_file false.
How do I find broken symbolic links in PHP?
A link is dangling when is_link($path) is true but file_exists($path) is false, because file_exists resolves the link and the target is gone. Use readlink() to see where it was pointing.
Does is_link detect hard links?
No. A hard link is just another directory entry for the same inode and is indistinguishable from the original file, so is_link returns false. Check the nlink field from stat() to see whether a file has more than one hard link.
More examples
Removing a path that might be a link
is_link is tested before is_dir because a symlink to a directory satisfies both, and rmdir() refuses to delete a link. Putting the checks in this order is what stops a cleanup routine following a link and deleting somebody else's files.
<?php
function removeSafely(string $p): string {
if (is_link($p)) { unlink($p); return "unlinked symlink"; }
if (is_dir($p)) { rmdir($p); return "removed directory"; }
if (is_file($p)) { unlink($p); return "removed file"; }
return "nothing there";
}
mkdir("cache_dir");
file_put_contents("cache.txt", "x");
echo removeSafely("cache_dir"), "\n";
echo removeSafely("cache.txt"), "\n";
echo removeSafely("ghost"), "\n";Spotting a dangling link
file_exists() follows the link, so it is false for both a missing path and a broken link. Combining it with is_link() separates the two cases; readlink() is only called on the branch where the path is known to be a link, so it never emits a warning.
<?php
$path = "current";
$state = match (true) {
is_link($path) && !file_exists($path) => "dangling symlink",
is_link($path) => "symlink -> " . readlink($path),
file_exists($path) => "regular path",
default => "absent",
};
echo $state, "\n";lstat sees the link, stat sees the target
For a regular file the two agree, both reporting 40 bytes. If this path were a symlink, stat() would still report the target's 40 bytes while lstat() would report the tiny size of the link entry itself — that divergence is exactly what is_link() detects.
<?php
file_put_contents("target.txt", str_repeat("x", 40));
$s = stat("target.txt");
$l = lstat("target.txt");
echo "is_link: ", var_export(is_link("target.txt"), true), "\n";
echo "stat size: ", $s['size'], "\n";
echo "lstat size: ", $l['size'], "\n";
unlink("target.txt");
Discussion