[PHP]is_dir関数とは?ディレクトリ判定の基本から実践まで完全解説

PHP

Webアプリケーション開発において、ファイルシステムの操作は避けて通れない作業です。特に、指定したパスがディレクトリなのかファイルなのかを判定することは、ファイル操作の基本中の基本。そんな時に活躍するのがis_dir()関数です。この記事では、PHPのis_dir()関数について基本的な使い方から実践的な活用例まで詳しく解説していきます。

is_dir関数とは

is_dir()は、指定したパスがディレクトリ(フォルダ)であるかどうかを判定するPHP関数です。ファイルシステム関連の処理で頻繁に使用される重要な関数の一つで、PHP4以降で利用可能です。

基本的な構文

is_dir(string $filename): bool
  • 引数: 判定したいパス(文字列)
  • 戻り値: ディレクトリの場合はtrue、そうでなければfalse

基本的な使用例

シンプルなディレクトリ判定

<?php
// 存在するディレクトリの場合
$directory = '/var/www/html';
if (is_dir($directory)) {
    echo "'{$directory}' はディレクトリです。\n";
} else {
    echo "'{$directory}' はディレクトリではありません。\n";
}

// 存在するファイルの場合
$file = '/var/www/html/index.php';
if (is_dir($file)) {
    echo "'{$file}' はディレクトリです。\n";
} else {
    echo "'{$file}' はディレクトリではありません。\n";
}

// 存在しないパスの場合
$nonexistent = '/path/that/does/not/exist';
if (is_dir($nonexistent)) {
    echo "'{$nonexistent}' はディレクトリです。\n";
} else {
    echo "'{$nonexistent}' はディレクトリではありません。\n";
}
?>

相対パスと絶対パスでの動作

<?php
// 相対パス
echo is_dir('.') ? 'カレントディレクトリは存在します' : 'カレントディレクトリが見つかりません';
echo "\n";

echo is_dir('..') ? '親ディレクトリは存在します' : '親ディレクトリが見つかりません';
echo "\n";

// 絶対パス
echo is_dir('/tmp') ? '/tmpディレクトリは存在します' : '/tmpディレクトリが見つかりません';
echo "\n";

// Windowsの場合
echo is_dir('C:\\Windows') ? 'Windowsディレクトリは存在します' : 'Windowsディレクトリが見つかりません';
echo "\n";
?>

実際の開発での活用例

1. ファイルアップロード処理

<?php
class FileUploadHandler
{
    private $uploadDir;
    
    public function __construct($uploadDir = 'uploads')
    {
        $this->uploadDir = $uploadDir;
        $this->ensureUploadDirectory();
    }
    
    private function ensureUploadDirectory()
    {
        if (!is_dir($this->uploadDir)) {
            if (!mkdir($this->uploadDir, 0755, true)) {
                throw new Exception("アップロードディレクトリの作成に失敗しました: {$this->uploadDir}");
            }
            echo "アップロードディレクトリを作成しました: {$this->uploadDir}\n";
        }
    }
    
    public function uploadFile($file, $subDir = '')
    {
        $targetDir = $this->uploadDir;
        
        if ($subDir) {
            $targetDir .= '/' . $subDir;
            
            // サブディレクトリが存在しない場合は作成
            if (!is_dir($targetDir)) {
                mkdir($targetDir, 0755, true);
            }
        }
        
        if (!is_dir($targetDir)) {
            throw new Exception("ターゲットディレクトリが存在しません: {$targetDir}");
        }
        
        $targetFile = $targetDir . '/' . basename($file['name']);
        
        if (move_uploaded_file($file['tmp_name'], $targetFile)) {
            return $targetFile;
        } else {
            throw new Exception("ファイルのアップロードに失敗しました");
        }
    }
}

// 使用例
$uploader = new FileUploadHandler('public/uploads');

// $_FILES['file'] がアップロードされたファイルと仮定
if (isset($_FILES['file'])) {
    try {
        $uploadedFile = $uploader->uploadFile($_FILES['file'], 'images');
        echo "ファイルがアップロードされました: {$uploadedFile}\n";
    } catch (Exception $e) {
        echo "エラー: " . $e->getMessage() . "\n";
    }
}
?>

2. ログファイル管理システム

<?php
class LogManager
{
    private $logDir;
    private $maxLogFiles;
    
    public function __construct($logDir = 'logs', $maxLogFiles = 30)
    {
        $this->logDir = $logDir;
        $this->maxLogFiles = $maxLogFiles;
        $this->initializeLogDirectory();
    }
    
    private function initializeLogDirectory()
    {
        if (!is_dir($this->logDir)) {
            if (!mkdir($this->logDir, 0755, true)) {
                throw new Exception("ログディレクトリの作成に失敗しました: {$this->logDir}");
            }
        }
        
        // 権限チェック
        if (!is_writable($this->logDir)) {
            throw new Exception("ログディレクトリに書き込み権限がありません: {$this->logDir}");
        }
    }
    
    public function writeLog($message, $level = 'INFO')
    {
        $logFile = $this->logDir . '/' . date('Y-m-d') . '.log';
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[{$timestamp}] [{$level}] {$message}\n";
        
        file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
        
        // 古いログファイルの清理
        $this->cleanupOldLogs();
    }
    
    private function cleanupOldLogs()
    {
        if (!is_dir($this->logDir)) {
            return;
        }
        
        $logFiles = glob($this->logDir . '/*.log');
        
        if (count($logFiles) > $this->maxLogFiles) {
            // ファイルを更新日時でソート
            usort($logFiles, function($a, $b) {
                return filemtime($a) - filemtime($b);
            });
            
            // 古いファイルを削除
            $filesToDelete = array_slice($logFiles, 0, count($logFiles) - $this->maxLogFiles);
            foreach ($filesToDelete as $file) {
                unlink($file);
            }
        }
    }
    
    public function getLogDirectoryInfo()
    {
        if (!is_dir($this->logDir)) {
            return ['error' => 'ログディレクトリが存在しません'];
        }
        
        $logFiles = glob($this->logDir . '/*.log');
        $totalSize = 0;
        
        foreach ($logFiles as $file) {
            $totalSize += filesize($file);
        }
        
        return [
            'directory' => $this->logDir,
            'file_count' => count($logFiles),
            'total_size' => $totalSize,
            'readable' => is_readable($this->logDir),
            'writable' => is_writable($this->logDir)
        ];
    }
}

// 使用例
$logger = new LogManager('application/logs');
$logger->writeLog('アプリケーションが開始されました');
$logger->writeLog('エラーが発生しました', 'ERROR');

$info = $logger->getLogDirectoryInfo();
print_r($info);
?>

3. 設定ファイル管理

<?php
class ConfigManager
{
    private $configDir;
    private $configs = [];
    
    public function __construct($configDir = 'config')
    {
        $this->configDir = $configDir;
        $this->loadConfigs();
    }
    
    private function loadConfigs()
    {
        if (!is_dir($this->configDir)) {
            throw new Exception("設定ディレクトリが存在しません: {$this->configDir}");
        }
        
        $configFiles = glob($this->configDir . '/*.php');
        
        foreach ($configFiles as $configFile) {
            $configName = basename($configFile, '.php');
            
            if (is_readable($configFile)) {
                $this->configs[$configName] = include $configFile;
            } else {
                echo "警告: 設定ファイルが読み込めません: {$configFile}\n";
            }
        }
    }
    
    public function get($configName, $key = null, $default = null)
    {
        if (!isset($this->configs[$configName])) {
            return $default;
        }
        
        if ($key === null) {
            return $this->configs[$configName];
        }
        
        return $this->configs[$configName][$key] ?? $default;
    }
    
    public function validateConfigDirectory()
    {
        $issues = [];
        
        if (!is_dir($this->configDir)) {
            $issues[] = "設定ディレクトリが存在しません: {$this->configDir}";
        } else {
            if (!is_readable($this->configDir)) {
                $issues[] = "設定ディレクトリが読み込めません: {$this->configDir}";
            }
            
            $requiredConfigs = ['database', 'app', 'mail'];
            
            foreach ($requiredConfigs as $config) {
                $configFile = $this->configDir . "/{$config}.php";
                if (!file_exists($configFile)) {
                    $issues[] = "必要な設定ファイルが見つかりません: {$configFile}";
                }
            }
        }
        
        return $issues;
    }
    
    public function createDefaultConfigs()
    {
        if (!is_dir($this->configDir)) {
            mkdir($this->configDir, 0755, true);
        }
        
        $defaultConfigs = [
            'app' => [
                'name' => 'My Application',
                'debug' => false,
                'timezone' => 'Asia/Tokyo'
            ],
            'database' => [
                'host' => 'localhost',
                'name' => 'myapp',
                'user' => 'root',
                'pass' => ''
            ]
        ];
        
        foreach ($defaultConfigs as $name => $config) {
            $configFile = $this->configDir . "/{$name}.php";
            
            if (!file_exists($configFile)) {
                $content = "<?php\nreturn " . var_export($config, true) . ";\n";
                file_put_contents($configFile, $content);
                echo "デフォルト設定ファイルを作成しました: {$configFile}\n";
            }
        }
    }
}

// 使用例
try {
    $config = new ConfigManager('app/config');
    
    // 設定の検証
    $issues = $config->validateConfigDirectory();
    if (!empty($issues)) {
        echo "設定に問題があります:\n";
        foreach ($issues as $issue) {
            echo "- {$issue}\n";
        }
        
        // デフォルト設定を作成
        $config->createDefaultConfigs();
    }
    
    // 設定の取得
    $appName = $config->get('app', 'name', 'Default App');
    $dbHost = $config->get('database', 'host', 'localhost');
    
    echo "アプリケーション名: {$appName}\n";
    echo "データベースホスト: {$dbHost}\n";
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

4. キャッシュディレクトリ管理

<?php
class CacheManager
{
    private $cacheDir;
    private $maxCacheAge; // 秒
    
    public function __construct($cacheDir = 'cache', $maxCacheAge = 3600)
    {
        $this->cacheDir = $cacheDir;
        $this->maxCacheAge = $maxCacheAge;
        $this->initializeCacheDirectory();
    }
    
    private function initializeCacheDirectory()
    {
        if (!is_dir($this->cacheDir)) {
            if (!mkdir($this->cacheDir, 0755, true)) {
                throw new Exception("キャッシュディレクトリの作成に失敗しました: {$this->cacheDir}");
            }
        }
        
        // .htaccessファイルでアクセスを制限
        $htaccessFile = $this->cacheDir . '/.htaccess';
        if (!file_exists($htaccessFile)) {
            file_put_contents($htaccessFile, "Deny from all\n");
        }
    }
    
    public function set($key, $data, $ttl = null)
    {
        if (!is_dir($this->cacheDir)) {
            throw new Exception("キャッシュディレクトリが存在しません: {$this->cacheDir}");
        }
        
        $cacheFile = $this->getCacheFilePath($key);
        $cacheData = [
            'data' => $data,
            'created' => time(),
            'ttl' => $ttl ?? $this->maxCacheAge
        ];
        
        $serializedData = serialize($cacheData);
        
        if (file_put_contents($cacheFile, $serializedData, LOCK_EX) === false) {
            throw new Exception("キャッシュファイルの書き込みに失敗しました: {$cacheFile}");
        }
        
        return true;
    }
    
    public function get($key)
    {
        if (!is_dir($this->cacheDir)) {
            return null;
        }
        
        $cacheFile = $this->getCacheFilePath($key);
        
        if (!file_exists($cacheFile)) {
            return null;
        }
        
        $serializedData = file_get_contents($cacheFile);
        $cacheData = unserialize($serializedData);
        
        if (!$cacheData) {
            return null;
        }
        
        // TTLチェック
        if (time() - $cacheData['created'] > $cacheData['ttl']) {
            unlink($cacheFile);
            return null;
        }
        
        return $cacheData['data'];
    }
    
    public function clear()
    {
        if (!is_dir($this->cacheDir)) {
            return false;
        }
        
        $cacheFiles = glob($this->cacheDir . '/*.cache');
        $clearedCount = 0;
        
        foreach ($cacheFiles as $file) {
            if (unlink($file)) {
                $clearedCount++;
            }
        }
        
        return $clearedCount;
    }
    
    public function cleanup()
    {
        if (!is_dir($this->cacheDir)) {
            return 0;
        }
        
        $cacheFiles = glob($this->cacheDir . '/*.cache');
        $cleanedCount = 0;
        
        foreach ($cacheFiles as $file) {
            $serializedData = file_get_contents($file);
            $cacheData = unserialize($serializedData);
            
            if ($cacheData && time() - $cacheData['created'] > $cacheData['ttl']) {
                if (unlink($file)) {
                    $cleanedCount++;
                }
            }
        }
        
        return $cleanedCount;
    }
    
    private function getCacheFilePath($key)
    {
        $hashedKey = md5($key);
        return $this->cacheDir . '/' . $hashedKey . '.cache';
    }
    
    public function getStats()
    {
        if (!is_dir($this->cacheDir)) {
            return ['error' => 'キャッシュディレクトリが存在しません'];
        }
        
        $cacheFiles = glob($this->cacheDir . '/*.cache');
        $totalSize = 0;
        $expiredCount = 0;
        
        foreach ($cacheFiles as $file) {
            $totalSize += filesize($file);
            
            $serializedData = file_get_contents($file);
            $cacheData = unserialize($serializedData);
            
            if ($cacheData && time() - $cacheData['created'] > $cacheData['ttl']) {
                $expiredCount++;
            }
        }
        
        return [
            'directory' => $this->cacheDir,
            'total_files' => count($cacheFiles),
            'total_size' => $totalSize,
            'expired_files' => $expiredCount,
            'directory_writable' => is_writable($this->cacheDir)
        ];
    }
}

// 使用例
$cache = new CacheManager('temp/cache');

// キャッシュの設定
$cache->set('user_data_123', ['name' => 'John', 'email' => 'john@example.com'], 1800);

// キャッシュの取得
$userData = $cache->get('user_data_123');
if ($userData) {
    echo "キャッシュからデータを取得: " . print_r($userData, true);
} else {
    echo "キャッシュが見つからないか期限切れです\n";
}

// 統計情報
$stats = $cache->getStats();
print_r($stats);

// 古いキャッシュの清理
$cleaned = $cache->cleanup();
echo "清理されたキャッシュファイル数: {$cleaned}\n";
?>

セキュリティ上の注意点

1. パストラバーサル攻撃の防止

<?php
class SecureFileHandler
{
    private $baseDir;
    
    public function __construct($baseDir)
    {
        $this->baseDir = realpath($baseDir);
        
        if (!$this->baseDir || !is_dir($this->baseDir)) {
            throw new Exception("無効なベースディレクトリです: {$baseDir}");
        }
    }
    
    public function isValidPath($path)
    {
        // パスを正規化
        $realPath = realpath($this->baseDir . '/' . $path);
        
        // ベースディレクトリ外への移動を防ぐ
        if ($realPath === false || strpos($realPath, $this->baseDir) !== 0) {
            return false;
        }
        
        return true;
    }
    
    public function safeIsDir($path)
    {
        if (!$this->isValidPath($path)) {
            throw new Exception("危険なパスが検出されました: {$path}");
        }
        
        $fullPath = $this->baseDir . '/' . $path;
        return is_dir($fullPath);
    }
    
    public function listDirectory($path = '')
    {
        if (!$this->isValidPath($path)) {
            throw new Exception("危険なパスが検出されました: {$path}");
        }
        
        $fullPath = $this->baseDir . '/' . $path;
        
        if (!is_dir($fullPath)) {
            throw new Exception("ディレクトリが存在しません: {$path}");
        }
        
        $items = [];
        $iterator = new DirectoryIterator($fullPath);
        
        foreach ($iterator as $item) {
            if ($item->isDot()) continue;
            
            $items[] = [
                'name' => $item->getFilename(),
                'type' => $item->isDir() ? 'directory' : 'file',
                'size' => $item->isFile() ? $item->getSize() : null,
                'modified' => $item->getMTime()
            ];
        }
        
        return $items;
    }
}

// 使用例
try {
    $handler = new SecureFileHandler('/var/www/uploads');
    
    // 安全なパスチェック
    echo $handler->safeIsDir('images') ? 'imagesディレクトリは存在します' : 'imagesディレクトリは存在しません';
    echo "\n";
    
    // 危険なパスの例(これはエラーになる)
    // $handler->safeIsDir('../../../etc');
    
    // ディレクトリの一覧取得
    $contents = $handler->listDirectory('images');
    print_r($contents);
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

2. 権限チェックの実装

<?php
class DirectoryPermissionChecker
{
    public function checkDirectoryPermissions($path)
    {
        $result = [
            'path' => $path,
            'exists' => false,
            'is_directory' => false,
            'readable' => false,
            'writable' => false,
            'executable' => false,
            'permissions' => null,
            'owner' => null,
            'group' => null
        ];
        
        if (!file_exists($path)) {
            return $result;
        }
        
        $result['exists'] = true;
        $result['is_directory'] = is_dir($path);
        
        if ($result['is_directory']) {
            $result['readable'] = is_readable($path);
            $result['writable'] = is_writable($path);
            $result['executable'] = is_executable($path);
            
            $perms = fileperms($path);
            $result['permissions'] = substr(sprintf('%o', $perms), -4);
            
            if (function_exists('posix_getpwuid') && function_exists('posix_getgrgid')) {
                $owner = posix_getpwuid(fileowner($path));
                $group = posix_getgrgid(filegroup($path));
                
                $result['owner'] = $owner['name'] ?? fileowner($path);
                $result['group'] = $group['name'] ?? filegroup($path);
            }
        }
        
        return $result;
    }
    
    public function ensureDirectoryPermissions($path, $requiredPerms = 0755)
    {
        if (!is_dir($path)) {
            if (!mkdir($path, $requiredPerms, true)) {
                throw new Exception("ディレクトリの作成に失敗しました: {$path}");
            }
            return true;
        }
        
        $currentPerms = fileperms($path) & 0777;
        
        if ($currentPerms !== $requiredPerms) {
            if (!chmod($path, $requiredPerms)) {
                throw new Exception("権限の変更に失敗しました: {$path}");
            }
        }
        
        return true;
    }
    
    public function validateWebDirectory($path)
    {
        $issues = [];
        
        if (!is_dir($path)) {
            $issues[] = "ディレクトリが存在しません: {$path}";
            return $issues;
        }
        
        // 基本的な権限チェック
        if (!is_readable($path)) {
            $issues[] = "ディレクトリが読み込み不可: {$path}";
        }
        
        if (!is_executable($path)) {
            $issues[] = "ディレクトリが実行不可: {$path}";
        }
        
        // セキュリティチェック
        $perms = fileperms($path) & 0777;
        
        // 他者書き込み権限をチェック
        if ($perms & 0002) {
            $issues[] = "危険: 他者書き込み権限が設定されています: {$path}";
        }
        
        // .htaccessファイルの存在チェック(Apacheの場合)
        $htaccessFile = $path . '/.htaccess';
        if (is_dir($path . '/private') && !file_exists($htaccessFile)) {
            $issues[] = "警告: プライベートディレクトリに.htaccessがありません";
        }
        
        return $issues;
    }
}

// 使用例
$checker = new DirectoryPermissionChecker();

// 権限チェック
$permissions = $checker->checkDirectoryPermissions('/var/www/html');
print_r($permissions);

// Web ディレクトリの検証
$issues = $checker->validateWebDirectory('/var/www/uploads');
if (!empty($issues)) {
    echo "セキュリティ上の問題が見つかりました:\n";
    foreach ($issues as $issue) {
        echo "- {$issue}\n";
    }
}

// 権限の確保
try {
    $checker->ensureDirectoryPermissions('/var/www/temp', 0755);
    echo "ディレクトリの権限を確保しました\n";
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

パフォーマンスとベストプラクティス

1. 効率的なディレクトリ操作

<?php
class OptimizedDirectoryHandler
{
    private $cache = [];
    private $cacheTimeout = 300; // 5分
    
    public function cachedIsDir($path)
    {
        $cacheKey = 'is_dir_' . md5($path);
        
        if (isset($this->cache[$cacheKey])) {
            $cached = $this->cache[$cacheKey];
            
            // キャッシュの有効期限チェック
            if (time() - $cached['timestamp'] < $this->cacheTimeout) {
                return $cached['result'];
            }
        }
        
        $result = is_dir($path);
        
        $this->cache[$cacheKey] = [
            'result' => $result,
            'timestamp' => time()
        ];
        
        return $result;
    }
    
    public function batchDirectoryCheck($paths)
    {
        $results = [];
        
        foreach ($paths as $path) {
            $results[$path] = [
                'is_dir' => is_dir($path),
                'exists' => file_exists($path),
                'readable' => is_readable($path),
                'writable' => is_writable($path)
            ];
        }
        
        return $results;
    }
    
    public function findDirectories($basePath, $pattern = '*', $recursive = false)
    {
        if (!is_dir($basePath)) {
            return [];
        }
        
        $directories = [];
        $searchPattern = $basePath . '/' . $pattern;
        
        if ($recursive) {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($basePath, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
            );
            
            foreach ($iterator as $item) {
                if ($item->isDir() && fnmatch($pattern, $item->getFilename())) {
                    $directories[] = $item->getPathname();
                }
            }
        } else {
            $items = glob($searchPattern, GLOB_ONLYDIR);
            $directories = $items ?: [];
        }
        
        return $directories;
    }
    
    public function clearCache()
    {
        $this->cache = [];
    }
    
    public function getCacheStats()
    {
        return [
            'entries' => count($this->cache),
            'memory_usage' => memory_get_usage(),
            'timeout' => $this->cacheTimeout
        ];
    }
}

// 使用例
$handler = new OptimizedDirectoryHandler();

// 複数パスの一括チェック
$paths = ['/var/www', '/tmp', '/home', '/nonexistent'];
$results = $handler->batchDirectoryCheck($paths);

foreach ($results as $path => $info) {
    echo "Path: {$path}\n";
    echo "  Is Directory: " . ($info['is_dir'] ? 'Yes' : 'No') . "\n";
    echo "  Exists: " . ($info['exists'] ? 'Yes' : 'No') . "\n";
    echo "  Readable: " . ($info['readable'] ? 'Yes' : 'No') . "\n";
    echo "  Writable: " . ($info['writable'] ? 'Yes' : 'No') . "\n\n";
}

// パターンマッチングでディレクトリ検索
$directories = $handler->findDirectories('/var/log', '*', true);
echo "Found directories: " . count($directories) . "\n";

// キャッシュ統計
$stats = $handler->getCacheStats();
print_r($stats);
?>

エラーハンドリングとデバッグ

<?php
class DirectoryDebugger
{
    public function debugPath($path)
    {
        $info = [
            'input_path' => $path,
            'absolute_path' => null,
            'exists' => false,
            'is_dir' => false,
            'is_file' => false,
            'is_link' => false,
            'readable' => false,
            'writable' => false,
            'executable' => false,
            'permissions' => null,
            'size' => null,
            'modified_time' => null,
            'error' => null
        ];
        
        try {
            // 絶対パスの取得
            $realPath = realpath($path);
            if ($realPath) {
                $info['absolute_path'] = $realPath;
            }
            
            // 基本的な存在チェック
            $info['exists'] = file_exists($path);
            
            if ($info['exists']) {
                $info['is_dir'] = is_dir($path);
                $info['is_file'] = is_file($path);
                $info['is_link'] = is_link($path);
                $info['readable'] = is_readable($path);
                $info['writable'] = is_writable($path);
                $info['executable'] = is_executable($path);
                
                // 権限情報
                $perms = fileperms($path);
                $info['permissions'] = substr(sprintf('%o', $perms), -4);
                
                // サイズと最終更新時刻
                if ($info['is_file']) {
                    $info['size'] = filesize($path);
                }
                $info['modified_time'] = filemtime($path);
            }
            
        } catch (Exception $e) {
            $info['error'] = $e->getMessage();
        }
        
        return $info;
    }
    
    public function validateDirectoryStructure($basePath, $requiredDirs = [])
    {
        $results = [
            'base_path' => $basePath,
            'base_exists' => is_dir($basePath),
            'required_directories' => [],
            'issues' => []
        ];
        
        if (!$results['base_exists']) {
            $results['issues'][] = "ベースディレクトリが存在しません: {$basePath}";
            return $results;
        }
        
        foreach ($requiredDirs as $dir) {
            $fullPath = $basePath . '/' . $dir;
            $dirInfo = [
                'path' => $dir,
                'full_path' => $fullPath,
                'exists' => is_dir($fullPath),
                'created' => false
            ];
            
            if (!$dirInfo['exists']) {
                // 自動作成を試す
                if (mkdir($fullPath, 0755, true)) {
                    $dirInfo['created'] = true;
                    $dirInfo['exists'] = true;
                } else {
                    $results['issues'][] = "必要なディレクトリの作成に失敗しました: {$dir}";
                }
            }
            
            $results['required_directories'][] = $dirInfo;
        }
        
        return $results;
    }
    
    public function troubleshootDirectoryIssues($path)
    {
        $suggestions = [];
        $pathInfo = $this->debugPath($path);
        
        if (!$pathInfo['exists']) {
            $suggestions[] = "パスが存在しません。パスのスペルを確認してください。";
            
            $parentDir = dirname($path);
            if (is_dir($parentDir)) {
                $suggestions[] = "親ディレクトリは存在します。ディレクトリを作成してみてください: mkdir('{$path}', 0755, true)";
            } else {
                $suggestions[] = "親ディレクトリも存在しません: {$parentDir}";
            }
        } elseif ($pathInfo['exists'] && !$pathInfo['is_dir']) {
            $suggestions[] = "パスは存在しますが、ディレクトリではありません。";
            
            if ($pathInfo['is_file']) {
                $suggestions[] = "これはファイルです。ディレクトリが必要な場合は、別の名前を使用してください。";
            }
        } elseif ($pathInfo['is_dir']) {
            if (!$pathInfo['readable']) {
                $suggestions[] = "ディレクトリが読み込み不可です。権限を確認してください: chmod +r '{$path}'";
            }
            
            if (!$pathInfo['writable']) {
                $suggestions[] = "ディレクトリが書き込み不可です。権限を確認してください: chmod +w '{$path}'";
            }
            
            if (!$pathInfo['executable']) {
                $suggestions[] = "ディレクトリが実行不可です。権限を確認してください: chmod +x '{$path}'";
            }
        }
        
        return [
            'path_info' => $pathInfo,
            'suggestions' => $suggestions
        ];
    }
}

// 使用例
$debugger = new DirectoryDebugger();

// パスの詳細情報を取得
$pathInfo = $debugger->debugPath('/var/www/html');
echo "パス情報:\n";
print_r($pathInfo);

// ディレクトリ構造の検証
$requiredDirs = ['uploads', 'cache', 'logs', 'temp'];
$validation = $debugger->validateDirectoryStructure('/var/www/myapp', $requiredDirs);
echo "\nディレクトリ構造の検証:\n";
print_r($validation);

// 問題のトラブルシューティング
$troubleshooting = $debugger->troubleshootDirectoryIssues('/var/www/problematic');
echo "\nトラブルシューティング:\n";
foreach ($troubleshooting['suggestions'] as $suggestion) {
    echo "- {$suggestion}\n";
}
?>

クロスプラットフォーム対応

<?php
class CrossPlatformDirectoryHandler
{
    private $isWindows;
    
    public function __construct()
    {
        $this->isWindows = (PHP_OS_FAMILY === 'Windows');
    }
    
    public function normalizePath($path)
    {
        // パス区切り文字を統一
        $normalized = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
        
        // 連続する区切り文字を単一に
        $normalized = preg_replace('#' . preg_quote(DIRECTORY_SEPARATOR) . '+#', DIRECTORY_SEPARATOR, $normalized);
        
        return $normalized;
    }
    
    public function isAbsolutePath($path)
    {
        if ($this->isWindows) {
            // Windows: C:\ や \\server\share のような形式
            return preg_match('/^[a-zA-Z]:\\\\|\\\\\\\\.+/', $path);
        } else {
            // Unix系: / で始まる
            return substr($path, 0, 1) === '/';
        }
    }
    
    public function safeIsDir($path)
    {
        $normalizedPath = $this->normalizePath($path);
        
        // セキュリティチェック
        if (strpos($normalizedPath, '..') !== false) {
            throw new Exception("危険なパスが検出されました: {$path}");
        }
        
        return is_dir($normalizedPath);
    }
    
    public function createDirectory($path, $permissions = 0755)
    {
        $normalizedPath = $this->normalizePath($path);
        
        if (is_dir($normalizedPath)) {
            return true;
        }
        
        // Windowsでは権限の概念が異なるため調整
        if ($this->isWindows) {
            return mkdir($normalizedPath, 0777, true);
        } else {
            return mkdir($normalizedPath, $permissions, true);
        }
    }
    
    public function getHomeDirectory()
    {
        if ($this->isWindows) {
            return $_SERVER['USERPROFILE'] ?? $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH'];
        } else {
            return $_SERVER['HOME'] ?? posix_getpwuid(posix_getuid())['dir'];
        }
    }
    
    public function getTempDirectory()
    {
        // システムの一時ディレクトリを取得
        $tempDirs = [
            sys_get_temp_dir(),
            '/tmp',
            '/var/tmp',
            'C:\\Windows\\Temp',
            'C:\\Temp'
        ];
        
        foreach ($tempDirs as $dir) {
            if (is_dir($dir) && is_writable($dir)) {
                return $this->normalizePath($dir);
            }
        }
        
        throw new Exception("利用可能な一時ディレクトリが見つかりません");
    }
    
    public function listSystemDirectories()
    {
        $directories = [];
        
        if ($this->isWindows) {
            $systemDirs = [
                'C:\\Windows',
                'C:\\Program Files',
                'C:\\Program Files (x86)',
                $this->getHomeDirectory() . '\\Documents',
                $this->getHomeDirectory() . '\\Desktop'
            ];
        } else {
            $systemDirs = [
                '/bin',
                '/usr',
                '/var',
                '/etc',
                '/home',
                '/tmp'
            ];
        }
        
        foreach ($systemDirs as $dir) {
            if (is_dir($dir)) {
                $directories[] = [
                    'path' => $dir,
                    'readable' => is_readable($dir),
                    'writable' => is_writable($dir)
                ];
            }
        }
        
        return $directories;
    }
}

// 使用例
$handler = new CrossPlatformDirectoryHandler();

echo "プラットフォーム情報:\n";
echo "OS: " . PHP_OS_FAMILY . "\n";
echo "ホームディレクトリ: " . $handler->getHomeDirectory() . "\n";
echo "一時ディレクトリ: " . $handler->getTempDirectory() . "\n\n";

// パスの正規化
$testPaths = [
    '/var/www/html',
    'C:\\Users\\Test\\Documents',
    './uploads/../cache',
    '/tmp//logs///debug'
];

foreach ($testPaths as $path) {
    echo "元のパス: {$path}\n";
    echo "正規化後: " . $handler->normalizePath($path) . "\n";
    echo "絶対パス: " . ($handler->isAbsolutePath($path) ? 'Yes' : 'No') . "\n\n";
}

// システムディレクトリの一覧
$systemDirs = $handler->listSystemDirectories();
echo "システムディレクトリ:\n";
foreach ($systemDirs as $dir) {
    printf("%-30s [R:%s W:%s]\n", 
        $dir['path'], 
        $dir['readable'] ? 'Y' : 'N', 
        $dir['writable'] ? 'Y' : 'N'
    );
}
?>

まとめ

is_dir()関数は、PHPでファイルシステム操作を行う際の基本的かつ重要な関数です。単純にディレクトリかどうかを判定するだけでなく、以下のような場面で幅広く活用できます:

  1. ファイルアップロード処理:アップロード先ディレクトリの存在確認と作成
  2. ログ管理システム:ログディレクトリの初期化と管理
  3. 設定ファイル管理:設定ディレクトリの検証と自動セットアップ
  4. キャッシュシステム:キャッシュディレクトリの管理と清理
  5. セキュリティ対策:パストラバーサル攻撃の防止と権限チェック

開発時の重要なポイント

  • セキュリティ: 常にパストラバーサル攻撃を意識し、適切な権限チェックを実装する
  • エラーハンドリング: ディレクトリが存在しない場合の適切な処理を用意する
  • クロスプラットフォーム: Windows と Unix系 OSの違いを考慮したコードを書く
  • パフォーマンス: 大量のファイル操作を行う場合はキャッシュ機能を活用する
  • 権限管理: 適切なディレクトリ権限の設定と検証を行う

is_dir()は単純な関数ですが、適切に活用することで安全で効率的なファイルシステム操作を実現できます。この記事で紹介した実例を参考に、あなたのプロジェクトでも効果的に活用してください。

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