Upstream thephpleague/uri-src state of the touched files before dc0df9e387
This commit is contained in:
1 parent
e39d486924
commit
432904515e
3 files changed
+22
-142
No files matched your search
@@ -85,31 +85,6 @@ echo $uriTemplate->expandOrFail($params);
|
|||||||
<p class="message-notice"><code>expandToUriOrFail()</code> and <code>expandToUrlOrFail()</code> are available since
|
<p class="message-notice"><code>expandToUriOrFail()</code> and <code>expandToUrlOrFail()</code> are available since
|
||||||
version <code>7.6.0</code></p>
|
version <code>7.6.0</code></p>
|
||||||
|
|
||||||
### Literal Expansion
|
|
||||||
|
|
||||||
The characters located outside of the template expressions are literals. As per
|
|
||||||
[RFC6570 section 3.1](https://www.rfc-editor.org/rfc/rfc6570#section-3.1) a literal character is
|
|
||||||
copied as is when it is allowed anywhere in the URI syntax, in other words when it belongs to the
|
|
||||||
`unreserved / reserved / pct-encoded` set. Any other character is percent encoded using its UTF-8
|
|
||||||
octets.
|
|
||||||
|
|
||||||
~~~php
|
|
||||||
$uriTemplate = new UriTemplate('https://example.com/café/{hotel}');
|
|
||||||
|
|
||||||
echo $uriTemplate->expand(['hotel' => 'Rest & Relax']), PHP_EOL;
|
|
||||||
// display https://example.com/caf%C3%A9/Rest%20%26%20Relax
|
|
||||||
~~~
|
|
||||||
|
|
||||||
Percent encoded triplets already present in the template are left untouched, they are never
|
|
||||||
encoded a second time.
|
|
||||||
|
|
||||||
~~~php
|
|
||||||
$uriTemplate = new UriTemplate('https://example.com/caf%C3%A9/{hotel}');
|
|
||||||
|
|
||||||
echo $uriTemplate->expand(['hotel' => 'Rest & Relax']), PHP_EOL;
|
|
||||||
// display https://example.com/caf%C3%A9/Rest%20%26%20Relax
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Template Variables
|
## Template Variables
|
||||||
|
|
||||||
<p class="message-notice">For maximum interoperability you should make sure your variables are
|
<p class="message-notice">For maximum interoperability you should make sure your variables are
|
||||||
|
|||||||
@@ -20,18 +20,14 @@ use Stringable;
|
|||||||
|
|
||||||
use function array_filter;
|
use function array_filter;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
|
use function array_reduce;
|
||||||
use function array_unique;
|
use function array_unique;
|
||||||
use function array_values;
|
use function preg_match_all;
|
||||||
use function implode;
|
|
||||||
use function preg_replace;
|
use function preg_replace;
|
||||||
use function preg_replace_callback;
|
use function str_replace;
|
||||||
use function preg_split;
|
|
||||||
use function rawurlencode;
|
|
||||||
use function str_starts_with;
|
|
||||||
use function strpbrk;
|
use function strpbrk;
|
||||||
|
|
||||||
use const PREG_SPLIT_DELIM_CAPTURE;
|
use const PREG_SET_ORDER;
|
||||||
use const PREG_SPLIT_NO_EMPTY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal The class exposes the internal representation of a Template and its usage
|
* @internal The class exposes the internal representation of a Template and its usage
|
||||||
@@ -43,36 +39,19 @@ final class Template implements Stringable
|
|||||||
*/
|
*/
|
||||||
private const REGEXP_EXPRESSION_DETECTOR = '/(?<expression>\{[^}]*})/x';
|
private const REGEXP_EXPRESSION_DETECTOR = '/(?<expression>\{[^}]*})/x';
|
||||||
|
|
||||||
/**
|
/** @var array<Expression> */
|
||||||
* Literal characters which must be percent encoded in the expanded
|
private readonly array $expressions;
|
||||||
* template: everything outside "unreserved / reserved / pct-encoded", a
|
|
||||||
* "%" starting no triplet included.
|
|
||||||
*
|
|
||||||
* @link https://www.rfc-editor.org/rfc/rfc6570#section-3.1
|
|
||||||
*/
|
|
||||||
private const REGEXP_LITERAL_TO_ENCODE = '/[^A-Za-z\d\-._~:\/?#\[\]@!$&\'()*+,;=%]+|%(?![A-Fa-f\d]{2})/';
|
|
||||||
|
|
||||||
/** @var array<string|Expression> */
|
|
||||||
private readonly array $parts;
|
|
||||||
/** @var array<string> */
|
/** @var array<string> */
|
||||||
public readonly array $variableNames;
|
public readonly array $variableNames;
|
||||||
|
|
||||||
private function __construct(public readonly string $value, string|Expression ...$parts)
|
private function __construct(public readonly string $value, Expression ...$expressions)
|
||||||
{
|
{
|
||||||
$this->parts = $parts;
|
$this->expressions = $expressions;
|
||||||
|
|
||||||
$expressions = [];
|
|
||||||
foreach ($parts as $part) {
|
|
||||||
if ($part instanceof Expression) {
|
|
||||||
$expressions[$part->value] = $part;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->variableNames = array_unique(
|
$this->variableNames = array_unique(
|
||||||
array_merge(
|
array_merge(
|
||||||
...array_map(
|
...array_map(
|
||||||
static fn (Expression $expression): array => $expression->variableNames,
|
static fn (Expression $expression): array => $expression->variableNames,
|
||||||
array_values($expressions)
|
$expressions
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -93,35 +72,17 @@ final class Template implements Stringable
|
|||||||
$remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
|
$remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
|
||||||
false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
|
false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
|
||||||
|
|
||||||
/** @var array<string> $segments */
|
preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $founds, PREG_SET_ORDER);
|
||||||
$segments = preg_split(self::REGEXP_EXPRESSION_DETECTOR, $template, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
|
||||||
|
|
||||||
$parts = [];
|
return new self($template, ...array_values(
|
||||||
$expressions = [];
|
array_reduce($founds, function (array $carry, array $found): array {
|
||||||
foreach ($segments as $segment) {
|
if (!isset($carry[$found['expression']])) {
|
||||||
if (!str_starts_with($segment, '{')) {
|
$carry[$found['expression']] = Expression::new($found['expression']);
|
||||||
$parts[] = self::encodeLiteral($segment);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$parts[] = $expressions[$segment] ??= Expression::new($segment);
|
return $carry;
|
||||||
}
|
}, [])
|
||||||
|
));
|
||||||
return new self($template, ...$parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Percent encodes the template characters which are not allowed to be copied as-is into a URI.
|
|
||||||
*
|
|
||||||
* @link https://www.rfc-editor.org/rfc/rfc6570#section-3.1
|
|
||||||
*/
|
|
||||||
private static function encodeLiteral(string $literal): string
|
|
||||||
{
|
|
||||||
return (string) preg_replace_callback(
|
|
||||||
self::REGEXP_LITERAL_TO_ENCODE,
|
|
||||||
static fn (array $matches): string => rawurlencode($matches[0]),
|
|
||||||
$literal
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,10 +116,11 @@ final class Template implements Stringable
|
|||||||
|
|
||||||
private function expandAll(VariableBag $variables): string
|
private function expandAll(VariableBag $variables): string
|
||||||
{
|
{
|
||||||
return implode('', array_map(
|
return array_reduce(
|
||||||
static fn (string|Expression $part): string => $part instanceof Expression ? $part->expand($variables) : $part,
|
$this->expressions,
|
||||||
$this->parts
|
fn (string $uri, Expression $expr): string => str_replace($expr->value, $expr->expand($variables), $uri),
|
||||||
));
|
$this->value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
|
|||||||
@@ -174,63 +174,6 @@ final class TemplateTest extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[DataProvider('providesLiteralEncoding')]
|
|
||||||
#[Test]
|
|
||||||
public function testItEncodesTheTemplateLiterals(string $notation, array $variables, string $expected): void
|
|
||||||
{
|
|
||||||
self::assertSame($expected, Template::new($notation)->expand($variables));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://www.rfc-editor.org/rfc/rfc6570#section-3.1
|
|
||||||
*/
|
|
||||||
public static function providesLiteralEncoding(): iterable
|
|
||||||
{
|
|
||||||
$variables = [
|
|
||||||
'var' => 'value',
|
|
||||||
'path' => '/foo/bar',
|
|
||||||
'hello' => 'Hello World!',
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
|
||||||
'a character disallowed in a URI is encoded as its UTF-8 octets' => [
|
|
||||||
'notation' => 'café/{var}',
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => 'caf%C3%A9/value',
|
|
||||||
],
|
|
||||||
'a percent encoded triplet is copied as is' => [
|
|
||||||
'notation' => 'x%20y/{var}',
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => 'x%20y/value',
|
|
||||||
],
|
|
||||||
'a percent encoded triplet is copied as is on both sides of an expression' => [
|
|
||||||
'notation' => 'x%20y{var}z%20w',
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => 'x%20yvaluez%20w',
|
|
||||||
],
|
|
||||||
'a space is encoded' => [
|
|
||||||
'notation' => 'a b/{var}',
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => 'a%20b/value',
|
|
||||||
],
|
|
||||||
'a percent sign which starts no triplet is encoded' => [
|
|
||||||
'notation' => '100%/{var}',
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => '100%25/value',
|
|
||||||
],
|
|
||||||
'reserved characters are copied as is' => [
|
|
||||||
'notation' => "/a[0]:b@c!d\$e&f'g(h)i*j+k,l;m=n?o#{var}",
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => "/a[0]:b@c!d\$e&f'g(h)i*j+k,l;m=n?o#value",
|
|
||||||
],
|
|
||||||
'the expanded expressions are never encoded twice' => [
|
|
||||||
'notation' => 'café{+path}{#hello}',
|
|
||||||
'variables' => $variables,
|
|
||||||
'expected' => 'caf%C3%A9/foo/bar#Hello%20World!',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExpandOrFailIfAtLeastOneVariableIsMissing(): void
|
public function testExpandOrFailIfAtLeastOneVariableIsMissing(): void
|
||||||
{
|
{
|
||||||
$this->expectException(TemplateCanNotBeExpanded::class);
|
$this->expectException(TemplateCanNotBeExpanded::class);
|
||||||
|
|||||||
Reference in new issue
Block a user