Upstream thephpleague/uri-src state of the touched files before dc0df9e387
This commit is contained in:
1 parent
e39d486924
commit
432904515e
3 files changed
+23
-143
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
|
||||
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
|
||||
|
||||
<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_map;
|
||||
use function array_reduce;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function implode;
|
||||
use function preg_match_all;
|
||||
use function preg_replace;
|
||||
use function preg_replace_callback;
|
||||
use function preg_split;
|
||||
use function rawurlencode;
|
||||
use function str_starts_with;
|
||||
use function str_replace;
|
||||
use function strpbrk;
|
||||
|
||||
use const PREG_SPLIT_DELIM_CAPTURE;
|
||||
use const PREG_SPLIT_NO_EMPTY;
|
||||
use const PREG_SET_ORDER;
|
||||
|
||||
/**
|
||||
* @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';
|
||||
|
||||
/**
|
||||
* Literal characters which must be percent encoded in the expanded
|
||||
* 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<Expression> */
|
||||
private readonly array $expressions;
|
||||
/** @var array<string> */
|
||||
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;
|
||||
|
||||
$expressions = [];
|
||||
foreach ($parts as $part) {
|
||||
if ($part instanceof Expression) {
|
||||
$expressions[$part->value] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
$this->expressions = $expressions;
|
||||
$this->variableNames = array_unique(
|
||||
array_merge(
|
||||
...array_map(
|
||||
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);
|
||||
false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
|
||||
|
||||
/** @var array<string> $segments */
|
||||
$segments = preg_split(self::REGEXP_EXPRESSION_DETECTOR, $template, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $founds, PREG_SET_ORDER);
|
||||
|
||||
$parts = [];
|
||||
$expressions = [];
|
||||
foreach ($segments as $segment) {
|
||||
if (!str_starts_with($segment, '{')) {
|
||||
$parts[] = self::encodeLiteral($segment);
|
||||
continue;
|
||||
}
|
||||
return new self($template, ...array_values(
|
||||
array_reduce($founds, function (array $carry, array $found): array {
|
||||
if (!isset($carry[$found['expression']])) {
|
||||
$carry[$found['expression']] = Expression::new($found['expression']);
|
||||
}
|
||||
|
||||
$parts[] = $expressions[$segment] ??= Expression::new($segment);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
return $carry;
|
||||
}, [])
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,10 +116,11 @@ final class Template implements Stringable
|
||||
|
||||
private function expandAll(VariableBag $variables): string
|
||||
{
|
||||
return implode('', array_map(
|
||||
static fn (string|Expression $part): string => $part instanceof Expression ? $part->expand($variables) : $part,
|
||||
$this->parts
|
||||
));
|
||||
return array_reduce(
|
||||
$this->expressions,
|
||||
fn (string $uri, Expression $expr): string => str_replace($expr->value, $expr->expand($variables), $uri),
|
||||
$this->value
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
$this->expectException(TemplateCanNotBeExpanded::class);
|
||||
|
||||
Reference in new issue
Block a user