Работа с путями в PHP: от базовых приёмов до продвинутых сценариев

Раздел: Работа с файлами -> Работа с файлами

Пути к файлам и директориям в PHP: основные подходы и примеры

Работа с файловой системой в PHP часто требует точного указания расположения файлов и папок. Понимание механизмов построения путей помогает избежать ошибок при переносе кода между средами (локальный сервер, хостинг, Docker) и упрощает сопровождение. В этой статье разобраны основные методы работы с путями, от простых констант до универсальных функций.

Основной подход: использование функции realpath() для канонизации пути. Она преобразует относительный или потенциально некорректный путь в абсолютный, разрешая символические ссылки и удаляя лишние элементы (точки, двойные разделители). Пример:


$relativePath = './documents/../files/./info.txt';
$absolutePath = realpath($relativePath);
echo $absolutePath; // /home/user/project/files/info.txt
    

Этот метод особенно полезен, когда нужно быть уверенным в фактическом расположении файла перед его использованием. Однако realpath() возвращает false, если путь не существует. В таких случаях стоит применить is_file() или file_exists() предварительно.

Типичные ошибки:

  • Игнорирование возвращаемого false приводит к непредсказуемому поведению.
  • Разные разделители в Windows (обратный слеш) и Linux (прямой). realpath() всегда возвращает прямой слеш, но при конкатенации путей на Windows нужно использовать DIRECTORY_SEPARATOR.
  • Работа с несуществующими путями: realpath() не создаёт папки, только проверяет существование.

Решение: для надёжности комбинировать realpath() с проверкой через is_dir() или file_exists(). При необходимости создания путей используйте mkdir() после проверки.

Как получить полный путь к текущему скрипту?

Магическая константа __FILE__ возвращает абсолютный путь к исполняемому файлу. Для получения директории используется __DIR__ (с PHP 5.3). Пример:


// file: /var/www/app/index.php
echo __FILE__; // /var/www/app/index.php
echo __DIR__;  // /var/www/app
    

Это удобно для построения путей относительно текущего скрипта: __DIR__ . '/config.php'. Однако при использовании в подключаемых файлах (include) __FILE__ всё равно указывает на исходный файл, а не на тот, откуда был вызван include. Для получения пути вызывающего скрипта используйте $_SERVER['SCRIPT_FILENAME'] или getcwd().

Проблема: если файл подключается из другого каталога, __DIR__ остаётся неизменным, что может привести к неожиданным путям.

Решение: применять realpath(__DIR__ . '/../config.php') для нормализации.

Как объединить части пути в кросс-платформенной среде?

Использование константы DIRECTORY_SEPARATOR гарантирует правильный разделитель (на Windows - обратный слеш \, на Linux - прямой). Пример сборки пути:


$base = __DIR__;
$filename = 'uploads' . DIRECTORY_SEPARATOR . 'image.jpg';
$fullPath = $base . DIRECTORY_SEPARATOR . $filename;
    

Более элегантный способ - функция sprintf() или константа PATH_SEPARATOR для разделения нескольких путей (например, в include_path). Однако для обычного объединения удобнее rtrim($base, '/\\') . '/' . ltrim($path, '/\\') на случай, если разделитель уже известен.

Ошибка: смешивание прямых и обратных слешей в одной строке на Windows может привести к сбоям.

Решение: после сборки пути применить str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path).

Как определить, является ли путь абсолютным?

Простая проверка первого символа: для Linux - '/', для Windows - буква диска с двоеточием (например, 'C:'). Универсальная функция:


function isAbsolutePath(string $path): bool {
    if (substr($path, 0, 1) === '/') return true;
    if (preg_match('/^[a-zA-Z]:\\\\/', $path)) return true; // Windows
    return false;
}
    

Это помогает при решении, нужно ли к пути добавлять базовую директорию.

Как безопасно проверить существование и тип файла?

Функции file_exists(), is_file(), is_dir() работают с путями. Пример:


$path = '/var/www/data';
if (is_dir($path)) {
    echo 'Это директория';
} elseif (is_file($path)) {
    echo 'Это файл';
} else {
    echo 'Путь не существует или недоступен';
}
    

Обратите внимание: file_exists() возвращает true и для файлов, и для директорий. Используйте is_file() и is_dir() для точности.

Проблема: кеширование результатов (stat cache) может привести к устаревшим данным.

Решение: очистить кеш функцией clearstatcache() перед повторной проверкой, особенно в долго работающих скриптах.

Как получить информацию о пути: имя файла, расширение, папку?

Функция pathinfo() возвращает массив с частями пути:


$info = pathinfo('/var/www/img/photo.jpg');
// $info['dirname'] => '/var/www/img'
// $info['basename'] => 'photo.jpg'
// $info['extension'] => 'jpg'
// $info['filename'] => 'photo'
    

Также доступны константы: PATHINFO_DIRNAME, PATHINFO_BASENAME и т.д. Полезно при переименовании или смене расширения.

Как обойти файлы в директории по шаблону?

Функция glob() находит пути, соответствующие шаблону (использует правила shell). Пример:


$logFiles = glob('/var/log/*.log');
foreach ($logFiles as $file) {
    echo basename($file) . '\n';
}
    

Можно указать флаги: GLOB_BRACE для перечисления альтернатив (*.{php,html}), GLOB_ONLYDIR - только директории.

Ошибка: glob() не проходит по поддиректориям рекурсивно.

Решение: использовать RecursiveDirectoryIterator или функцию glob() с GLOB_REC (но она не входит в стандартный набор; требуется ручная рекурсия).

Как рекурсивно обойти все файлы и папки?

Использование итератора RecursiveDirectoryIterator в сочетании с RecursiveIteratorIterator. Пример:


$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www', RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $fileinfo) {
    echo $fileinfo->getPathname() . '\n';
}
    

Константа SKIP_DOTS убирает . и ... Для фильтрации по типу используйте RecursiveCallbackFilterIterator.

Расширенные примеры работы с путями

Ниже приведены более сложные сценарии, которые часто встречаются на практике.

Пример 1. Создание и нормализация пути с учётом символических ссылок

Пример

$target = '/var/www/link_to_project'; // символическая ссылка на /home/user/project
$resolved = realpath($target);
if ($resolved === false) {
    echo 'Целевой путь не существует';
} else {
    echo 'Реальный путь: ' . $resolved; // /home/user/project
}
Реальный путь: /home/user/project

Пояснение:

realpath() прослеживает символические ссылки и возвращает канонический абсолютный путь. Если ссылка ведёт в несуществующее место - false.

Пример 2. Построение универсального относительного пути между двумя абсолютными

Пример

function getRelativePath(string $from, string $to): string {
    $fromParts = explode('/', rtrim($from, '/'));
    $toParts = explode('/', rtrim($to, '/'));
    $commonLength = 0;
    $min = min(count($fromParts), count($toParts));
    for ($i = 0; $i < $min; $i++) {
        if ($fromParts[$i] === $toParts[$i]) $commonLength++;
        else break;
    }
    $ups = array_fill(0, count($fromParts) - $commonLength, '..');
    $downs = array_slice($toParts, $commonLength);
    return implode('/', array_merge($ups, $downs)) ?: '.';
}

echo getRelativePath('/var/www/html/app', '/var/www/html/app/config/db.php');
// config/db.php
config/db.php

Использование:

Полезно при генерации ссылок внутри проекта, особенно если проект может быть развёрнут в разных корнях.

Пример 3. Рекурсивное создание директории по пути

Пример

$path = '/tmp/nested/deep/folder';
if (!is_dir($path)) {
    // Третий параметр true разрешает рекурсивное создание
    if (mkdir($path, 0755, true)) {
        echo 'Директория создана: ' . realpath($path);
    } else {
        echo 'Ошибка создания';
    }
}
Директория создана: /tmp/nested/deep/folder

Важно:

Параметр true (recursive) в mkdir() доступен с PHP 5.0.0. Без него нужно создавать каждую вложенную папку вручную.

Пример 4. Безопасное чтение файла с проверкой расширения и размера

Пример

function readAllowedFile(string $path): ?string {
    $allowedExt = ['txt', 'csv'];
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExt)) {
        echo 'Расширение не разрешено';
        return null;
    }
    if (!is_file($path) || filesize($path) > 1024 * 1024) {
        echo 'Файл не существует или слишком большой';
        return null;
    }
    return file_get_contents($path);
}
(содержимое файла, если проверка пройдена)

Пояснение:

pathinfo() с константой PATHINFO_EXTENSION извлекает только расширение. Проверка размера защищает от переполнения памяти при считывании.

Пример 5. Поиск всех файлов с определённым расширением рекурсивно (без итераторов)

Пример

function findFilesByExt(string $dir, string $ext): array {
    $result = [];
    $ext = ltrim($ext, '.');
    $iterator = new RecursiveDirectoryIterator($dir);
    $filtered = new RecursiveCallbackFilterIterator($iterator, function ($current) use ($ext) {
        return $current->isFile() && $current->getExtension() === $ext;
    });
    foreach (new RecursiveIteratorIterator($filtered) as $fileinfo) {
        $result[] = $fileinfo->getPathname();
    }
    return $result;
}

$files = findFilesByExt('/var/www', 'php');
print_r($files);
Array
(
    [0] => /var/www/index.php
    [1] => /var/www/config.php
    ...
)

Примечание:

getExtension() доступен с PHP 5.3.6. Для более старых версий используйте pathinfo($current->getFilename(), PATHINFO_EXTENSION).

PHP: пути к файлам и директориям - comments

En
Php file directory (php)