is_link

Tells whether a given path is a symbolic link, without following it.

Signature
is_link(string $filename): bool
  • Added in PHP 4

Parameters

ParameterTypeDescription
$filenamestringPath 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · php
<?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.

Example · php
<?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.

Example · php
<?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

  • Be the first to comment on this lesson.