Как получить канонизированный путь (реальный путь) несуществующего файла в PHP?

script.php

$filename = realpath(sprintf("%s/%s", getcwd(), $argv[1]));
var_dump($filename);

Давайте попробуем некоторые вещи

[/foo/bar/bof] $ php script.php ../foo.txt
string(16) "/foo/bar/foo.txt"

[/foo/bar/bof] $ php script.php ../nonexistent.txt
bool(false)

Проклятье! realpath возвращает false, потому что файл не существует.

Что я хотел бы увидеть для ../nonexsitent.txt является

string(24) "/foo/bar/nonexistent.txt"

Как я могу получить канонизированный путь для любого относительного пути в PHP?

Примечание. Я видел несколько вопросов, касающихся разрешения путей символьных ссылок. Ответы на эти вопросы не относятся к моему вопросу.

2 ответа

Решение

Это лучшее, что я мог придумать

function canonicalize_path($path, $cwd=null) {

  // don't prefix absolute paths
  if (substr($path, 0, 1) === "/") {
    $filename = $path;
  }

  // prefix relative path with $root
  else {
    $root      = is_null($cwd) ? getcwd() : $cwd;
    $filename  = sprintf("%s/%s", $root, $path);
  }

  // get realpath of dirname
  $dirname   = dirname($filename);
  $canonical = realpath($dirname);

  // trigger error if $dirname is nonexistent
  if ($canonical === false) {
    trigger_error(sprintf("Directory `%s' does not exist", $dirname), E_USER_ERROR);
  }

  // prevent double slash "//" below
  if ($canonical === "/") $canonical = null;

  // return canonicalized path
  return sprintf("%s/%s", $canonical, basename($filename));
}

Это требует, чтобы все каталоги в пути существовали. basename пути - единственная часть, которая может не существовать.

Будет выдана ошибка в том случае, если dirname не существует.

Я создал это:

$path_canonicalize = function($str, $started = false) use(&$path_canonicalize)
{
    $str = str_replace('/', DIRECTORY_SEPARATOR, $str).DIRECTORY_SEPARATOR;

    if (!$started)
        $str = preg_replace("/".preg_quote(DIRECTORY_SEPARATOR, "'".DIRECTORY_SEPARATOR."'")."{2,}/", DIRECTORY_SEPARATOR, $str);

    $pos = strpos($str, '..'.DIRECTORY_SEPARATOR);
    if ($pos !== false)
    {
        $part = trim(substr($str, 0, $pos), DIRECTORY_SEPARATOR);
        $str = $path_canonicalize(trim(substr($part, 0, strrpos($part, DIRECTORY_SEPARATOR)), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.trim(substr($str, $pos+3), DIRECTORY_SEPARATOR), true);
    }
    return rtrim($str, DIRECTORY_SEPARATOR);
};

/*
Try those cases to check the consistency:
$str = __DIR__.'/template//////../header//..';
$str = __DIR__.'/template///..///../header//..';
$str = __DIR__.'/template/../header/..';
$str = __DIR__.'/template/../header/../';
$str = __DIR__.'/template/../header/..//';
$str = __DIR__.'/template/../header/..///';
$str = __DIR__.'/template/../header/..///..';
$str = __DIR__.'/template/../header/..///../';
$str = __DIR__.'/template\\..\\header\\..';
*/
$str = __DIR__.'/template/../header/..///..//';
echo 'original: '.$str.PHP_EOL;
echo 'normalized: '.$path_canonicalize($str).PHP_EOL;

Некоторые проблемы:

  1. Процедура не проверяет, является ли данный путь относительным или абсолютным.
  2. Рекомендуется указывать абсолютный путь, однако работает и для относительных путей. Подпрограмма обрабатывает все как строку, а не как файловую систему.
  3. Окончательный результат удаляет разделитель каталогов из начала и конца строки.
  4. Не поддерживает одну точку ./ или же /.
Другие вопросы по тегам