From ee4547c94266ce0cf1de967a1044beab5cf19d47 Mon Sep 17 00:00:00 2001 From: Jamie Hannaford Date: Tue, 27 May 2014 14:44:56 +0200 Subject: [PATCH] Adding URL class for better encapsulation Because a lot of work is done with URIs, proper encapsulation is necessary. By using an OO approach, we have greater flexibility, ease of use, clarity and power - as apposed to dealing with string manipulation all the time. Change-Id: I3db0712aeadd65f70a599d11b4bc257ad464d0d0 --- src/OpenStack/Common/Transport/Url.php | 321 +++++++++++++++++++++++ tests/Tests/Common/Transport/UrlTest.php | 113 ++++++++ 2 files changed, 434 insertions(+) create mode 100644 src/OpenStack/Common/Transport/Url.php create mode 100644 tests/Tests/Common/Transport/UrlTest.php diff --git a/src/OpenStack/Common/Transport/Url.php b/src/OpenStack/Common/Transport/Url.php new file mode 100644 index 0000000..1f63ce5 --- /dev/null +++ b/src/OpenStack/Common/Transport/Url.php @@ -0,0 +1,321 @@ +populateFromArray($value); + } + + /** + * Internal method that allows for the hydration of this object with an + * array. It iterates through each element and calls the necessary setter + * method if it exists. + * + * @param array $array The input array + */ + private function populateFromArray(array $array) + { + foreach ($array as $key => $val) { + if ($key == 'pass') { + $key = 'password'; + } + $method = 'set' . $key; + if ($val && method_exists($this, $method)) { + $this->$method($val); + } + } + } + + /** + * @param string $scheme + */ + public function setScheme($scheme) + { + $this->scheme = (string)$scheme; + } + + /** + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * @param string $host + */ + public function setHost($host) + { + $this->host = (string)$host; + } + + /** + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * @param int $port + */ + public function setPort($port) + { + $this->port = (int)$port; + } + + /** + * @return int|null + */ + public function getPort() + { + return $this->port; + } + + /** + * @param string $user + */ + public function setUser($user) + { + $this->user = (string)$user; + } + + /** + * @return string + */ + public function getUser() + { + return $this->user; + } + + /** + * @param string $password + */ + public function setPassword($password) + { + $this->password = (string)$password; + } + + /** + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Sets the path to a string value, ensuring that a trailing slash is always + * added. + * + * @param string $path + */ + public function setPath($path) + { + $this->path = rtrim((string)$path, '/'); + } + + /** + * Adds a string path to the existing path value. + * + * @param string $path + */ + public function addPath($path) + { + $path = '/' . ltrim((string)$path, '/'); + $this->setPath($this->path . $path); + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the query value. If a string is provided, it is expanded according + * to conventional key=pair representation, where `&' is a delimeter. An + * array can also be provided. + * + * @param string|array $query + * + * @throws \InvalidArgumentException + */ + public function setQuery($query) + { + if (is_string($query)) { + $query = $this->expandQueryString($query); + } elseif (!is_array($query)) { + throw new \InvalidArgumentException("Query must be an array"); + } + + $this->query = $query; + } + + /** + * Internal method for expanding a string representation of a query into an + * array. The return value should be a simple key/value pair. Query arrays + * are also supported. + * + * @param string $value A string based query representation, in the form of + * ?foo=val&bar=val&baz[]=val_1&baz[]=val_2 + * + * @return array + */ + private function expandQueryString($value) + { + $parts = explode('&', $value); + $array = []; + foreach ($parts as $partArray) { + $inner = explode('=', $partArray); + $key = str_replace('[]', '', $inner[0]); + $val = $inner[1]; + + if (isset($array[$key])) { + $array[$key] = [$array[$key], $val]; + } else { + $array[$key] = $val; + } + } + + return $array; + } + + /** + * @param array $query + */ + public function addQuery(array $query) + { + $this->setQuery((array)$this->query + $query); + } + + /** + * @return array + */ + public function getQuery() + { + return $this->query; + } + + /** + * @param string $fragment + */ + public function setFragment($fragment) + { + $this->fragment = (string)$fragment; + } + + /** + * @return string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Shrinks the query array and returns as a string representation. + * + * @return string + */ + private function shrinkQueryArray() + { + $url = '?'; + foreach ($this->query as $key => $val) { + if (is_array($val)) { + foreach ($val as $subVal) { + $url .= $key . '[]=' . $subVal . '&'; + } + } else { + $url .= $key . '=' . $val . '&'; + } + } + + return rtrim($url, '&'); + } + + /** + * Cast this URL object into a string representation + * + * @return string + */ + public function __toString() + { + $url = ($this->scheme) ? $this->scheme . '://' : '//'; + + if ($this->user && $this->password) { + $url .= sprintf("%s:%s@", $this->user, $this->password); + } + + $url .= $this->host; + + if ($this->port) { + $url .= ':' . (int)$this->port; + } + + $url .= $this->path; + + if (!empty($this->query)) { + $url .= $this->shrinkQueryArray(); + } + + if ($this->fragment) { + $url .= '#' . $this->fragment; + } + + return $url; + } +} \ No newline at end of file diff --git a/tests/Tests/Common/Transport/UrlTest.php b/tests/Tests/Common/Transport/UrlTest.php new file mode 100644 index 0000000..3ee4515 --- /dev/null +++ b/tests/Tests/Common/Transport/UrlTest.php @@ -0,0 +1,113 @@ +url = new Url(self::URL_STRING); + } + + public function testIsConstructedWithProperties() + { + $this->assertEquals('https', $this->url->getScheme()); + $this->assertEquals('openstack.org', $this->url->getHost()); + $this->assertEquals('80', $this->url->getPort()); + $this->assertEquals('/community/members', $this->url->getPath()); + $this->assertEquals('username', $this->url->getUser()); + $this->assertEquals('password', $this->url->getPassword()); + $this->assertEquals('anchor', $this->url->getFragment()); + } + + public function testSettingStringUrlResultsInArrayBasedQuery() + { + $url = new Url('//foo.com?bar=a&baz=b'); + $this->assertEquals(['bar' => 'a', 'baz' => 'b'], $url->getQuery()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testExceptionIsThrownWhenPopulatingWithInvalidDataType() + { + $value = (object) ['path' => 'https', 'host' => 'openstack.org']; + new Url($value); + } + + public function testSettingQueryWithString() + { + $this->url->setQuery('foo=bar&baz=boo'); + $this->assertEquals(['foo' => 'bar', 'baz' => 'boo'], $this->url->getQuery()); + } + + public function testSettingQueryWithStringArray() + { + $this->url->setQuery('foo[]=bar&foo[]=baz'); + $this->assertEquals(['foo' => ['bar', 'baz']], $this->url->getQuery()); + } + + public function testSettingQueryWithArray() + { + $query = ['foo' => 'bar']; + $this->url->setQuery($query); + $this->assertEquals($query, $this->url->getQuery()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testExceptionIsThrownWhenSettingQueryWithInvalidDataType() + { + $this->url->setQuery(false); + } + + public function testAddPath() + { + $this->url->addPath('foo'); + $this->assertEquals('/community/members/foo', $this->url->getPath()); + } + + public function testAddQuery() + { + $this->url->setQuery(['foo' => 'bar']); + $this->url->addQuery(['baz' => 'boo']); + $this->assertEquals(['foo' => 'bar', 'baz' => 'boo'], $this->url->getQuery()); + } + + public function testCastingToString() + { + $this->assertEquals(self::URL_STRING, (string) $this->url); + } + + public function testCastingToStringForQueryArrays() + { + $url = new Url('http://openstack.org'); + $url->setQuery(['foo' => ['val1', 'val2'], 'bar' => 'val3']); + + $this->assertEquals('http://openstack.org?foo[]=val1&foo[]=val2&bar=val3', (string) $url); + } +} \ No newline at end of file