29

I need a function that given a relative URL and a base returns an absolute URL. I've searched and found many functions that do it different ways.

resolve("../abc.png", "http://example.com/path/thing?foo=bar")
# returns http://example.com/abc.png

Is there a canonical way?

On this site I see great examples for python and c#, lets get a PHP solution.

4

7 Answers 7

8

Perhaps this article could help?

http:// nashruddin.com/PHP_Script_for_Converting_Relative_to_Absolute_URL

Edit: reproduced code below for convenience

<?php
    function rel2abs($rel, $base)
    {
        /* return if already absolute URL */
        if (parse_url($rel, PHP_URL_SCHEME) != '' || substr($rel, 0, 2) == '//') return $rel;

        /* queries and anchors */
        if ($rel[0]=='#' || $rel[0]=='?') return $base.$rel;

        /* parse base URL and convert to local variables:
         $scheme, $host, $path */
        extract(parse_url($base));

        /* remove non-directory element from path */
        $path = preg_replace('#/[^/]*$#', '', $path);

        /* destroy path if relative url points to root */
        if ($rel[0] == '/') $path = '';

        /* dirty absolute URL */
        $abs = "$host$path/$rel";

        /* replace '//' or '/./' or '/foo/../' with '/' */
        $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
        for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}

        /* absolute URL is ready! */
        return $scheme.'://'.$abs;
    }
?>
Sign up to request clarification or add additional context in comments.

2 Comments

This implementation doesn't work if the base URL is: foobar.com With no trailing /. It also doesn't respect port numbers in base URLs.
It's by design ignoring any RFC specs that are in use for that anyway. I would say it's guessing the absolute URL or a relative one.
7

Another solution in case you already use GuzzleHttp.

This solution is based on an internal method of GuzzleHttp\Client.

use GuzzleHttp\Psr7\UriResolver;
use GuzzleHttp\Psr7\Utils;

function resolve(string $uri, ?string $base_uri): string
{
    $uri = Utils::uriFor(trim($uri));

    if (isset($base_uri)) {
        $uri = UriResolver::resolve(Utils::uriFor(trim($base_uri)), $uri);
    }

    // optional: set default scheme if missing
    $uri = $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;

    return (string)$uri;
}

EDIT: the source code was updated as suggested by myriacl

1 Comment

This code is a bit old, but the best solution in my opinion, here is the modern version ` use GuzzleHttp\Psr7\UriResolver; use GuzzleHttp\Psr7\Utils; function resolve($uri, $base_uri):string { $uri = Utils::uriFor($uri); if (isset($base_uri)) { $uri = UriResolver::resolve( Utils::uriFor($base_uri), $uri); } // optional: set default scheme if missing $uri = $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; return (string) $uri; } `
4

If your have pecl-http, you can use http://php.net/manual/en/function.http-build-url.php

<?php
$url_parts = parse_url($relative_url);
$absolute = http_build_url($source_url, $url_parts, HTTP_URL_JOIN_PATH);

Ex:

<?php
function getAbsoluteURL($source_url, $relative_url)
{
    $url_parts = parse_url($relative_url);
    return http_build_url($source_url, $url_parts, HTTP_URL_JOIN_PATH);
}
echo getAbsoluteURL('http://foo.tw/a/b/c', '../pic.jpg') . "\n";
// http://foo.tw/a/pic.jpg

echo getAbsoluteURL('http://foo.tw/a/b/c/', '../pic.jpg') . "\n";
// http://foo.tw/a/b/pic.jpg

echo getAbsoluteURL('http://foo.tw/a/b/c/', 'http://bar.tw/a.js') . "\n";
// http://bar.tw/a.js

echo getAbsoluteURL('http://foo.tw/a/b/c/', '/robots.txt') . "\n";
// http://foo.tw/robots.txt

2 Comments

FYI, the http_build_url method is part of a PECL extension that is not bundled with PHP.
This works as described, but watch out when installing the pecl extension. The newly released 2.0 version now uses namespaces and does not provide this function directly. So I installed an older Version and it works perfectly for me: pecl install pecl_http-1.7.6
1

other tools that are already linked in page linked in pguardiario's comment: http://publicmind.in/blog/urltoabsolute/ , https://github.com/monkeysuffrage/phpuri .

and i have found other tool from comment in http://nadeausoftware.com/articles/2008/05/php_tip_how_convert_relative_url_absolute_url :

require_once 'Net/URL2.php';
$base = new Net_URL2('http://example.org/foo.html');
$absolute = (string)$base->resolve('relative.html#bar'); 

Comments

1

Here is another function that can handle protocol relative urls

<?php
function getAbsoluteURL($to, $from = null) {
    $arTarget = parse_url($to);
    $arSource = parse_url($from);
    $targetPath = isset($arTarget['path']) ? $arTarget['path'] : '';

    if (isset($arTarget['host'])) {
        if (!isset($arTarget['scheme'])) {
            $proto = isset($arSource['scheme']) ? "{$arSource['scheme']}://" : '//';
        } else {
            $proto = "{$arTarget['scheme']}://";
        }
        $baseUrl = "{$proto}{$arTarget['host']}" . (isset($arTarget['port']) ? ":{$arTarget['port']}" : '');
    } else {
        if (isset($arSource['host'])) {
            $proto = isset($arSource['scheme']) ? "{$arSource['scheme']}://" : '//';
            $baseUrl = "{$proto}{$arSource['host']}" . (isset($arSource['port']) ? ":{$arSource['port']}" : '');
        } else {
            $baseUrl = '';
        }
        $arPath = [];

        if ((empty($targetPath) || $targetPath[0] !== '/') && !empty($arSource['path'])) {
            $arTargetPath = explode('/', $targetPath);
            if (empty($arSource['path'])) {
                $arPath = [];
            } else {
                $arPath = explode('/', $arSource['path']);
                array_pop($arPath);
            }
            $len = count($arPath);
            foreach ($arTargetPath as $idx => $component) {
                if ($component === '..') {
                    if ($len > 1) {
                        $len--;
                        array_pop($arPath);
                    }
                } elseif ($component !== '.') {
                    $len++;
                    array_push($arPath, $component);
                }
            }
            $targetPath = implode('/', $arPath);
        }
    }

    return $baseUrl . $targetPath;
}

// SAMPLES
// Absolute path => https://www.google.com/doubleclick/
echo getAbsoluteURL('/doubleclick/', 'https://www.google.com/doubleclick/insights/') . "\n";
// Relative path 1 => https://www.google.com/doubleclick/studio
echo getAbsoluteURL('../studio', 'https://www.google.com/doubleclick/insights/') . "\n";
// Relative path 2 => https://www.google.com/doubleclick/insights/case-studies.html
echo getAbsoluteURL('./case-studies.html', 'https://www.google.com/doubleclick/insights/') . "\n";
// Relative path 3 => https://www.google.com/doubleclick/insights/case-studies.html
echo getAbsoluteURL('case-studies.html', 'https://www.google.com/doubleclick/insights/') . "\n";
// Protocol relative url => https://www.google.com/doubleclick/
echo getAbsoluteURL('//www.google.com/doubleclick/', 'https://www.google.com/doubleclick/insights/') . "\n";
// Empty path => https://www.google.com/doubleclick/insights/
echo getAbsoluteURL('', 'https://www.google.com/doubleclick/insights/') . "\n";
// Different url => http://www.yahoo.com/
echo getAbsoluteURL('http://www.yahoo.com/', 'https://www.google.com') . "\n";

Comments

0
function absoluteUri($Path, $URI)
{   # Requires PHP4 or better.
    $URL = parse_url($URI);
    $Str = "{$URL['scheme']}://";

    if (isset($URL['user']) || isset($URL['pass']))
        $Str .= "{$URL['user']}:{$URL['pass']}@";

    $Str .= $URL['host'];

    if (isset($URL['port']))
        $Str .= ":{$URL['port']}";

    $Str .= realpath($URL['path'] . $Path); # This part might have an issue on windows boxes.

    if (isset($URL['query']))
        $Str .= "?{$URL['query']}";

    if (isset($URL['fragment']))
        $Str .= "#{$URL['fragment']}";

    return $Str;
}

absoluteUri("../abc.png", "http://example.com/path/thing?foo=bar");
# Should return "http://example.com/abc.png?foo=bar" on Linux boxes.

1 Comment

On linux boxes but not windows? This seems to be the only solution so far that is not even worth considering.
-1

I noticed the upvoted answer above uses RegEx, which can be dangerous when dealing with URLs.

This function will resolve relative URLs to a given current page url in $pgurl without regex. It successfully resolves:

/home.php?example types,

same-dir nextpage.php types,

../...../.../parentdir types,

full http://example.net urls,

and shorthand //example.net urls

//Current base URL (you can dynamically retrieve from $_SERVER)
$pgurl = 'http://example.com/scripts/php/absurl.php';

function absurl($url) {
 global $pgurl;
 if(strpos($url,'://')) return $url; //already absolute
 if(substr($url,0,2)=='//') return 'http:'.$url; //shorthand scheme
 if($url[0]=='/') return parse_url($pgurl,PHP_URL_SCHEME).'://'.parse_url($pgurl,PHP_URL_HOST).$url; //just add domain
 if(strpos($pgurl,'/',9)===false) $pgurl .= '/'; //add slash to domain if needed
 return substr($pgurl,0,strrpos($pgurl,'/')+1).$url; //for relative links, gets current directory and appends new filename
}

function nodots($path) { //Resolve dot dot slashes, no regex!
 $arr1 = explode('/',$path);
 $arr2 = array();
 foreach($arr1 as $seg) {
  switch($seg) {
   case '.':
    break;
   case '..':
    array_pop($arr2);
    break;
   case '...':
    array_pop($arr2); array_pop($arr2);
    break;
   case '....':
    array_pop($arr2); array_pop($arr2); array_pop($arr2);
    break;
   case '.....':
    array_pop($arr2); array_pop($arr2); array_pop($arr2); array_pop($arr2);
    break;
   default:
    $arr2[] = $seg;
  }
 }
 return implode('/',$arr2);
}

Usage Example:

echo nodots(absurl('../index.html'));

nodots() must be called after the URL is converted to absolute.

The dots function is kind of redundant, but is readable, fast, doesn't use regex's, and will resolve 99% of typical urls (if you want to be 100% sure, just extend the switch block to support 6+ dots, although I've never seen that many dots in a URL).

Hope this helps,

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.