php 从PHP中的绝对路径获取相对路径
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2637945/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Getting relative path from absolute path in PHP
提问by Young
I noticed some similar questions about this problem when I typed the title, but they seem not be in PHP. So what's the solution to it with a PHP function?
当我输入标题时,我注意到一些关于这个问题的类似问题,但它们似乎不在 PHP 中。那么用PHP函数解决它的方法是什么?
To be specified.
待指定。
$a="/home/apache/a/a.php";
$b="/home/root/b/b.php";
$relpath = getRelativePath($a,$b); //needed function,should return '../../root/b/b.php'
Any good ideas? Thanks.
有什么好主意吗?谢谢。
回答by Gordon
Try this one:
试试这个:
function getRelativePath($from, $to)
{
// some compatibility fixes for Windows paths
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
$from = str_replace('\', '/', $from);
$to = str_replace('\', '/', $to);
$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;
foreach($from as $depth => $dir) {
// find first non-matching dir
if($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}
This will give
这会给
$a="/home/a.php";
$b="/home/root/b/b.php";
echo getRelativePath($a,$b), PHP_EOL; // ./root/b/b.php
and
和
$a="/home/apache/a/a.php";
$b="/home/root/b/b.php";
echo getRelativePath($a,$b), PHP_EOL; // ../../root/b/b.php
and
和
$a="/home/root/a/a.php";
$b="/home/apache/htdocs/b/en/b.php";
echo getRelativePath($a,$b), PHP_EOL; // ../../apache/htdocs/b/en/b.php
and
和
$a="/home/apache/htdocs/b/en/b.php";
$b="/home/root/a/a.php";
echo getRelativePath($a,$b), PHP_EOL; // ../../../../root/a/a.php
回答by lucaferrario
Since we've had several answers, I decided to test them all and benchmark them. I used this paths to test:
由于我们有几个答案,我决定对它们全部进行测试并对其进行基准测试。我用这个路径来测试:
$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/";NOTE: if it is a folder, you have to put a trailing slash for functions to work correctly! So, __DIR__will not work. Use __FILE__instead or __DIR__ . '/'
$from = "/var/www/sites/web/mainroot/webapp/folder/sub/subf/subfo/subfol/subfold/lastfolder/";注意:如果是文件夹,则必须在尾部添加斜杠才能使函数正常工作!所以,__DIR__不会工作。使用__FILE__替代或__DIR__ . '/'
$to = "/var/www/sites/web/mainroot/webapp/folder/aaa/bbb/ccc/ddd";
$to = "/var/www/sites/web/mainroot/webapp/folder/aaa/bbb/ccc/ddd";
RESULTS: (decimal separator is comma, thousand separator is dot)
结果:(十进制分隔符是逗号,千位分隔符是点)
- Function by Gordon: result CORRECT, time for 100.000 execs 1,222seconds
- Function by Young: result CORRECT, time for 100.000 execs 1,540seconds
- Function by Ceagle: result WRONG(it works with some paths but fails with some others, like the ones used in the tests and written above)
- Function by Loranger: result WRONG(it works with some paths but fails with some others, like the ones used in the tests and written above)
- Gordon 的函数:结果正确,100.000 次执行的时间为1,222秒
- Young 的函数:结果正确,100.000 次执行的时间为1,540秒
- Ceagle 的函数:结果错误(它适用于某些路径,但适用于其他一些路径,例如测试中使用的和上面写的那些)
- Loranger 的函数:结果错误(它适用于某些路径,但适用于其他一些路径,例如测试中使用的和上面写的那些)
So, I suggest that you use Gordon's implementation! (the one marked as answer)
所以,我建议你使用 Gordon 的实现!(标记为答案的那个)
Young's one is good too and performs better with simple directory structures (like "a/b/c.php"), while Gordon's one performs better with complex structures, with lots of subdirectories (like the ones used in this benchmark).
Young 的也很好,在简单的目录结构(如“a/b/c.php”)上表现更好,而 Gordon 的在复杂结构中表现更好,有很多子目录(如本基准测试中使用的那些)。
NOTE: I write here below the results returned with $fromand $toas inputs, so you can verify that 2 of them are OK, while other 2 are wrong:
注意:这里我写的下面返回的结果$from,并$to作为输入,这样你就可以验证他们2个是OK,而另外2个是错误的:
- Gordon:
../../../../../../aaa/bbb/ccc/ddd--> CORRECT - Young:
../../../../../../aaa/bbb/ccc/ddd--> CORRECT - Ceagle:
../../../../../../bbb/ccc/ddd--> WRONG - Loranger:
../../../../../aaa/bbb/ccc/ddd--> WRONG
- 戈登:
../../../../../../aaa/bbb/ccc/ddd--> 正确 - 年轻:
../../../../../../aaa/bbb/ccc/ddd--> 正确 - Ceagle:
../../../../../../bbb/ccc/ddd--> 错 - 罗兰格:
../../../../../aaa/bbb/ccc/ddd--> 错
回答by webbiedave
Relative path? This seems more like a travel path. You seem to want to know the path one travels to get from path A to path B. If that's the case, you can explode$a and $b on '/'then inversely loop through the $aParts, comparing them to $bParts of the same index until the "common denominator" directory is found (recording the number of loops along the way). Then create an empty string and add '../'to it $numLoops-1 times then add to that $b minus the common denominator directory.
相对路径?这似乎更像是一条旅行路线。你似乎想知道一个沿从路径A到路径B.如果是这样的情况下,路径,你可以爆炸$ a和$ b在'/'通过$ Aparts的再负循环,比较他们在同一指数的$ bParts直到找到“公分母”目录(记录沿途循环次数)。然后创建一个空字符串并添加'../'$numLoops-1 次,然后添加到 $b 减去公分母目录。
回答by clockworkgeek
const DS = DIRECTORY_SEPARATOR; // for convenience
function getRelativePath($from, $to) {
$dir = explode(DS, is_file($from) ? dirname($from) : rtrim($from, DS));
$file = explode(DS, $to);
while ($dir && $file && ($dir[0] == $file[0])) {
array_shift($dir);
array_shift($file);
}
return str_repeat('..'.DS, count($dir)) . implode(DS, $file);
}
My attempt is deliberately simpler, although probably no different in performance. I'll leave benchmarking as an exercise for the curious reader. However, this is fairly robust and should be platform agnostic.
我的尝试故意更简单,尽管在性能上可能没有什么不同。我将把基准测试留给好奇的读者作为练习。但是,这相当健壮,应该与平台无关。
Bewarethe solutions using array_intersectfunctions as these will break if parallel directories have the same name. For example getRelativePath('start/A/end/', 'start/B/end/')would return "../end" because array_intersectfinds all the equal names, in this case 2 when there should only be 1.
请注意使用array_intersect函数的解决方案,因为如果并行目录具有相同的名称,这些解决方案将会中断。例如,getRelativePath('start/A/end/', 'start/B/end/')将返回“ ../end”,因为array_intersect找到所有相同的名称,在这种情况下为 2,而应该只有 1。
回答by ya.teck
This code is taken from Symfony URL generator https://github.com/symfony/Routing/blob/master/Generator/UrlGenerator.php
此代码取自 Symfony URL 生成器 https://github.com/symfony/Routing/blob/master/Generator/UrlGenerator.php
/**
* Returns the target path as relative reference from the base path.
*
* Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash.
* Both paths must be absolute and not contain relative parts.
* Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
* Furthermore, they can be used to reduce the link size in documents.
*
* Example target paths, given a base path of "/a/b/c/d":
* - "/a/b/c/d" -> ""
* - "/a/b/c/" -> "./"
* - "/a/b/" -> "../"
* - "/a/b/c/other" -> "other"
* - "/a/x/y" -> "../../x/y"
*
* @param string $basePath The base path
* @param string $targetPath The target path
*
* @return string The relative target path
*/
function getRelativePath($basePath, $targetPath)
{
if ($basePath === $targetPath) {
return '';
}
$sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
$targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath);
array_pop($sourceDirs);
$targetFile = array_pop($targetDirs);
foreach ($sourceDirs as $i => $dir) {
if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
unset($sourceDirs[$i], $targetDirs[$i]);
} else {
break;
}
}
$targetDirs[] = $targetFile;
$path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
// A reference to the same base directory or an empty subdirectory must be prefixed with "./".
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name
// (see http://tools.ietf.org/html/rfc3986#section-4.2).
return '' === $path || '/' === $path[0]
|| false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
? "./$path" : $path;
}
回答by Young
Based on Gordon's function,my solution is as follows:
基于戈登的功能,我的解决方案如下:
function getRelativePath($from, $to)
{
$from = explode('/', $from);
$to = explode('/', $to);
foreach($from as $depth => $dir)
{
if(isset($to[$depth]))
{
if($dir === $to[$depth])
{
unset($to[$depth]);
unset($from[$depth]);
}
else
{
break;
}
}
}
//$rawresult = implode('/', $to);
for($i=0;$i<count($from)-1;$i++)
{
array_unshift($to,'..');
}
$result = implode('/', $to);
return $result;
}
回答by Ceagle
Some Reason Gordon's didn't work for me.... Here's my solution
戈登对我不起作用的一些原因......这是我的解决方案
function getRelativePath($from, $to) {
$patha = explode('/', $from);
$pathb = explode('/', $to);
$start_point = count(array_intersect($patha,$pathb));
while($start_point--) {
array_shift($patha);
array_shift($pathb);
}
$output = "";
if(($back_count = count($patha))) {
while($back_count--) {
$output .= "../";
}
} else {
$output .= './';
}
return $output . implode('/', $pathb);
}
回答by loranger
I came to the same result using those array manipulations :
我使用这些数组操作得出了相同的结果:
function getRelativePath($path, $from = __FILE__ )
{
$path = explode(DIRECTORY_SEPARATOR, $path);
$from = explode(DIRECTORY_SEPARATOR, dirname($from.'.'));
$common = array_intersect_assoc($path, $from);
$base = array('.');
if ( $pre_fill = count( array_diff_assoc($from, $common) ) ) {
$base = array_fill(0, $pre_fill, '..');
}
$path = array_merge( $base, array_diff_assoc($path, $common) );
return implode(DIRECTORY_SEPARATOR, $path);
}
The second argument is the file which the path is relative to. It's optional so you can get the relative path regardless the webpage your currently are. In order to use it with @Young or @Gordon example, because you want to know the relative path to $b from $a, you'll have to use
第二个参数是路径相对于的文件。它是可选的,因此无论您当前所在的网页如何,您都可以获得相对路径。为了在@Young 或@Gordon 示例中使用它,因为您想知道从 $a 到 $b 的相对路径,您必须使用
getRelativePath($b, $a);
回答by kenorb
Simple one-liner for common scenarios:
常见场景的简单单行:
str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $filepath)
or:
或者:
substr($filepath, strlen(getcwd())+1)
To check if path is absolute, try:
要检查路径是否是绝对路径,请尝试:
$filepath[0] == DIRECTORY_SEPARATOR
回答by Samuel Adeshina
Here's what works for me. For some unknown reason, the most upvoted answer to this question didn't work as expected
这对我有用。由于某种未知原因,这个问题的最高投票答案没有按预期工作
public function getRelativePath($absolutePathFrom, $absolutePathDestination)
{
$absolutePathFrom = is_dir($absolutePathFrom) ? rtrim($absolutePathFrom, "\/")."/" : $absolutePathFrom;
$absolutePathDestination = is_dir($absolutePathDestination) ? rtrim($absolutePathDestination, "\/")."/" : $absolutePathDestination;
$absolutePathFrom = explode("/", str_replace("\", "/", $absolutePathFrom));
$absolutePathDestination = explode("/", str_replace("\", "/", $absolutePathDestination));
$relativePath = "";
$path = array();
$_key = 0;
foreach($absolutePathFrom as $key => $value)
{
if (strtolower($value) != strtolower($absolutePathDestination[$key]))
{
$_key = $key + 1;
for ($i = $key; $i < count($absolutePathDestination); $i++)
{
$path[] = $absolutePathDestination[$i];
}
break;
}
}
for ($i = 0; $i <= (count($absolutePathFrom) - $_key - 1); $i++)
{
$relativePath .= "../";
}
return $relativePath.implode("/", $path);
}
if $a = "C:\xampp\htdocs\projects\SMS\App\www\App\index.php"and
$b = "C:\xampp\htdocs\projects\SMS\App/www/App/bin/bootstrap/css/bootstrap.min.css"
如果$a = "C:\xampp\htdocs\projects\SMS\App\www\App\index.php"和
$b = "C:\xampp\htdocs\projects\SMS\App/www/App/bin/bootstrap/css/bootstrap.min.css"
Then $c, which is the relative path of $bfrom $a, will be
那么$c,这是$bfrom的相对路径$a,将是
$c = getRelativePath($a, $b) = "bin/bootstrap/css/bootstrap.min.css"
$c = getRelativePath($a, $b) = "bin/bootstrap/css/bootstrap.min.css"

