1
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to `uri-template` will be documented in this file.
|
||||
|
||||
Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
|
||||
|
||||
## v1.0.0 - 2021-08-14
|
||||
|
||||
### Changed
|
||||
- Dropped support for PHP 7.1
|
||||
|
||||
## v0.2.0 - 2020-07-21
|
||||
|
||||
### Added
|
||||
- Support PHP 7.1 and 8.0
|
||||
|
||||
### Changed
|
||||
- Renamed `GuzzleHttp\Utility\` to `GuzzleHttp\UriTemplate\`
|
||||
|
||||
### Fixed
|
||||
- Delegate RFC 3986 query string encoding to PHP
|
||||
- Fixed some bugs when parts ofs values are not strings
|
||||
|
||||
## v0.1.1 - 2020-06-30
|
||||
|
||||
### Fixed
|
||||
- Fixed an error due to strict_types [d47d1b0a8e78a3fac1cd0f69d675fc9e06771ac8](https://github.com/guzzle/uri-template/commit/d47d1b0a8e78a3fac1cd0f69d675fc9e06771ac8)
|
||||
|
||||
## v0.1.0 - 2020-06-30
|
||||
|
||||
### Added
|
||||
- Moved the `UriTemplate` class in this package
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 George Mponos <gmponos@gmail.com>
|
||||
|
||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
> of this software and associated documentation files (the "Software"), to deal
|
||||
> in the Software without restriction, including without limitation the rights
|
||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
> copies of the Software, and to permit persons to whom the Software is
|
||||
> furnished to do so, subject to the following conditions:
|
||||
>
|
||||
> The above copyright notice and this permission notice shall be included in
|
||||
> all copies or substantial portions of the Software.
|
||||
>
|
||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
> THE SOFTWARE.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# uri-template
|
||||
|
||||
## Install
|
||||
|
||||
Via Composer
|
||||
|
||||
``` bash
|
||||
$ composer require guzzlehttp/uri-template
|
||||
```
|
||||
|
||||
## Change log
|
||||
|
||||
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
|
||||
|
||||
## Testing
|
||||
|
||||
``` bash
|
||||
$ make test
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
If you discover any security related issues, please email security@guzzlephp.org instead of using the issue tracker.
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "guzzlehttp/uri-template",
|
||||
"type": "library",
|
||||
"description": "A polyfill class for uri_template of PHP",
|
||||
"keywords": [
|
||||
"guzzlehttp",
|
||||
"uri-template"
|
||||
],
|
||||
"homepage": "https://github.com/guzzlehttp/uri-template",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "uri-template/tests",
|
||||
"version": "1.0.0",
|
||||
"dist": {
|
||||
"url": "https://github.com/uri-templates/uritemplate-test/archive/520fdd8b0f78779d12178c357a986e0e727f4bd0.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php" : "^7.2.5 || ^8.0",
|
||||
"symfony/polyfill-php80": "^1.17"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit" : "^8.5.19 || ^9.5.8",
|
||||
"uri-template/tests": "1.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\UriTemplate\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\UriTemplate\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GuzzleHttp\UriTemplate;
|
||||
|
||||
/**
|
||||
* Expands URI templates. Userland implementation of PECL uri_template.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc6570
|
||||
*/
|
||||
final class UriTemplate
|
||||
{
|
||||
/**
|
||||
* @var array<string, array{prefix:string, joiner:string, query:bool}> Hash for quick operator lookups
|
||||
*/
|
||||
private static $operatorHash = [
|
||||
'' => ['prefix' => '', 'joiner' => ',', 'query' => false],
|
||||
'+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
|
||||
'#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
|
||||
'.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
|
||||
'/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
|
||||
';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
|
||||
'?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
|
||||
'&' => ['prefix' => '&', 'joiner' => '&', 'query' => true],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[] Delimiters
|
||||
*/
|
||||
private static $delims = [
|
||||
':',
|
||||
'/',
|
||||
'?',
|
||||
'#',
|
||||
'[',
|
||||
']',
|
||||
'@',
|
||||
'!',
|
||||
'$',
|
||||
'&',
|
||||
'\'',
|
||||
'(',
|
||||
')',
|
||||
'*',
|
||||
'+',
|
||||
',',
|
||||
';',
|
||||
'=',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[] Percent encoded delimiters
|
||||
*/
|
||||
private static $delimsPct = [
|
||||
'%3A',
|
||||
'%2F',
|
||||
'%3F',
|
||||
'%23',
|
||||
'%5B',
|
||||
'%5D',
|
||||
'%40',
|
||||
'%21',
|
||||
'%24',
|
||||
'%26',
|
||||
'%27',
|
||||
'%28',
|
||||
'%29',
|
||||
'%2A',
|
||||
'%2B',
|
||||
'%2C',
|
||||
'%3B',
|
||||
'%3D',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $variables Variables to use in the template expansion
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function expand(string $template, array $variables): string
|
||||
{
|
||||
if (false === \strpos($template, '{')) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
/** @var string|null */
|
||||
$result = \preg_replace_callback(
|
||||
'/\{([^\}]+)\}/',
|
||||
self::expandMatchCallback($variables),
|
||||
$template
|
||||
);
|
||||
|
||||
if (null === $result) {
|
||||
throw new \RuntimeException(\sprintf('Unable to process template: %s', \preg_last_error_msg()));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $variables Variables to use in the template expansion
|
||||
*
|
||||
* @return callable(string[]): string
|
||||
*/
|
||||
private static function expandMatchCallback(array $variables): callable
|
||||
{
|
||||
return static function (array $matches) use ($variables): string {
|
||||
return self::expandMatch($matches, $variables);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an expansion
|
||||
*
|
||||
* @param array<string,mixed> $variables Variables to use in the template expansion
|
||||
* @param string[] $matches Matches met in the preg_replace_callback
|
||||
*
|
||||
* @return string Returns the replacement string
|
||||
*/
|
||||
private static function expandMatch(array $matches, array $variables): string
|
||||
{
|
||||
$replacements = [];
|
||||
$parsed = self::parseExpression($matches[1]);
|
||||
$prefix = self::$operatorHash[$parsed['operator']]['prefix'];
|
||||
$joiner = self::$operatorHash[$parsed['operator']]['joiner'];
|
||||
$useQuery = self::$operatorHash[$parsed['operator']]['query'];
|
||||
$allUndefined = true;
|
||||
|
||||
foreach ($parsed['values'] as $value) {
|
||||
if (!isset($variables[$value['value']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var mixed */
|
||||
$variable = $variables[$value['value']];
|
||||
$actuallyUseQuery = $useQuery;
|
||||
$expanded = '';
|
||||
|
||||
if (\is_array($variable)) {
|
||||
$isAssoc = self::isAssoc($variable);
|
||||
$kvp = [];
|
||||
/** @var mixed $var */
|
||||
foreach ($variable as $key => $var) {
|
||||
if ($isAssoc) {
|
||||
$key = \rawurlencode((string) $key);
|
||||
$isNestedArray = \is_array($var);
|
||||
} else {
|
||||
$isNestedArray = false;
|
||||
}
|
||||
|
||||
if (!$isNestedArray) {
|
||||
$var = \rawurlencode((string) $var);
|
||||
if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
|
||||
$var = self::decodeReserved($var);
|
||||
}
|
||||
}
|
||||
|
||||
if ($value['modifier'] === '*') {
|
||||
if ($isAssoc) {
|
||||
if ($isNestedArray) {
|
||||
// Nested arrays must allow for deeply nested structures.
|
||||
$var = \http_build_query([$key => $var], '', '&', \PHP_QUERY_RFC3986);
|
||||
} else {
|
||||
$var = \sprintf('%s=%s', (string) $key, (string) $var);
|
||||
}
|
||||
} elseif ($key > 0 && $actuallyUseQuery) {
|
||||
$var = \sprintf('%s=%s', $value['value'], (string) $var);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string $var */
|
||||
|
||||
$kvp[$key] = $var;
|
||||
}
|
||||
|
||||
if (0 === \count($variable)) {
|
||||
$actuallyUseQuery = false;
|
||||
} elseif ($value['modifier'] === '*') {
|
||||
$expanded = \implode($joiner, $kvp);
|
||||
if ($isAssoc) {
|
||||
// Don't prepend the value name when using the explode
|
||||
// modifier with an associative array.
|
||||
$actuallyUseQuery = false;
|
||||
}
|
||||
} else {
|
||||
if ($isAssoc) {
|
||||
// When an associative array is encountered and the
|
||||
// explode modifier is not set, then the result must be
|
||||
// a comma separated list of keys followed by their
|
||||
// respective values.
|
||||
foreach ($kvp as $k => &$v) {
|
||||
$v = \sprintf('%s,%s', $k, $v);
|
||||
}
|
||||
}
|
||||
$expanded = \implode(',', $kvp);
|
||||
}
|
||||
} else {
|
||||
$allUndefined = false;
|
||||
if ($value['modifier'] === ':' && isset($value['position'])) {
|
||||
$variable = \substr((string) $variable, 0, $value['position']);
|
||||
}
|
||||
$expanded = \rawurlencode((string) $variable);
|
||||
if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
|
||||
$expanded = self::decodeReserved($expanded);
|
||||
}
|
||||
}
|
||||
|
||||
if ($actuallyUseQuery) {
|
||||
if (!$expanded && $joiner !== '&') {
|
||||
$expanded = $value['value'];
|
||||
} else {
|
||||
$expanded = \sprintf('%s=%s', $value['value'], $expanded);
|
||||
}
|
||||
}
|
||||
|
||||
$replacements[] = $expanded;
|
||||
}
|
||||
|
||||
$ret = \implode($joiner, $replacements);
|
||||
|
||||
if ('' === $ret) {
|
||||
// Spec section 3.2.4 and 3.2.5
|
||||
if (false === $allUndefined && ('#' === $prefix || '.' === $prefix)) {
|
||||
return $prefix;
|
||||
}
|
||||
} else {
|
||||
if ('' !== $prefix) {
|
||||
return \sprintf('%s%s', $prefix, $ret);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an expression into parts
|
||||
*
|
||||
* @param string $expression Expression to parse
|
||||
*
|
||||
* @return array{operator:string, values:array<array{value:string, modifier:(''|'*'|':'), position?:int}>}
|
||||
*/
|
||||
private static function parseExpression(string $expression): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (isset(self::$operatorHash[$expression[0]])) {
|
||||
$result['operator'] = $expression[0];
|
||||
/** @var string */
|
||||
$expression = \substr($expression, 1);
|
||||
} else {
|
||||
$result['operator'] = '';
|
||||
}
|
||||
|
||||
$result['values'] = [];
|
||||
foreach (\explode(',', $expression) as $value) {
|
||||
$value = \trim($value);
|
||||
$varspec = [];
|
||||
if ($colonPos = \strpos($value, ':')) {
|
||||
$varspec['value'] = (string) \substr($value, 0, $colonPos);
|
||||
$varspec['modifier'] = ':';
|
||||
$varspec['position'] = (int) \substr($value, $colonPos + 1);
|
||||
} elseif (\substr($value, -1) === '*') {
|
||||
$varspec['modifier'] = '*';
|
||||
$varspec['value'] = (string) \substr($value, 0, -1);
|
||||
} else {
|
||||
$varspec['value'] = $value;
|
||||
$varspec['modifier'] = '';
|
||||
}
|
||||
$result['values'][] = $varspec;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an array is associative.
|
||||
*
|
||||
* This makes the assumption that input arrays are sequences or hashes.
|
||||
* This assumption is a tradeoff for accuracy in favor of speed, but it
|
||||
* should work in almost every case where input is supplied for a URI
|
||||
* template.
|
||||
*/
|
||||
private static function isAssoc(array $array): bool
|
||||
{
|
||||
return $array && \array_keys($array)[0] !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes percent encoding on reserved characters (used with + and #
|
||||
* modifiers).
|
||||
*/
|
||||
private static function decodeReserved(string $string): string
|
||||
{
|
||||
return \str_replace(self::$delimsPct, self::$delims, $string);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user