[PHP]posix_get_last_error関数の使い方を完全解説!最後のエラーを取得する方法

PHP

はじめに

PHPでPOSIX関数を使用する際、処理の失敗理由を知りたいと思ったことはありませんか?

POSIX関数は失敗時に単にfalseを返すだけで、詳細なエラー情報を提供しないことが多いです。しかし、失敗の詳細な理由はエラー番号として内部に保存されています。

その最後のエラー番号を取得するのが**posix_get_last_error関数**です。この関数はposix_errnoのエイリアス(別名)であり、より分かりやすい名前でエラー情報にアクセスできます。

この記事では、posix_get_last_errorの基本から実践的なエラーハンドリング方法まで、詳しく解説します。

posix_get_last_errorとは?

posix_get_last_errorは、最後に失敗したPOSIX関数のエラー番号を返す関数です。posix_errnoの別名(エイリアス)として機能します。

基本構文

posix_get_last_error(): int

パラメータ

この関数はパラメータを取りません。

戻り値

  • 整数: 最後に失敗したPOSIX関数のエラー番号
  • 0: エラーなし、または前回の操作が成功

posix_errnoとの違い

<?php
// 両者は完全に同じ機能を持つ
$result = posix_kill(99999, SIGTERM);

if ($result === false) {
    $errno1 = posix_errno();
    $errno2 = posix_get_last_error();
    
    echo "posix_errno(): {$errno1}\n";
    echo "posix_get_last_error(): {$errno2}\n";
    echo "同じ値: " . ($errno1 === $errno2 ? 'はい' : 'いいえ') . "\n";
    
    // 出力:
    // posix_errno(): 3
    // posix_get_last_error(): 3
    // 同じ値: はい
}

// どちらを使用しても結果は同じ
// posix_get_last_error()の方が名前から意図が明確
?>

対応環境

  • POSIX準拠システム(Linux、Unix、macOS)
  • Windows では利用不可
  • POSIX拡張モジュールが必要

対応バージョン

  • PHP 4.2.0 以降で使用可能

基本的な使い方

最後のエラーを取得

<?php
// 存在しないファイルを開こうとする
$fd = posix_open('/nonexistent/file.txt', O_RDONLY);

if ($fd === false) {
    $errno = posix_get_last_error();
    echo "エラー番号: {$errno}\n";
    
    // エラーメッセージを取得
    $error_message = posix_strerror($errno);
    echo "エラーメッセージ: {$error_message}\n";
}

// 出力例:
// エラー番号: 2
// エラーメッセージ: No such file or directory
?>

エラーの有無をチェック

<?php
function checkLastError() {
    $errno = posix_get_last_error();
    
    if ($errno === 0) {
        echo "最後の操作は成功しました\n";
        return true;
    } else {
        echo "最後の操作は失敗しました\n";
        echo "エラー: " . posix_strerror($errno) . "\n";
        return false;
    }
}

// 成功する操作
posix_getpid();
checkLastError();

// 失敗する操作
posix_kill(99999, SIGTERM);
checkLastError();
?>

実践的な使用例

例1: エラーハンドリングクラス

<?php
class PosixErrorManager {
    /**
     * 最後のエラーを取得
     */
    public static function getLastError() {
        $errno = posix_get_last_error();
        
        if ($errno === 0) {
            return null;
        }
        
        return [
            'errno' => $errno,
            'message' => posix_strerror($errno),
            'description' => self::getErrorDescription($errno)
        ];
    }
    
    /**
     * エラーの日本語説明を取得
     */
    private static function getErrorDescription($errno) {
        return match($errno) {
            EPERM => '操作が許可されていません。root権限が必要な可能性があります',
            ENOENT => 'ファイルまたはディレクトリが見つかりません',
            ESRCH => '指定されたプロセスが存在しません',
            EINTR => 'システムコールが割り込まれました',
            EIO => '入出力エラーが発生しました',
            EACCES => 'アクセスが拒否されました。権限を確認してください',
            EEXIST => 'ファイルまたはディレクトリが既に存在します',
            ENOTDIR => 'ディレクトリではありません',
            EISDIR => 'ディレクトリです。ファイルとして処理できません',
            EINVAL => '無効な引数が指定されました',
            EMFILE => '開いているファイルが多すぎます。リソース制限を確認してください',
            ENOSPC => 'ディスク容量が不足しています',
            EROFS => '読み取り専用ファイルシステムです',
            default => posix_strerror($errno)
        };
    }
    
    /**
     * エラーがあるかチェック
     */
    public static function hasError() {
        return posix_get_last_error() !== 0;
    }
    
    /**
     * エラーをクリア(次のPOSIX操作で上書きされる)
     */
    public static function clearError() {
        // エラーをクリアするには成功する操作を実行
        posix_getpid();
    }
    
    /**
     * エラー情報を表示
     */
    public static function displayLastError($prefix = '') {
        $error = self::getLastError();
        
        if ($error === null) {
            echo "{$prefix}エラーはありません\n";
            return;
        }
        
        echo "{$prefix}エラーが発生しました:\n";
        echo "  エラー番号: {$error['errno']}\n";
        echo "  メッセージ: {$error['message']}\n";
        echo "  詳細: {$error['description']}\n";
    }
}

// 使用例
posix_kill(99999, SIGTERM);

if (PosixErrorManager::hasError()) {
    PosixErrorManager::displayLastError('【POSIX操作】');
}
?>

例2: リトライ機能付きPOSIX操作

<?php
class PosixRetryHandler {
    /**
     * リトライ可能なエラーかどうか
     */
    private static function isRetryableError($errno) {
        return in_array($errno, [
            EINTR,   // システムコールの中断
            EAGAIN,  // リソースが一時的に利用不可
            EBUSY    // リソースがビジー
        ]);
    }
    
    /**
     * リトライ付きで操作を実行
     */
    public static function executeWithRetry(
        callable $operation,
        $max_retries = 3,
        $retry_delay_ms = 100
    ) {
        $attempt = 0;
        
        while ($attempt <= $max_retries) {
            $result = $operation();
            
            if ($result !== false) {
                return $result; // 成功
            }
            
            $errno = posix_get_last_error();
            
            // リトライ不可能なエラー
            if (!self::isRetryableError($errno)) {
                throw new RuntimeException(
                    "操作失敗: " . posix_strerror($errno),
                    $errno
                );
            }
            
            $attempt++;
            
            if ($attempt <= $max_retries) {
                echo "リトライ {$attempt}/{$max_retries} (errno: {$errno})...\n";
                usleep($retry_delay_ms * 1000);
            }
        }
        
        $errno = posix_get_last_error();
        throw new RuntimeException(
            "最大リトライ回数に到達: " . posix_strerror($errno),
            $errno
        );
    }
}

// 使用例
try {
    $result = PosixRetryHandler::executeWithRetry(
        fn() => posix_kill($pid, SIGTERM),
        max_retries: 5,
        retry_delay_ms: 200
    );
    echo "シグナル送信成功\n";
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例3: エラーコンテキストの追跡

<?php
class PosixOperationTracker {
    private static $operations = [];
    
    /**
     * 操作を実行してトラッキング
     */
    public static function track($operation_name, callable $operation) {
        $start_time = microtime(true);
        $result = $operation();
        $end_time = microtime(true);
        
        $record = [
            'name' => $operation_name,
            'success' => $result !== false,
            'duration' => $end_time - $start_time,
            'timestamp' => date('Y-m-d H:i:s'),
        ];
        
        if ($result === false) {
            $errno = posix_get_last_error();
            $record['errno'] = $errno;
            $record['error'] = posix_strerror($errno);
        }
        
        self::$operations[] = $record;
        
        return $result;
    }
    
    /**
     * トラッキング履歴を取得
     */
    public static function getHistory() {
        return self::$operations;
    }
    
    /**
     * 失敗した操作のみ取得
     */
    public static function getFailures() {
        return array_filter(
            self::$operations,
            fn($op) => !$op['success']
        );
    }
    
    /**
     * 統計情報を取得
     */
    public static function getStatistics() {
        $total = count(self::$operations);
        $successes = count(array_filter(
            self::$operations,
            fn($op) => $op['success']
        ));
        $failures = $total - $successes;
        
        return [
            'total' => $total,
            'successes' => $successes,
            'failures' => $failures,
            'success_rate' => $total > 0 ? ($successes / $total) * 100 : 0
        ];
    }
    
    /**
     * レポートを表示
     */
    public static function displayReport() {
        $stats = self::getStatistics();
        
        echo "=== POSIX操作レポート ===\n\n";
        echo "総操作数: {$stats['total']}\n";
        echo "成功: {$stats['successes']}\n";
        echo "失敗: {$stats['failures']}\n";
        echo "成功率: " . number_format($stats['success_rate'], 2) . "%\n\n";
        
        $failures = self::getFailures();
        
        if (!empty($failures)) {
            echo "失敗した操作:\n";
            foreach ($failures as $i => $op) {
                echo ($i + 1) . ". {$op['name']}\n";
                echo "   時刻: {$op['timestamp']}\n";
                echo "   errno: {$op['errno']}\n";
                echo "   エラー: {$op['error']}\n\n";
            }
        }
    }
    
    /**
     * 履歴をクリア
     */
    public static function clear() {
        self::$operations = [];
    }
}

// 使用例
PosixOperationTracker::track(
    'プロセス情報取得',
    fn() => posix_getpid()
);

PosixOperationTracker::track(
    '存在しないプロセスへのシグナル送信',
    fn() => posix_kill(99999, SIGTERM)
);

PosixOperationTracker::track(
    'ユーザー情報取得',
    fn() => posix_getpwuid(posix_getuid())
);

PosixOperationTracker::displayReport();
?>

例4: 詳細なファイル操作エラーレポート

<?php
class FileOperationError {
    /**
     * ファイル操作を試行してエラーを詳細報告
     */
    public static function tryFileOperation($filepath, $operation_type) {
        echo "=== ファイル操作: {$operation_type} ===\n";
        echo "対象: {$filepath}\n\n";
        
        $result = match($operation_type) {
            'read' => self::tryRead($filepath),
            'write' => self::tryWrite($filepath),
            'execute' => self::tryExecute($filepath),
            'delete' => self::tryDelete($filepath),
            default => ['success' => false, 'error' => '不明な操作']
        };
        
        if ($result['success']) {
            echo "✓ 操作成功\n";
        } else {
            echo "✗ 操作失敗\n";
            self::displayErrorDetails($result);
        }
        
        return $result;
    }
    
    private static function tryRead($filepath) {
        if (!file_exists($filepath)) {
            return [
                'success' => false,
                'error' => 'ファイルが存在しません',
                'errno' => ENOENT
            ];
        }
        
        $result = posix_access($filepath, POSIX_R_OK);
        
        if ($result === false) {
            return [
                'success' => false,
                'error' => '読み取り権限がありません',
                'errno' => posix_get_last_error(),
                'suggestion' => 'chmod +r または所有者として実行してください'
            ];
        }
        
        return ['success' => true];
    }
    
    private static function tryWrite($filepath) {
        if (!file_exists($filepath)) {
            // 親ディレクトリの書き込み権限をチェック
            $parent = dirname($filepath);
            
            if (!posix_access($parent, POSIX_W_OK | POSIX_X_OK)) {
                return [
                    'success' => false,
                    'error' => '親ディレクトリへの書き込み権限がありません',
                    'errno' => posix_get_last_error(),
                    'suggestion' => "chmod +w {$parent}"
                ];
            }
            
            return ['success' => true, 'note' => '新規ファイル作成可能'];
        }
        
        $result = posix_access($filepath, POSIX_W_OK);
        
        if ($result === false) {
            return [
                'success' => false,
                'error' => '書き込み権限がありません',
                'errno' => posix_get_last_error(),
                'suggestion' => 'chmod +w または所有者として実行してください'
            ];
        }
        
        return ['success' => true];
    }
    
    private static function tryExecute($filepath) {
        if (!file_exists($filepath)) {
            return [
                'success' => false,
                'error' => 'ファイルが存在しません',
                'errno' => ENOENT
            ];
        }
        
        if (is_dir($filepath)) {
            return [
                'success' => false,
                'error' => 'ディレクトリは実行できません',
                'errno' => EISDIR
            ];
        }
        
        $result = posix_access($filepath, POSIX_X_OK);
        
        if ($result === false) {
            return [
                'success' => false,
                'error' => '実行権限がありません',
                'errno' => posix_get_last_error(),
                'suggestion' => 'chmod +x で実行権限を付与してください'
            ];
        }
        
        return ['success' => true];
    }
    
    private static function tryDelete($filepath) {
        if (!file_exists($filepath)) {
            return [
                'success' => false,
                'error' => 'ファイルが存在しません',
                'errno' => ENOENT
            ];
        }
        
        $parent = dirname($filepath);
        
        if (!posix_access($parent, POSIX_W_OK | POSIX_X_OK)) {
            return [
                'success' => false,
                'error' => '親ディレクトリへの書き込み権限がありません',
                'errno' => posix_get_last_error(),
                'suggestion' => "chmod +w {$parent}"
            ];
        }
        
        return ['success' => true];
    }
    
    private static function displayErrorDetails($result) {
        echo "\nエラー詳細:\n";
        
        if (isset($result['errno'])) {
            echo "  エラー番号: {$result['errno']}\n";
            echo "  システムメッセージ: " . posix_strerror($result['errno']) . "\n";
        }
        
        echo "  説明: {$result['error']}\n";
        
        if (isset($result['suggestion'])) {
            echo "\n対処方法:\n";
            echo "  {$result['suggestion']}\n";
        }
    }
}

// 使用例
FileOperationError::tryFileOperation('/etc/shadow', 'read');
echo "\n";
FileOperationError::tryFileOperation('/tmp/test.txt', 'write');
?>

例5: システムコールのデバッグヘルパー

<?php
class PosixDebugger {
    private static $debug_mode = false;
    private static $log = [];
    
    /**
     * デバッグモードを有効化
     */
    public static function enable() {
        self::$debug_mode = true;
    }
    
    /**
     * デバッグモードを無効化
     */
    public static function disable() {
        self::$debug_mode = false;
    }
    
    /**
     * POSIX関数をデバッグ実行
     */
    public static function call($function_name, ...$args) {
        $start = microtime(true);
        
        // 関数を実行
        $result = call_user_func($function_name, ...$args);
        
        $duration = microtime(true) - $start;
        $errno = posix_get_last_error();
        
        $log_entry = [
            'function' => $function_name,
            'args' => $args,
            'result' => $result,
            'success' => $result !== false,
            'errno' => $errno,
            'error' => $errno !== 0 ? posix_strerror($errno) : null,
            'duration' => $duration,
            'timestamp' => microtime(true)
        ];
        
        self::$log[] = $log_entry;
        
        if (self::$debug_mode) {
            self::printCallInfo($log_entry);
        }
        
        return $result;
    }
    
    private static function printCallInfo($entry) {
        $args_str = implode(', ', array_map(function($arg) {
            if (is_string($arg)) return "'{$arg}'";
            if (is_bool($arg)) return $arg ? 'true' : 'false';
            if (is_null($arg)) return 'null';
            return (string)$arg;
        }, $entry['args']));
        
        echo "[DEBUG] {$entry['function']}({$args_str})\n";
        echo "        結果: " . var_export($entry['result'], true) . "\n";
        echo "        成功: " . ($entry['success'] ? 'はい' : 'いいえ') . "\n";
        
        if (!$entry['success']) {
            echo "        errno: {$entry['errno']}\n";
            echo "        エラー: {$entry['error']}\n";
        }
        
        echo "        実行時間: " . number_format($entry['duration'] * 1000, 3) . "ms\n";
        echo "\n";
    }
    
    /**
     * ログを取得
     */
    public static function getLog() {
        return self::$log;
    }
    
    /**
     * ログをクリア
     */
    public static function clearLog() {
        self::$log = [];
    }
    
    /**
     * サマリーを表示
     */
    public static function displaySummary() {
        $total = count(self::$log);
        $successes = count(array_filter(self::$log, fn($e) => $e['success']));
        $failures = $total - $successes;
        
        echo "=== デバッグサマリー ===\n";
        echo "総呼び出し数: {$total}\n";
        echo "成功: {$successes}\n";
        echo "失敗: {$failures}\n";
        
        if ($failures > 0) {
            echo "\n失敗した呼び出し:\n";
            foreach (self::$log as $entry) {
                if (!$entry['success']) {
                    echo "  {$entry['function']}() - errno: {$entry['errno']}, {$entry['error']}\n";
                }
            }
        }
    }
}

// 使用例
PosixDebugger::enable();

PosixDebugger::call('posix_getpid');
PosixDebugger::call('posix_kill', 99999, SIGTERM);
PosixDebugger::call('posix_getpwuid', posix_getuid());
PosixDebugger::call('posix_access', '/etc/shadow', POSIX_R_OK);

echo "\n";
PosixDebugger::displaySummary();
?>

まとめ

posix_get_last_errorは、POSIX関数の最後のエラー番号を取得するための関数です。

主な特徴:

  • posix_errnoのエイリアス(完全に同じ機能)
  • ✅ 最後に失敗したPOSIX関数のエラー番号を返す
  • posix_strerrorと組み合わせて詳細なエラーメッセージを取得
  • ✅ より分かりやすい関数名

使い分け:

  • posix_errno() – 短く書きたい場合
  • posix_get_last_error() – コードの可読性を重視する場合

主なエラー番号:

  • EPERM (1) – 権限エラー
  • ENOENT (2) – ファイル/ディレクトリが存在しない
  • ESRCH (3) – プロセスが存在しない
  • EINTR (4) – システムコールが中断
  • EACCES (13) – アクセス拒否
  • EEXIST (17) – 既に存在
  • EINVAL (22) – 無効な引数
  • EMFILE (24) – ファイル記述子の上限

ベストプラクティス:

  • POSIX関数の戻り値を常にチェック
  • 失敗時は必ずエラー番号を確認
  • エラーハンドリングクラスで統一的に処理
  • デバッグ用のロギング機能を実装

関連関数:

  • posix_errno() – 同じ機能の別名
  • posix_strerror() – エラー番号からメッセージを取得

この関数を活用して、より堅牢で保守性の高いPOSIXプログラミングを実現しましょう!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!

タイトルとURLをコピーしました