はじめに
Webアプリケーションを開発していると、アップロードされた一時ファイルの削除、古いログファイルのクリーンアップ、キャッシュファイルの破棄など、「ファイルシステム上のファイルを削除したい」という場面は頻繁に発生します。
PHPでこの処理を行うための基本的な関数が unlink() です。Unix系OSにおける unlink システムコールに由来する名前で、指定したパスのファイルを削除します。シンプルな関数に見えますが、権限エラーの扱い、ディレクトリには使えないという制約、削除の失敗をどう検知するかなど、実務では気をつけるべきポイントがいくつも存在します。本記事では基本的な使い方から、安全な削除処理の実装パターンまで詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | unlink() |
| 所属拡張 | コア関数(標準で常に利用可能) |
| シグネチャ | unlink(string $filename, ?resource $context = null): bool |
| 引数1 | $filename — 削除対象のファイルパス |
| 引数2 | $context — ストリームコンテキスト(省略可) |
| 戻り値 | 成功時に true、失敗時に false |
| 対応バージョン | PHP 4以降 |
| 対象 | ファイルのみ(ディレクトリの削除には使えない) |
| 関連関数 | rmdir()(空のディレクトリを削除)、file_exists()(存在確認) |
削除処理の流れ(イメージ図)
削除対象のファイル
"/var/www/uploads/temp_12345.jpg"
│
▼
┌──────────────────┐
│ file_exists()で │ ← 事前チェック(推奨)
│ 存在確認するのが安全 │
└──────────────────┘
│
▼
unlink($path)
│
┌──────────┴──────────┐
│ 成功 → true が返る │
│ 失敗 → false + E_WARNING │
│ (権限不足・ファイル無しなど)│
└──────────┬──────────┘
▼
ファイルシステムから削除される
(ゴミ箱には入らず即座に削除)
ポイントは、**unlink() による削除は元に戻せない(ゴミ箱に移動するのではなく即座に削除される)**という点です。また、ファイルが存在しない場合や書き込み権限がない場合は false を返し、E_WARNING レベルの警告が発生します。エラーハンドリングを適切に行わないと、警告メッセージが画面に表示されてしまったり、削除失敗に気づかず処理が進んでしまったりする可能性があります。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicFileDeleter
{
public function delete(string $path): bool
{
// ファイルを削除し、成功/失敗を真偽値で受け取る
return unlink($path);
}
}
$deleter = new BasicFileDeleter();
$result = $deleter->delete('/tmp/sample.txt');
var_dump($result);
例2:存在確認と権限チェックを行う安全な削除クラス
<?php
class SafeFileDeleter
{
/**
* 削除前にファイルの存在と書き込み権限を確認し、
* 警告を出さずに安全に削除する
*/
public function deleteSafely(string $path): bool
{
if (!file_exists($path)) {
// 存在しないファイルは削除不要とみなしtrueを返す設計も可能
return false;
}
if (!is_writable($path)) {
return false;
}
// "@" は使わずエラーハンドリングを明示的に行うのが望ましい
return unlink($path);
}
}
$deleter = new SafeFileDeleter();
var_dump($deleter->deleteSafely('/tmp/uploads/temp_abc.jpg'));
例3:一時ファイルを一括クリーンアップするクラス
<?php
class TempFileCleaner
{
/**
* 指定ディレクトリ内の、一定時間より古い一時ファイルを
* まとめて削除する
*/
public function cleanupOldFiles(string $directory, int $maxAgeSeconds): array
{
$deleted = [];
$now = time();
foreach (glob($directory . '/*') as $file) {
if (!is_file($file)) {
continue;
}
$age = $now - filemtime($file);
if ($age > $maxAgeSeconds) {
if (unlink($file)) {
$deleted[] = $file;
}
}
}
return $deleted;
}
}
$cleaner = new TempFileCleaner();
// 1時間(3600秒)より古い一時ファイルを削除
print_r($cleaner->cleanupOldFiles('/tmp/app_cache', 3600));
例4:例外を使った厳格なエラーハンドリング
<?php
class StrictFileDeleter
{
/**
* 削除に失敗した場合は例外をスローし、
* 呼び出し元で確実にエラーを検知できるようにする
*/
public function delete(string $path): void
{
if (!file_exists($path)) {
throw new RuntimeException("ファイルが存在しません: {$path}");
}
// set_error_handler等と組み合わせてwarningを例外化する運用も一般的
if (!unlink($path)) {
throw new RuntimeException("ファイルの削除に失敗しました: {$path}");
}
}
}
$deleter = new StrictFileDeleter();
try {
$deleter->delete('/tmp/report_2026.pdf');
echo '削除に成功しました' . PHP_EOL;
} catch (RuntimeException $e) {
echo 'エラー: ' . $e->getMessage() . PHP_EOL;
}
例5:アップロード処理失敗時のロールバック(後始末)に使う
<?php
class UploadRollbackHandler
{
/**
* ファイルアップロード後、後続のDB保存処理などに失敗した場合、
* 既にアップロード済みのファイルを削除してロールバックする
*/
public function handleUpload(string $uploadedFilePath, callable $postProcess): bool
{
try {
$postProcess($uploadedFilePath);
return true;
} catch (Throwable $e) {
// 後続処理が失敗した場合、アップロード済みファイルを削除する
if (file_exists($uploadedFilePath)) {
unlink($uploadedFilePath);
}
return false;
}
}
}
$handler = new UploadRollbackHandler();
$handler->handleUpload('/var/www/uploads/photo123.jpg', function (string $path) {
// ここでDB保存などの処理を行い、失敗すれば例外をスローする
throw new RuntimeException('DB保存に失敗しました');
});
例6:ログを残しながら複数ファイルを削除するバッチ処理クラス
<?php
class BatchFileDeleter
{
private array $log = [];
/**
* 複数のファイルパスを受け取り、削除結果をログとして記録する
*/
public function deleteMultiple(array $paths): array
{
foreach ($paths as $path) {
if (!file_exists($path)) {
$this->log[] = "{$path}: スキップ(存在しない)";
continue;
}
if (unlink($path)) {
$this->log[] = "{$path}: 削除成功";
} else {
$this->log[] = "{$path}: 削除失敗";
}
}
return $this->log;
}
}
$batchDeleter = new BatchFileDeleter();
print_r($batchDeleter->deleteMultiple([
'/tmp/file1.txt',
'/tmp/file2.txt',
'/tmp/nonexistent.txt',
]));
例7:シンボリックリンクを安全に削除するクラス
<?php
class SymlinkAwareDeleter
{
/**
* unlink()はシンボリックリンク自体を削除し、
* リンク先の実体は削除しないという挙動を利用する
*/
public function deleteLinkOnly(string $path): bool
{
if (!is_link($path)) {
// シンボリックリンクでない場合は誤削除を防ぐため処理しない
return false;
}
$target = readlink($path);
$result = unlink($path);
if ($result) {
echo "シンボリックリンクを削除しました(リンク先 {$target} は残っています)" . PHP_EOL;
}
return $result;
}
}
$deleter = new SymlinkAwareDeleter();
$deleter->deleteLinkOnly('/var/www/current');
関連関数との比較
| 関数 | 役割 | unlinkとの違い |
|---|---|---|
unlink() | ファイルを削除する | 本記事の対象。ファイル専用で、ディレクトリには使えない |
rmdir() | 空のディレクトリを削除する | 対象がディレクトリである点が異なり、中身が空でないと失敗する |
file_exists() | ファイル/ディレクトリの存在確認 | 削除処理ではなく、削除前後の確認に使う補助関数 |
is_writable() | ファイルへの書き込み権限を確認 | 削除実行前に権限エラーを事前に検知するために使う |
array_map + unlink (glob併用) | 複数ファイルの一括削除パターン | unlink()自体は単一ファイルのみが対象だが、glob()などと組み合わせて一括処理する |
よくある落とし穴(注意点)
- ディレクトリの削除には使えない
unlink()はファイル専用の関数であり、ディレクトリに対して実行すると失敗(falseが返り警告が発生)します。ディレクトリを削除したい場合はrmdir()(空の場合)や再帰的な削除処理を別途実装する必要があります。 - 削除は即座かつ不可逆である ゴミ箱のような復元機能はなく、
unlink()の実行と同時にファイルシステムから即座に削除されます。誤って重要なファイルを削除しないよう、削除対象のパスは慎重に検証しましょう。 - 戻り値の確認を怠る
unlink()の戻り値を確認せずに「削除できたはず」という前提で後続処理を進めると、権限不足などで実際には削除に失敗していた場合に不整合が生じます。 - 競合状態(レースコンディション)に注意する
file_exists()でチェックしてからunlink()を呼び出すまでの間に、別のプロセスが同じファイルを削除している可能性があります。厳密な排他制御が必要な場面では、ファイルロックの利用も検討しましょう。 - 開いたままのファイルハンドルがある場合の挙動 OSによっては、ファイルハンドルが開いたままの状態で
unlink()を実行しても、参照が残っている間はディスク容量が解放されないことがあります(Unix系OSではファイル自体は削除マークされるだけで、最後の参照が閉じられるまで実体は残ります)。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | 指定したパスのファイルをファイルシステムから削除する |
| 主な用途 | 一時ファイルのクリーンアップ、アップロード失敗時のロールバック、ログのローテーションなど |
| 対象 | ファイルのみ(ディレクトリにはrmdir()が必要) |
| 削除の性質 | 即座かつ不可逆(ゴミ箱機能はない) |
| 注意点 | 戻り値の確認、権限エラーへの対応、競合状態への配慮 |
unlink() はシンプルながら、誤って使うと重要なデータを失いかねない重みのある関数です。実行前の存在確認・権限チェック、実行後の戻り値確認をセットで行う習慣をつけることで、安全かつ確実なファイル削除処理を実装できます。
