3v4l.org

run code in 300+ PHP versions simultaneously
<?php class URL { /* * In the native implementation, all the properties would be coerced to the correct type when setting them * You can't do this in userland because of the query element - in order for the array elements to be writable * without overwriting the whole array, you cannot use accessors :-( */ /** * @var string */ public $scheme; /** * @var string */ public $user; /** * @var string */ public $pass; /** * @var string */ public $host; /** * @var int */ public $port; /** * @var string */ public $path; /** * @var array */ public $query = []; /** * @var string */ public $fragment; /** * Wrapper for parse_url() * * @param string $string * @return URL * @throws InvalidArgumentException */ public static function createFromString($string) { if (false === $parts = parse_url($string)) { throw new InvalidArgumentException($string . ' could not be parsed as a valid URL'); } return new static( isset($parts['scheme']) ? $parts['scheme'] : null, isset($parts['user']) ? $parts['user'] : null, isset($parts['pass']) ? $parts['pass'] : null, isset($parts['host']) ? $parts['host'] : null, isset($parts['port']) ? $parts['port'] : null, isset($parts['path']) ? $parts['path'] : null, isset($parts['query']) ? $parts['query'] : null, isset($parts['fragment']) ? $parts['fragment'] : null ); } /** * Resolve $target as a relative URL against $source, using the same rules as a browser, so for example * * $source = http://google.com/ $target = /foo result = http://google.com/foo * $source = http://google.com/foo $target = bar result = http://google.com/bar * $source = http://google.com/foo $target = http://google.com/baz result = http://google.com/baz * * @param string|URL $source * @param string|URL $target * @return URL */ public static function resolve($source, $target) { if (!($source instanceof static)) { $source = static::createFromString((string) $source); } if (!($target instanceof static)) { $target = static::createFromString((string) $target); } // returning the same instance sometimes but not others would be confusing $result = clone $target; if (!isset($target->scheme)) { // anything with a scheme is considered absolute if (isset($target->host)) { // similarly anything with a host is absolute, just add a scheme is we have one if (isset($source->scheme)) { $result->scheme = $source->scheme; } } else { // host/scheme portion not specified, inherit from source foreach (['scheme', 'user', 'pass', 'host', 'port'] as $prop) { if (isset($source->{$prop})) { $result->{$prop} = $source->{$prop}; } } if ($target->path[0] === '/') { // If the target path is absolute, canonicalize it and use it $resultPath = self::resolveCanonicalPathComponents($target->path); } else { // Target path is relative // First we resolve the source path to a canonical and remove the file name component $sourcePath = self::resolveCanonicalPathComponents($source->path); array_pop($sourcePath); // Now resolve the target path against the source $resultPath = self::resolveCanonicalPathComponents($target->path, $sourcePath); } $result->path = '/' . implode('/', $resultPath); // The query and fragment elements are not inheritable so we don't touch them } } return $result; } /** * Normalise a path, resolving empty, . and .. components, optionally against another path * * @param $path * @param array $target * @return array */ private static function resolveCanonicalPathComponents($path, array $target = []) { // strip empty components and resolve . and .. foreach (preg_split('#[\\\\/]+#', $path, -1, PREG_SPLIT_NO_EMPTY) as $component) { switch ($component) { case '.': // current directory - do nothing break; case '..': // up a level array_pop($target); break; default: array_push($target, $component); break; } } // add a trailing empty element if path refers to a directory $lastChar = $path[strlen($path) - 1]; if ($lastChar === '/' || $lastChar === '\\') { array_push($target, ''); } return $target; } /** * Constructor takes components as individual arguments * * @param string $scheme * @param string $user * @param string $pass * @param string $host * @param int $port * @param string $path * @param string|array $query * @param string $fragment */ public function __construct($scheme = null, $user = null, $pass = null, $host = null, $port = null, $path = null, $query = null, $fragment = null) { foreach (['scheme', 'user', 'pass', 'host', 'path', 'fragment'] as $stringProp) { if (${$stringProp} !== null) { $this->{$stringProp} = (string) ${$stringProp}; } } if ($port !== null) { $this->port = (int) $port; } if ($query !== null) { if (is_scalar($query)) { parse_str((string) $query, $queryParsed); } else { $queryParsed = (array) $query; } $this->query = $queryParsed; } } /** * Forms all non-null components into a URL * * @return string */ public function __toString() { $result = ''; if (isset($this->scheme)) { $result = $this->scheme . ':'; } if (isset($this->host)) { $result .= '//'; if (isset($this->user)) { $result .= $this->user; if (isset($this->pass)) { $result .= ':' . $this->pass; } $result .= '@'; } $result .= $this->host; if (isset($this->port)) { $result .= ':' . $this->port; } } if (isset($this->path)) { $result .= $this->path; } if (isset($this->query) && $this->query !== []) { $result .= '?' . http_build_query($this->query); } if (isset($this->fragment)) { $result .= '#' . $this->fragment; } return $result; } } $url = URL::createFromString('http://google.com/'); $url->query['foo'] = [1, 2, 3]; echo $url . "\n"; echo URL::resolve('http://google.com/', '/foo') . "\n"; echo URL::resolve('http://google.com/foo', 'bar/') . "\n"; echo URL::resolve('http://google.com/foo', 'http://google.com/baz') . "\n";

preferences:
42.95 ms | 402 KiB | 5 Q