Refactor URL class and test

The constructor of the class is now private, instantiation happens
with Url::fromRequest and Url::fromPath. Also updated all occurences
of Url and fixed the Qlink helper to not overwrite the baseUrl with null
values

refs #4381
This commit is contained in:
Jannis Moßhammer 2013-07-31 10:59:34 +02:00 committed by Eric Lippmann
parent e24d3ca031
commit 50ca1aec1a
10 changed files with 617 additions and 100 deletions

View File

@ -38,7 +38,7 @@ class Zend_View_Helper_QUrl extends Zend_View_Helper_Abstract
} else {
$params = array();
}
return Url::create($url, $params);
return Url::fromPath($url, $params);
$params = array_map('rawurlencode', $params);
return $this->view->baseUrl(vsprintf($url, $params));
}

View File

@ -79,9 +79,12 @@ class Zend_View_Helper_Qlink extends Zend_View_Helper_Abstract
$url = $urlFormat;
$uriParams = $url->getParams() + $uriParams;
} else {
$url = Url::create($urlFormat);
$url = Url::fromPath($urlFormat);
}
$url->setParams($uriParams);
if ($baseUrl) {
$url->setBaseUrl($baseUrl);
}
$url->setParams($uriParams)->setBaseUrl($baseUrl);
return sprintf(
'<a href="%s"%s>%s</a>',
// $this->getFormattedUrl($urlFormat, $uriParams, $baseUrl),

View File

@ -12,7 +12,7 @@ define('K_TCPDF_EXTERNAL_CONFIG', true);
//define('K_PATH_URL', 'http://net-test-icinga-vm1.adm.netways.de/develop/'); // ???
// define('K_PATH_URL', '/var/www/net-test-icinga-vm1.adm.netways.de/develop/public'); // ???
define('K_PATH_URL', (string) Url::create('/') === '/' ? '' : (string) Url::create('/')); // ???'/'));
define('K_PATH_URL', (string) Url::fromPath('/') === '/' ? '' : (string) Url::fromPath('/')); // ???'/'));
define('K_PATH_MAIN', dirname(ICINGA_LIBDIR) . '/public');
define('K_PATH_FONTS', ICINGA_LIBDIR . '/vendor/tcpdf/fonts/');
define('K_PATH_CACHE', ICINGA_LIBDIR . '/vendor/tcpdf/cache/');

View File

@ -3,108 +3,221 @@
namespace Icinga\Web;
use Icinga\Application\Icinga;
use Icinga\Exception\ProgrammingError;
/**
* Url class that provides convenient access to parameters, allows to modify query parameters and
* returns Urls reflecting all changes made to the url and to the parameters.
*
* Direct instantiation is prohibited and should be done either with @see Url::fromRequest() or
* @see Url::fromUrlString()
*
* Currently, protocol, host and port are ignored and will be implemented when required
*
*/
class Url
{
protected $params = array();
protected $path;
protected $baseUrl;
protected $request;
/**
* An array of all parameters stored in this Url
*
* @var array
*/
private $params = array();
public function __construct($url, $params = null, $request = null)
/**
* The relative path of this Url, without query parameters
*
* @var string
*/
private $path = '';
/**
* The baseUrl that will be appended to @see Url::$path in order to
* create an absolute Url
*
* @var string
*/
private $baseUrl = '/';
private function __construct()
{
}
/**
* Create a new Url class representing the current request
*
* If $params are given, those will be added to the request's parameters
* and overwrite any existing parameters
*
* @param string $url The string representation of the Url to parse
* @param array $params Parameters that should additionally be considered for the Url
* @param Zend_Request $request A request to use instead of the default one
*
* @return Url
*/
public static function fromRequest(array $params = array(), $request = null)
{
if ($request === null) {
$this->request = Icinga::app()->getFrontController()->getRequest();
} else {
// Tests only
$this->request = $request;
}
if ($url === null) {
$this->path = $this->request->getPathInfo();
$this->params = $this->request->getQuery();
} else {
if (($split = strpos($url, '?')) === false) {
$this->path = $url;
} else {
$this->path = substr($url, 0, $split);
// TODO: Use something better than parse_str
parse_str(substr($url, $split + 1), $urlParams);
$this->params = $urlParams;
}
}
if (! empty($params)) {
$this->setParams($params);
$request = Icinga::app()->getFrontController()->getRequest();
}
$urlObject = new Url();
$urlObject->setPath($request->getPathInfo());
$urlObject->setParams(array_merge($request->getQuery(), $params));
$urlObject->setBaseUrl($request->getBaseUrl());
return $urlObject;
}
public static function create($url, $params = null, $request = null)
/**
* Create a new Url class representing the given url
*
* If $params are given, those will be added to the urls parameters
* and overwrite any existing parameters
*
* @param string $url The string representation of the Url to parse
* @param array $params An array of parameters that should additionally be considered for the Url
* @param Zend_Request $request A request to use instead of the default one
*
* @return Url
*/
public static function fromPath($url, array $params = array(), $request = null)
{
$u = new Url($url, $params, $request);
return $u;
$urlObject = new Url();
if ($request === null) {
$request = Icinga::app()->getFrontController()->getRequest();
}
$urlObject->setBaseUrl($request->getBaseUrl());
$urlParts = parse_url($url);
if (isset($urlParts["path"])) {
$urlObject->setPath($urlParts["path"]);
}
if (isset($urlParts["query"])) {
parse_str($urlParts["query"], $urlParams);
$params = array_merge($urlParams, $params);
}
$urlObject->setParams($params);
return $urlObject;
}
// For tests
/**
* Overwrite the baseUrl.
*
* If an empty Url is given '/' is used as the base
*
* @param string $baseUrl The url path to use as the Url Base
* @return $this
*/
public function setBaseUrl($baseUrl)
{
if (trim($baseUrl) == '') {
$baseUrl = '/';
}
$this->baseUrl = $baseUrl;
return $this;
}
public static function current($request = null)
/**
* Set the relative path of this url, without query parameters
*
* @param string $path The path to set
*/
public function setPath($path)
{
$url = new Url(null, null, $request);
return $url;
$this->path = $path;
}
/**
* Return the relative path of this Url, without query parameters
*
* If you want the relative path with query parameters use getRelativeUrl
*
* @return string
*/
public function getPath()
{
return $this->path;
}
public function getRelative()
/**
* Return the relative url with query parameters as a string
*
* @return string
*/
public function getRelativeUrl()
{
$params = $args = array();
foreach ($this->params as $name => $value) {
if (is_int($name)) {
$params[] = rawurlencode($value);
} else {
$args[] = rawurlencode($name) . '=' . rawurlencode($value);
}
}
$url = vsprintf($this->path, $params);
if (! empty($args)) {
$url .= '?' . implode('&amp;', $args);
}
return $url;
if (empty($this->params))
return ltrim($this->path,'/');
return ltrim($this->path,'/').'?'.http_build_query($this->params);
}
public function addParams($params)
/**
* Return the absolute url with query parameters as a string
*
* @return string
*/
public function getAbsoluteUrl()
{
$url = $this->getRelativeUrl();
$baseUrl = '/'.ltrim($this->baseUrl, '/');
return $baseUrl.'/'.$url;
}
/**
* Add a set of parameters to the query part
*
* @param array $params The parameters to add
* @return $this
*/
public function addParams(array $params)
{
$this->params += $params;
return $this;
}
public function setParams($params)
/**
* Overwrite the parameters used in the query part
*
* @param array $params The new parameters to use for the query part
* @return $this
*/
public function setParams(array $params)
{
if ($params === null) {
$this->params = array();
} else {
$this->params = $params;
}
$this->params = $params;
return $this;
}
/**
* Return all parameters that will be used in the query part
*
* @return array An associative key => value array containing all parameters
*/
public function getParams()
{
return $this->params;
}
/**
* Return true if the Urls' query parameter with $key exists, otherwise false
*
* @param $key A key to check for existing
* @return bool
*/
public function hasParam($key)
{
return array_key_exists($key, $this->params);
}
/**
* Return the Url's query parameter with the name $key if exists, otherwise $default
*
* @param $key A query parameter name to return if existing
* @param mixed $default A value to return when the parameter doesn't exist
* @return mixed
*/
public function getParam($key, $default = null)
{
if ($this->hasParam($key)) {
@ -113,48 +226,85 @@ class Url
return $default;
}
/**
* Set a single parameter $key, overwriting existing ones with the same key
*
* @param string $key A string representing the key of the parameter
* @param array|string $value An array or string to set as the parameter value
* @return $this
*/
public function setParam($key, $value)
{
$this->params[$key] = $value;
return $this;
}
public function remove()
/**
* Remove provided key (if string) or keys (if array of string) from the query parameter array
*
* @param string|array $keyOrArrayOfKeys An array of strings or a string representing the key(s)
* of the parameters to be removed
* @return $this
*/
public function remove($keyOrArrayOfKeys)
{
$args = func_get_args();
foreach ($args as $keys) {
if (! is_array($keys)) {
$keys = array($keys);
}
foreach ($keys as $key) {
if (array_key_exists($key, $this->params)) {
unset($this->params[$key]);
}
}
if (is_array($keyOrArrayOfKeys)) {
$this->removeKeys($keyOrArrayOfKeys);
} else {
$this->removeKey($keyOrArrayOfKeys);
}
return $this;
}
public function without()
/**
* Remove all parameters with the parameter names in the $keys array
*
* @param array $keys An array of strings containing parameter names to remove
* @return $this
*/
public function removeKeys(array $keys)
{
$url = clone($this);
$args = func_get_args();
return call_user_func_array(array($url, 'remove'), $args);
foreach ($keys as $key) {
$this->removeKey($key);
}
return $this;
}
public function __toString()
/**
* Remove a single parameter with the provided parameter name $key
*
* @param string $key The key to remove from the Url
* @return $this
*/
public function removeKey($key)
{
$url = $this->getRelative();
$base = null === $this->baseUrl
? $this->request->getBaseUrl()
: $this->baseUrl;
if ($base === '' && $url[0]!== '/') {
// Otherwise all URLs would be relative to wherever you are
$base = '/';
if (isset($this->params[$key])) {
unset($this->params[$key]);
}
if (strlen($base) > 0 && strlen($url) > 0 && $url[0] !== '?') {
$base = rtrim($base, '/') . '/';
}
return $base . $url;
return $this;
}
/**
* Return a copy of this url without the parameter given
*
* The argument can either a single query parameter name or an array of parameter names to
* remove from the query list
*
* @param string|array $keyOrArrayOfKeys A single string or an array containing parameter names
* @return Url
*/
public function getUrlWithout($keyOrArrayOfKeys)
{
$url = clone($this);
$url->remove($keyOrArrayOfKeys);
return $url;
}
/**
* Alias for @see Url::getAbsoluteUrl()
* @return mixed
*/
public function __toString() {
return $this->getAbsoluteUrl();
}
}

View File

@ -7,12 +7,12 @@ use Icinga\Web\Url;
function url($path = null, $params = null)
{
if ($path === null) {
$url = Url::current();
$url = Url::fromRequest();
if ($params !== null) {
$url->setParams($params);
}
} else {
$url = Url::create($path, $params);
$url = Url::fromPath($path, $params);
}
return $url;
}

View File

@ -24,7 +24,7 @@ class Dashboard extends AbstractWidget
protected function init()
{
if ($this->url === null) {
$this->url = Url::current()->without($this->tabParam);
$this->url = Url::fromRequest()->without($this->tabParam);
}
}
@ -141,7 +141,7 @@ class Dashboard extends AbstractWidget
{
$active = $this->tabs()->getActiveName();
if (! $active) {
if ($active = Url::current()->getParam($this->tabParam)) {
if ($active = Url::fromRequest()->getParam($this->tabParam)) {
$this->activate($active);
} else {
reset($this->panes);
@ -186,7 +186,7 @@ class Dashboard extends AbstractWidget
unset($item['base_url']);
$this->getPane($dashboard)->addComponent(
$title,
Url::create($base_url, $item)
Url::fromPath($base_url, $item)
);
}
}

View File

@ -22,7 +22,7 @@ class Component
if ($url instanceof Url) {
$this->url = $url;
} else {
$this->url = Url::create($url);
$this->url = Url::fromPath($url);
}
}
@ -57,7 +57,7 @@ class Component
if ($url instanceof Url) {
$this->url = $url;
} else {
$this->url = Url::create($url);
$this->url = Url::fromPath($url);
}
return $this;
}

View File

@ -204,34 +204,34 @@ class Tabs extends AbstractWidget implements Countable
$special = array();
$special[] = $this->view()->qlink(
$this->view()->img('img/classic/application-pdf.png') . ' PDF',
Url::current(),
Url::fromRequest(),
array('filetype' => 'pdf'),
array('target' => '_blank', 'quote' => false)
);
$special[] = $this->view()->qlink(
$this->view()->img('img/classic/application-csv.png') . ' CSV',
Url::current(),
Url::fromRequest(),
array('format' => 'csv'),
array('target' => '_blank', 'quote' => false)
);
$special[] = $this->view()->qlink(
$this->view()->img('img/classic/application-json.png') . ' JSON',
Url::current(),
Url::fromRequest(),
array('format' => 'json', 'quote' => false),
array('target' => '_blank', 'quote' => false)
);
$special[] = $this->view()->qlink(
$this->view()->img('img/classic/basket.png') . ' URL Basket',
Url::create('basket/add'),
array('url' => Url::current()->getRelative()),
Url::fromPath('basket/add'),
array('url' => Url::fromRequest()->getRelativeUrl()),
array('quote' => false)
);
$special[] = $this->view()->qlink(
$this->view()->img('img/classic/dashboard.png') . ' Dashboard',
Url::create('dashboard/addurl'),
array('url' => Url::current()->getRelative()),
Url::fromPath('dashboard/addurl'),
array('url' => Url::fromRequest()->getRelativeUrl()),
array('quote' => false)
);
// $auth = Auth::getInstance();

View File

@ -39,7 +39,7 @@ class Monitoring_SoapController extends ModuleActionController
$wsdl->handle();
} else {
$wsdl->dump('/tmp/test.wsdl');
$uri = 'http://itenos-devel.tom.local/' . Url::create('monitoring/soap');
$uri = 'http://itenos-devel.tom.local/' . Url::fromPath('monitoring/soap');
$server = new Zend_Soap_Server('/tmp/test.wsdl');
$server->setClass('Api');
$server->handle();

View File

@ -0,0 +1,364 @@
<?php
namespace Tests\Icinga\Web;
require_once('../../library/Icinga/Web/Url.php');
use Icinga\Web\Url;
/**
* Request mock that implements all methods required by the
* Url class
*
*/
class RequestMock
{
/**
* The path of the request
*
* @var string
*/
public $path = "";
/**
* The baseUrl of the request
*
* @var string
*/
public $baseUrl = "";
/**
* An array of query parameters that the request should resemble
*
* @var array
*/
public $query = array();
/**
* Returns the path set for the request
*
* @return string
*/
public function getPathInfo()
{
return $this->path;
}
/**
* Returns the baseUrl set for the request
*
* @return string
*/
public function getBaseUrl()
{
return $this->baseUrl;
}
/**
* Returns the query set for the request
*
* @return array
*/
public function getQuery()
{
return $this->query;
}
}
/**
* Tests for the Icinga\Web\Url class that provides convenient access to Url manipulation method
*
*
*/
class UrlTest extends \PHPUnit_Framework_TestCase
{
/**
* Tests whether a simple Url without query parameters and baseUrl is correctly parsed and returns correct Urls
*
*/
function testFromStringWithoutQuery()
{
$url = Url::fromPath('http://myHost/my/test/url.html', array(), new RequestMock());
$this->assertEquals('/my/test/url.html', $url->getPath(), 'Assert the parsed url path to be equal to the input path');
$this->assertEquals($url->getPath(), '/'.$url->getRelativeUrl(), 'Assert the path of an url without query to be equal the relative path');
}
/**
* Tests whether a simple Url without query parameters and with baseUrl is correctly parsed and returns correct Urls
*
*/
function testFromUrlWithBasePath()
{
$url = Url::fromPath('my/test/url.html', array(), new RequestMock());
$url->setBaseUrl('the/path/to');
$this->assertEquals('/the/path/to/my/test/url.html', $url->getAbsoluteUrl(), 'Assert the url path to be the base path with the relative path');
}
/**
* Tests whether query parameters in Urls are correctly recognized and decoded
*
*/
function testFromUrlWithKeyValueQuery()
{
$url = Url::fromPath('/my/test/url.html?param1=%25arg1&param2=arg2', array(), new RequestMock());
$this->assertEquals('/my/test/url.html', $url->getPath(), 'Assert the parsed url path to be equal to the input path');
$this->assertEquals(
array(
'param1' => '%arg1',
'param2' => 'arg2'
),
$url->getParams(),
'Assert single key=value Url parameters to be correctly parsed and recognized'
);
}
/**
* Tests whether unnamed query parameters in Urls are correctly recognized and decoded
*
*/
function testFromUrlWithArrayInQuery()
{
$url = Url::fromPath('/my/test/url.html?param[]=%25val1&param[]=%40val2', array(), new RequestMock());
$this->assertEquals(
array(
'param' => array('%val1', '@val2')
),
$url->getParams(),
'Assert arrays in param[] = value syntax to be correctly recognized and parsed as arrays'
);
}
/**
* Tests whether named query parameters in Urls are correctly recognized and decoded
*
*/
function testFromUrlWithAssociativeArrayInQuery()
{
$url = Url::fromPath('/my/test/url.html?param[value]=%25val1&param[value2]=%40val2', array(), new RequestMock());
$this->assertEquals(
array(
'param' => array(
'value' => '%val1',
'value2' => '@val2'
)
),
$url->getParams(),
'Assert arrays in param[] = value syntax to be correctly recognized and parsed as arrays'
);
}
/**
* Tests whether simple query parameters can be correctly added on an existing query and ends up in correct Urls
*
*/
function testAddQueryParameterToUrlWithoutQuery()
{
$url = Url::fromPath(
'/my/test/url.html',
array(
'param1' => 'val1',
'param2' => 'val2'
),
new RequestMock()
);
$url->setBaseUrl('path/to');
$this->assertEquals(
'/path/to/my/test/url.html?param1=val1&param2=val2',
$url->getAbsoluteUrl(),
'Assert additional parameters to be correctly added to the Url'
);
}
/**
* Test whether parameters are correctly added to existing query parameters and existing ones are correctly overwritten
* if they have the same key
*
*/
function testOverwritePartialQuery()
{
$url = Url::fromPath(
'/my/test/url.html?param1=oldval1',
array(
'param1' => 'val1',
'param2' => 'val2'
),
new RequestMock()
);
$url->setBaseUrl('path/to');
$this->assertEquals(
'/path/to/my/test/url.html?param1=val1&param2=val2',
$url->getAbsoluteUrl(),
'Assert additional parameters to be correctly added to the Url and overwriting existing parameters'
);
}
/**
* Test whether array parameters are correctly added to an existing Url and end up in correct Urls
*
*/
function testSetQueryWithArrayParameter()
{
$url = Url::fromPath(
'/my/test/url.html',
array(
'flatarray' => array('val1', 'val2'),
'param' => array('value1'=>'val1', 'value2' => 'val2')
),
new RequestMock()
);
$url->setBaseUrl('path/to');
$this->assertEquals(
'/path/to/my/test/url.html?flatarray'.urlencode('[0]').'=val1&'.
'flatarray'.urlencode('[1]').'=val2&'.
'param'.urlencode('[value1]').'=val1&'.
'param'.urlencode('[value2]').'=val2',
$url->getAbsoluteUrl(),
'Assert array parameters to be correctly encoded and added to the Url'
);
}
/**
* Test whether Urls from the request are correctly parsed when no query is given
*
*/
function testUrlFromRequestWithoutQuery()
{
$request = new RequestMock();
$request->baseUrl = 'path/to';
$request->path = 'my/test/url.html';
$request->query = array();
$url = Url::fromRequest(array(), $request);
$this->assertEquals(
'/path/to/my/test/url.html',
$url->getAbsoluteUrl(),
'Asserting absolute path resembling the requests path appended by the baseUrl'
);
}
/**
* Test whether Urls from the request are correctly parsed when a query is given
*
*/
function testUrlFromRequestWithQuery()
{
$request = new RequestMock();
$request->baseUrl = 'path/to';
$request->path = 'my/test/url.html';
$request->query = array(
'param1' => 'value1',
'param2' => array('key1' => 'value1', 'key2' => 'value2')
);
$url = Url::fromRequest(array(), $request);
$this->assertEquals(
'/path/to/my/test/url.html?param1=value1&'.
'param2'.urlencode('[key1]').'=value1&'.
'param2'.urlencode('[key2]').'=value2',
$url->getAbsoluteUrl(),
'Asserting absolute path resembling the requests path appended by the baseUrl'
);
}
/**
* Test the @see Url::getParam($name, $default) function
*
*/
function testGetParameterByName()
{
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2', array(), new RequestMock());
$this->assertEquals(
"val",
$url->getParam("param", "wrongval"),
"Asserting a parameter can be fetched via getParam()"
);
$this->assertEquals(
"val2",
$url->getParam("param2", "wrongval2"),
"Asserting a parameter can be fetched via getParam()"
);
$this->assertEquals(
"nonexisting",
$url->getParam("param3", "nonexisting"),
"Asserting a non existing parameter returning the default value when fetched via getParam()"
);
}
/**
* Test the Url::remove function with a single key passed
*
*/
function testRemoveSingleParameter()
{
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2', array(), new RequestMock());
$url->remove("param");
$this->assertEquals(
"val2",
$url->getParam("param2", "wrongval2"),
"Asserting other parameters (param2) not being affected by remove"
);
$this->assertEquals(
"rightval",
$url->getParam("param", "rightval"),
"Asserting a parameter (param) can be removed via remove"
);
}
/**
* Test the Url::remove function with an array of keys passed
*
*/
function testRemoveMultipleParameters()
{
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2&param3=val3', array(), new RequestMock());
$url->remove(array("param", "param2"));
$this->assertEquals(
"val3",
$url->getParam("param3", "wrongval"),
"Asserting other parameters (param3) not being affected by remove"
);
$this->assertEquals(
"rightval",
$url->getParam("param", "rightval"),
"Asserting a parameter (param) can be removed via remove in a batch"
);
$this->assertEquals(
"rightval",
$url->getParam("param2", "rightval"),
"Asserting a parameter (param2) can be removed via remove in a batch"
);
}
/**
* Test the Url::without call and whether it returns a copy instead of working on the called object
*
*/
function testWithoutCall()
{
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2&param3=val3', array(), new RequestMock());
$url2 = $url->getUrlWithout(array("param"));
$this->assertNotEquals(
$url,
$url2,
"Asserting without creating a copy of the url"
);
$this->assertTrue(
$url->hasParam("param"),
"Asserting the original Url not being affected when calling 'without'"
);
$this->assertFalse(
$url2->hasParam("param"),
"Asserting the returned Url being without the passed parameter when calling 'without'"
);
}
/**
* Test whether toString is the same as getAbsoluteUrl
*
*/
function testToString()
{
$url = Url::fromPath('/my/test/url.html?param=val&param2=val2&param3=val3', array(), new RequestMock());
$this->assertEquals($url->getAbsoluteUrl(), (string) $url, "Asserting whether toString returns the absolute Url");
}
}