Scripts

URL class

Een class om gemakkelijker met urls te werken: samenvoegen, informatie eruit halen, opbouwen en veranderen. Doordat __toString geïmplementeerd is kan je de class bijna overal zonder aanpassingen gebruiken. Testcase.php bevat een aantal testjes waarin ook de mogelijkheden worden gedemonstreerd. De checks zijn nog niet helemaal lekker, en zouden eigenlijk volgens de RFC's die urls beschrijven moeten zijn, ware het niet dat die dingen zo lastig te lezen zijn. Ik denk niet dat hij blij is met IPv6 adressen.

URL.php
<?php

class URL
{
	private $url;
	
	static public function format()
	{
		$args = func_get_args();
	
		$url = vsprintf(array_shift($args), array_map('urlencode', $args));
	
		return new self($url);
	}
	
	static public function current()
	{
		return new self($_SERVER['REQUEST_URI']);
	}
	
	public function __construct($url = null)
	{
		if($url)
			$this->url = parse_url($url);
		else
			$this->url = array();
		
		if(isset($this->url['query']))
			$this->url['query'] = new URL_Query($this->url['query']);
		else
			$this->url['query'] = new URL_Query();
	}
	
	public function __get($part)
	{
		return $this->url[$part];
	}
	
	public function __isset($part)
	{
		return isset($this->url[$part]) && !is_null($this->url[$part]);
	}
	
	public function __set($part, $value)
	{
		switch($part) {
			case 'scheme':
				if(!self::isValidScheme($value))
					throw new InvalidArgumentException('Scheme can only contain alphanummeric characters and digits');
				break;
			
			case 'host':
				if(!self::isValidHost($value))
					throw new InvalidArgumentException('Invalid Host');
				break;
			
			case 'port':
				if(!self::isValidPort($value))
					throw new InvalidArgumentException('Invalid port number');
				break;
			
			case 'path':
				break;
			
			case 'query':
				if(is_string($value))
					$value = $this->url['query']->merge(new URL_Query($value));
				else if(!$value instanceof URL_Query)
					throw new InvalidArgumentException('Query has to be a query-string or an instance of URL_Query');
				break;
			
			case 'fragment':
				break;
			
			default:
				throw new InvalidArgumentException('Unknown part of url:' . $part);
		}
		
		$this->url[$part] = $value;
	}
	
	public function __toString()
	{
		return $this->generate();
	}
	
	public function generate()
	{
		$url = '';
		
		if(!empty($this->url['scheme']))
			$url .= $this->url['scheme'] . ':';
		
		if(!empty($this->url['host']))
			$url .= '//' . $this->url['host'];
		
		if(!empty($this->url['port']))
			$url .= ':' . $this->url['port'];
		
		if(!empty($this->url['host']) || !empty($this->url['port']) ||
			(!empty($this->url['path']) && substr($this->url['path'], 0, 1) == '/'))
			$url .= '/';
		
		if(!empty($this->url['path']))
			$url .= ltrim($this->url['path'], '/');
		
		if(count($this->url['query']))
			$url .= '?' . $this->url['query']->generate();
		
		if(!empty($this->url['fragment']))
			$url .= '#' . $this->url['fragment'];
			
		return $url;
	}
	
	public function merge(self $other)
	{
		$combined = new self();
		
		$combined->url = array_merge($this->url, $other->url);
		
		$combined->url['path'] = self::concatPath(
			isset($this->url['path'])   ? $this->url['path'] : '',
			isset($other->url['path']) ? $other->url['path'] : '');
		
		$combined->url['query'] = $this->url['query']->merge($other->url['query']);
		
		return $combined;
	}
	
	static private function concatPath($left, $right)
	{
		if(strlen($left) == 0)
			return $right;
		
		if(strlen($right) == 0)
			return $left;
		
		$left = explode('/', $left);
		
		$right = explode('/', $right);

		array_pop($left); // bestand weghalen

		while(current($right) == '..') {
			array_pop($left);
			array_shift($right);
		}

		$combined = array_merge($left, $right);

		$check_empty = create_function('$string', 'return $string != ".";');

		$combined = array_filter($combined, $check_empty);

		return implode('/', $combined);
	}
	
	static public function isValidScheme($scheme)
	{
		return preg_match('/^[a-zA-Z]+[a-zA-Z0-9]*$/', $scheme);
	}
	
	static public function isValidHost($host)
	{
		return preg_match('/^([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+$/', $host);
	}
	
	static public function isValidPort($port)
	{
		return ctype_digit((string) $port);
	}
}

class URL_Query extends ArrayObject
{
	public function __construct($query = null)
	{
		if($query)
			parse_str($query, $data);
		else
			$data = array();
		
		if(!is_array($data))
			throw new InvalidArgumentException('Query cannot be parsed with parse_str');
		
		parent::__construct($data);
	}
	
	public function __toString()
	{
		return $this->generate();
	}
	
	public function merge(URL_Query $other)
	{
		$merged = new self;
		
		$merged->exchangeArray(array_merge($this->getArrayCopy(), $other->getArrayCopy()));
		
		return $merged;
	}
	
	public function generate()
	{
		return http_build_query($this);
	}
}
testcase.php
<?php

include 'URL.php';

class URL_Testcase
{
	static public function run()
	{
		$with = create_function('$x', 'return $x;');
		
		$a = new URL('http://www.ikhoefgeen.nl');
		
		assert('!isset($a->path)');

		$b = new URL('/test/');
		
		assert('!isset($b->host)');
		
		assert('isset($b->path) && $b->path == "/test/"');

		$c = new URL('alfa-beta');

		$d = new URL('pagina.html#test');
		
		assert('$d->fragment == "test"');

		$e = URL::format('https://ikhoefgeen.nl/index/taart.php?a=%s&c=%s#linkList', 'b', 'd');
		
		assert('$a->merge($b) == "http://www.ikhoefgeen.nl/test/"');
		
		assert('$a->merge($c) == "http://www.ikhoefgeen.nl/alfa-beta"');
		
		assert('$a->merge($d) == "http://www.ikhoefgeen.nl/pagina.html#test"');
		
		assert('$a->merge($e) == "https://ikhoefgeen.nl/index/taart.php?a=b&c=d#linkList"');
		
		$f = new URL('/alfa/beta/gamma/delta');
		
		$g = new URL('../../epsilon/');
		
		// bovenliggende urls lossen /../-stappen op.
		assert('$a->merge($f)->merge($g) == "http://www.ikhoefgeen.nl/alfa/epsilon/"');
		
		// losse onderdelen zijn apart in te stellen
		$h = clone $g;
		$h->scheme = 'http';
		$h->host = 'www.ikhoef-geen.nl';
		$h->port = 8084;
		$h->query = 'alfa=beta&gamma=delta';
		assert('$h == "http://www.ikhoef-geen.nl:8084/../../epsilon/?alfa=beta&gamma=delta"');
		
		// query-onderdelen zijn toe te voegen
		$h->query['taart'] = 'appel';
		assert('$h == "http://www.ikhoef-geen.nl:8084/../../epsilon/?alfa=beta&gamma=delta&taart=appel"');
		
		// volgorde query verandert niet zomaar wanneer je een query-onderdeel verandert
		$h->query['alfa'] = 'grieks';
		assert('$h == "http://www.ikhoef-geen.nl:8084/../../epsilon/?alfa=grieks&gamma=delta&taart=appel"');
		
		$j = new URL('?a=b&e=fout');
		$k = new URL('?c=d&e=goed');
		
		// queries worden ook gemerged: overschrijft wanneer naam al bestaat
		assert('$j->merge($k) == "?a=b&e=goed&c=d"');
		
		// query kan ook een array zijn, gaat goed.
		$l = new URL('?a[]=a&a[]=b&a[]=c');
		assert('is_array($l->query["a"])');
		
		echo "All OK";
	}
}

URL_Testcase::run();

Reacties

0
Nog geen reacties.