Upstream thephpleague/uri-src state of the touched files before dc0df9e387

This commit is contained in:
upstream committed 2026-09-04 08:43:47 +02:00
1 parent e39d486924
commit 432904515e
3 files changed
+23 -143

No files matched your search

-25
View File
@@ -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
+23 -61
View File
@@ -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
-57
View File
@@ -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);