diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c47d008c..568dfeed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All Notable changes to `League\Uri\Components` will be documented in this file +## 1.0.0 - 2017-01-17 + +### Added + +- None + +### Fixed + +- Improve validation check for `Query::build` +- Remove `func_* function usage +- Improve `HierarchicalPath::createFromSegments` +- Internal code simplification + +### Deprecated + +- None + +### Remove + +- None + ## 1.0.0-RC1 - 2017-01-09 ### Added diff --git a/src/HierarchicalPath.php b/src/HierarchicalPath.php index e9f4db167..d90226bea 100644 --- a/src/HierarchicalPath.php +++ b/src/HierarchicalPath.php @@ -68,10 +68,14 @@ public static function createFromSegments($data, int $type = self::IS_RELATIVE): $path = implode(static::$separator, static::filterIterable($data)); if (static::IS_ABSOLUTE === $type) { - $path = static::$separator.$path; + if (static::$separator !== substr($path, 0, 1)) { + return new static(static::$separator.$path); + } + + return new static($path); } - return new static($path); + return new static(ltrim($path, '/')); } /** @@ -212,19 +216,17 @@ public function getSegment(int $offset, $default = null) * If a value is specified only the keys associated with * the given value will be returned * + * @param mixed ...$args the total number of argument given to the method + * * @return array */ - public function keys(): array + public function keys(...$args): array { - if (0 === func_num_args()) { + if (empty($args)) { return array_keys($this->data); } - return array_keys( - $this->data, - $this->decodeComponent($this->validateString(func_get_arg(0))), - true - ); + return array_keys($this->data, $this->decodeComponent($this->validateString($args[0])), true); } /** diff --git a/src/Host.php b/src/Host.php index df41d7467..d7cd6ff5d 100644 --- a/src/Host.php +++ b/src/Host.php @@ -225,7 +225,7 @@ protected function validate(string $host = null): array return [$host]; } - if ($this->isValidHostnameIpv6($host)) { + if ($this->isValidIpv6Hostname($host)) { $this->host_as_ipv6 = true; $this->has_zone_identifier = false !== strpos($host, '%'); @@ -331,15 +331,17 @@ public function getLabel(int $offset, $default = null) * If a value is specified only the keys associated with * the given value will be returned * + * @param mixed ...$args the total number of argument given to the method + * * @return array */ - public function keys(): array + public function keys(...$args): array { - if (0 === func_num_args()) { + if (empty($args)) { return array_keys($this->data); } - return array_keys($this->data, idn_to_utf8($this->validateString(func_get_arg(0))), true); + return array_keys($this->data, idn_to_utf8($this->validateString($args[0])), true); } /** diff --git a/src/HostInfoTrait.php b/src/HostInfoTrait.php index f7e39fee9..4d9500de9 100644 --- a/src/HostInfoTrait.php +++ b/src/HostInfoTrait.php @@ -74,6 +74,59 @@ trait HostInfoTrait */ protected static $invalid_uri_chars = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F"; + /** + * Load the hostname info + * + * @param string $key hostname info key + * + * @return mixed + */ + protected function getHostnameInfo(string $key) + { + $this->loadHostnameInfo(); + return $this->hostnameInfo[$key]; + } + + /** + * parse and save the Hostname information from the Parser + */ + protected function loadHostnameInfo() + { + if ($this->isIp() || $this->hostnameInfoLoaded) { + return; + } + + $host = $this->__toString(); + if ($this->isAbsolute()) { + $host = substr($host, 0, -1); + } + + $this->hostnameInfo = array_map( + 'sprintf', + $this->getPdpParser()->parseHost($host)->toArray() + ) + $this->hostnameInfo; + + if ('' !== $this->hostnameInfo['publicSuffix']) { + $this->hostnameInfo['isPublicSuffixValid'] = $this->getPdpParser()->isSuffixValid($host); + } + + $this->hostnameInfoLoaded = true; + } + + /** + * Initialize and access the Parser object + * + * @return Parser + */ + protected function getPdpParser(): Parser + { + if (!static::$pdp_parser instanceof Parser) { + static::$pdp_parser = new Parser((new PublicSuffixListManager())->getList()); + } + + return static::$pdp_parser; + } + /** * Return the host public suffix * @@ -114,45 +167,6 @@ public function isPublicSuffixValid(): bool return $this->getHostnameInfo('isPublicSuffixValid'); } - /** - * Load the hostname info - * - * @param string $key hostname info key - * - * @return mixed - */ - protected function getHostnameInfo(string $key) - { - $this->loadHostnameInfo(); - return $this->hostnameInfo[$key]; - } - - /** - * parse and save the Hostname information from the Parser - */ - protected function loadHostnameInfo() - { - if ($this->isIp() || $this->hostnameInfoLoaded) { - return; - } - - $host = $this->__toString(); - if ($this->isAbsolute()) { - $host = mb_substr($host, 0, -1, 'UTF-8'); - } - - $this->hostnameInfo = array_merge( - $this->hostnameInfo, - array_map('sprintf', $this->getPdpParser()->parseHost($host)->toArray()) - ); - - if ('' !== $this->hostnameInfo['publicSuffix']) { - $this->hostnameInfo['isPublicSuffixValid'] = $this->getPdpParser()->isSuffixValid($host); - } - - $this->hostnameInfoLoaded = true; - } - /** * validate an Ipv6 Hostname * @@ -163,7 +177,7 @@ protected function loadHostnameInfo() * * @return bool */ - protected function isValidHostnameIpv6(string $ipv6): bool + protected function isValidIpv6Hostname(string $ipv6): bool { if (false === strpos($ipv6, '[') || false === strpos($ipv6, ']')) { return false; @@ -209,7 +223,7 @@ protected function isValidHostname(string $host): bool { $labels = array_map('idn_to_ascii', explode('.', $host)); - return 127 > count($labels) && $labels === array_filter($labels, [$this, 'isValidHostLabel']); + return 127 > count($labels) && $labels === array_filter($labels, [$this, 'isValidLabel']); } /** @@ -219,7 +233,7 @@ protected function isValidHostname(string $host): bool * * @return bool */ - protected function isValidHostLabel(string $label): bool + protected function isValidLabel(string $label): bool { if ('' == $label) { return false; @@ -253,18 +267,4 @@ abstract public function isIp(): bool; * @return bool */ abstract public function isAbsolute(): bool; - - /** - * Initialize and access the Parser object - * - * @return Parser - */ - protected function getPdpParser(): Parser - { - if (!static::$pdp_parser instanceof Parser) { - static::$pdp_parser = new Parser((new PublicSuffixListManager())->getList()); - } - - return static::$pdp_parser; - } } diff --git a/src/Query.php b/src/Query.php index 67ca7de9f..02e0383e6 100644 --- a/src/Query.php +++ b/src/Query.php @@ -82,41 +82,13 @@ class Query implements ComponentInterface, Countable, IteratorAggregate public static function createFromPairs($pairs): self { $pairs = static::filterIterable($pairs); - static:: validatePairs($pairs); - if (empty($pairs)) { return new static(); } - return new static(static::build($pairs, static::$separator)); } - /** - * Filter the submitted pair array. - * - * @param array $pairs - * - * @throws Exception If the array contains non valid data - */ - protected static function validatePairs(array $pairs) - { - foreach ($pairs as $value) { - if (!is_array($value)) { - $value = [$value]; - } - - foreach ($value as $val) { - if (null !== $val && !is_scalar($val)) { - throw new Exception(sprintf( - 'Expected data to be a scalar or null; received "%s"', - (is_object($val) ? get_class($val) : gettype($val)) - )); - } - } - } - } - /** * This static method is called for classes exported by var_export() * @@ -196,12 +168,13 @@ public function __debugInfo(): array public function getContent(int $enc_type = ComponentInterface::RFC3986_ENCODING) { $this->assertValidEncoding($enc_type); - if (!$this->preserve_delimiter) { return null; } - return static::build($this->pairs, static::$separator, $enc_type); + $encoder = self::getEncoder(static::$separator, $enc_type); + + return static::getQueryString($this->pairs, static::$separator, $encoder); } /** @@ -324,15 +297,17 @@ public function hasPair(string $offset): bool * the given value will be returned. The specified value * must be decoded * + * @param mixed ...$args the total number of argument given to the method + * * @return array */ - public function keys(): array + public function keys(...$args): array { - if (0 === func_num_args()) { + if (empty($args)) { return array_keys($this->pairs); } - return array_keys($this->pairs, func_get_arg(0), true); + return array_keys($this->pairs, $args[0], true); } /** diff --git a/src/QueryParserTrait.php b/src/QueryParserTrait.php index 96992b4a2..0da5801d9 100644 --- a/src/QueryParserTrait.php +++ b/src/QueryParserTrait.php @@ -127,8 +127,55 @@ public static function build( string $separator = '&', int $enc_type = Query::RFC3986_ENCODING ): string { + self::assertValidPairs($pairs); self::assertValidEncoding($enc_type); $encoder = self::getEncoder($separator, $enc_type); + + return self::getQueryString($pairs, $separator, $encoder); + } + + /** + * Filter the submitted pair array. + * + * @param array $pairs + * + * @throws Exception If the array contains non valid data + */ + protected static function assertValidPairs(array $pairs) + { + $invalid = array_filter($pairs, function ($value) { + if (!is_array($value)) { + $value = [$value]; + } + + return array_filter($value, function ($val) { + return $val !== null && !is_scalar($val); + }); + }); + + if (empty($invalid)) { + return; + } + + throw new Exception('Invalid value contained in the submitted pairs'); + } + + /** + * Build a query string from an associative array + * + * The method expects the return value from Query::parse to build + * a valid query string. This method differs from PHP http_build_query as: + * + * - it does not modify parameters keys + * + * @param array $pairs Query pairs + * @param string $separator Query string separator + * @param callable $encoder Query encoder + * + * @return string + */ + protected static function getQueryString(array $pairs, string $separator, callable $encoder): string + { $normalized_pairs = array_map(function ($value) { return !is_array($value) ? [$value] : $value; }, $pairs); diff --git a/tests/HierarchicalPathTest.php b/tests/HierarchicalPathTest.php index dc0268b6d..5c227a50a 100644 --- a/tests/HierarchicalPathTest.php +++ b/tests/HierarchicalPathTest.php @@ -162,6 +162,10 @@ public function createFromSegmentsValid() 'ending delimiter' => [['foo/bar/baz', ''], Path::IS_RELATIVE, 'foo/bar/baz/'], 'use reserved characters #' => [['all', 'i%23s', 'good'], Path::IS_ABSOLUTE, '/all/i%23s/good'], 'use reserved characters ?' => [['all', 'i%3fs', 'good'], Path::IS_RELATIVE, 'all/i%3Fs/good'], + 'enforce path status (1)' => [['', 'toto', 'yeah', ''], Path::IS_RELATIVE, 'toto/yeah/'], + 'enforce path status (2)' => [['', 'toto', 'yeah', ''], Path::IS_ABSOLUTE, '/toto/yeah/'], + 'enforce path status (3)' => [['', '', 'toto', 'yeah', ''], Path::IS_RELATIVE, 'toto/yeah/'], + 'enforce path status (4)' => [['', '', 'toto', 'yeah', ''], Path::IS_ABSOLUTE, '//toto/yeah/'], ]; }