Upstream thephpleague/uri-src state of the touched files before 6b1d90a020
This commit is contained in:
1 parent
c0b6c4bc5f
commit
cb44762108
3 files changed
+631
No files matched your search
@@ -0,0 +1,294 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
title: RFC 6570 compliant URI template
|
||||||
|
description: The UriTemplate class enables expanding a URI template and its submitted parameters into an URI object.
|
||||||
|
---
|
||||||
|
|
||||||
|
URI Template
|
||||||
|
=======
|
||||||
|
|
||||||
|
The `League\Uri\UriTemplate` class enables expanding a URI object based on a URI template and its
|
||||||
|
submitted parameters following [RFC 6570 URI Template](http://tools.ietf.org/html/rfc6570).
|
||||||
|
|
||||||
|
## Template Expansion
|
||||||
|
|
||||||
|
### RFC Expansion
|
||||||
|
|
||||||
|
The `UriTemplate::expand` public method expands a URI template to generate a valid URI conforming
|
||||||
|
to RFC3986.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use League\Uri\UriTemplate;
|
||||||
|
|
||||||
|
$template = 'https://example.com/hotels/{hotel}/bookings/{booking}';
|
||||||
|
$params = ['booking' => '42', 'hotel' => 'Rest & Relax'];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
$uri = $uriTemplate->expand($params); // instance of League\Uri\Uri
|
||||||
|
$rfc3986Uri = $uriTemplate->expandToUri($params); // instance of Uri\Rfc3986\Uri
|
||||||
|
$whatWgUrl = $uriTemplate->expandToUrl($params); // instance of Uri\Whatwg\Url
|
||||||
|
$psr7Uri = $uriTemplate->expandToPsr7Uri($params); // instance of Psr7 UriInterface
|
||||||
|
|
||||||
|
echo $uri; //display https://example.com/hotels/Rest%20%26%20Relax/bookings/42"
|
||||||
|
~~~
|
||||||
|
|
||||||
|
<p class="message-notice"><code>expandToUri()</code>, <code>expandToUrl()</code> and <code>expandToPsr7Uri()</code> are available since
|
||||||
|
version <code>7.6.0</code></p>
|
||||||
|
|
||||||
|
<p class="message-info">Since version <code>7.6</code> the <code>expand()</code> method takes a second optional parameter
|
||||||
|
which serves as a base URI. The generated URI is resolved against the Base URI.</p>
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use League\Uri\UriTemplate;
|
||||||
|
|
||||||
|
$template = '/hotels/{hotel}/bookings/{booking}';
|
||||||
|
$params = ['booking' => '42', 'hotel' => 'Rest & Relax'];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
$uri = $uriTemplate->expand($params, 'https://example.com'); // instance of League\Uri\Uri
|
||||||
|
$rfc3986Uri = $uriTemplate->expandToUri($params, 'https://example.com'); // instance of Uri\Rfc3986\Uri
|
||||||
|
$whatWgUrl = $uriTemplate->expandToUrl($params, 'https://example.com'); // instance of Uri\Whatwg\Url
|
||||||
|
$whatWgUrl = $uriTemplate->expandToPsr7Uri($params, 'https://example.com'); // instance of League\Uri\Http
|
||||||
|
|
||||||
|
echo $uri; //display https://example.com/hotels/Rest%20%26%20Relax/bookings/42"
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Strict Expansion
|
||||||
|
|
||||||
|
By default, if variables are missing or are not provided an empty string is used as replacement
|
||||||
|
string as per the RFC. If you want to force correct expansion you can use the `expandOrFail`
|
||||||
|
method. It behaves exactly like the `expand` method but will additionnally throw an
|
||||||
|
exception if there are missing required variables.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://api.twitter.com/{version}/search/{term}/{?q*,limit}';
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => ['john', 'doe'],
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
echo $uriTemplate->expand($params), PHP_EOL;
|
||||||
|
// display https://api.twitter.com//search/john,doe/?q=a&q=b&limit=10 with missing version
|
||||||
|
|
||||||
|
echo $uriTemplate->expandOrFail($params);
|
||||||
|
// will throw a TemplateCanNotBeExpanded exception with the following message
|
||||||
|
// Missing variables `version`
|
||||||
|
~~~
|
||||||
|
|
||||||
|
<p class="message-notice"><code>expandToUriOrFail()</code> and <code>expandToUrlOrFail()</code> are available since
|
||||||
|
version <code>7.6.0</code></p>
|
||||||
|
|
||||||
|
## Template Variables
|
||||||
|
|
||||||
|
<p class="message-notice">For maximum interoperability you should make sure your variables are
|
||||||
|
strings or stringable objects otherwise the value will be cast to string following PHP rules
|
||||||
|
except for boolean values <code>true</code> and <code>false</code> which will be converted
|
||||||
|
to <code>1</code> and <code>0</code> respectively.</p>
|
||||||
|
|
||||||
|
### Default Variables
|
||||||
|
|
||||||
|
The constructor takes an optional set of default variables that can be applied by default when
|
||||||
|
expanding the URI template.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://api.twitter.com/{version}/search/{term:1}/{term}/{?q*,limit}';
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => 'john',
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template, ['version' => 1.1]);
|
||||||
|
echo $uriTemplate->expand($params);
|
||||||
|
//displays https://api.twitter.com/1.1/search/j/john/?q=a&q=b&limit=10
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Runtime Variables
|
||||||
|
|
||||||
|
The default variables are overwritten by those supplied to the `expand` method.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://api.twitter.com/{version}/search/{term:1}/{term}/{?q*,limit}';
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => 'john',
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
'version' => '2.0'
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template, ['version' => '1.1']);
|
||||||
|
echo $uriTemplate->expand($params), PHP_EOL;
|
||||||
|
//displays https://api.twitter.com/2.0/search/j/john/?q=a&q=b&limit=10
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The `expandToUri()`, `expandToUrl()` and `expandToPsr7Uri()` methods will act exactly like the `expand()` method
|
||||||
|
but will instead return a `Uri\Rfc3986\Uri`, a `Uri\Whatwg\Url` or a `Psr7\Http\Message\UriInterface` object respectively.
|
||||||
|
|
||||||
|
<p class="message-warning">a WHATWG URL must always be absolute if the URI template is not you
|
||||||
|
MUST provide a base URL to <code>expandToUrl()</code> otherwise an <code>Uri\WhatWg\InvalidUrlException</code>
|
||||||
|
exception will be thrown.</p>
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = '/{version}/search/{term:1}/{term}/{?q*,limit}';
|
||||||
|
//the template represents a non-absolute URL
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => 'john',
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
'version' => '2.0'
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template, ['version' => '1.1']);
|
||||||
|
|
||||||
|
echo $uriTemplate->expandToUrl($params, 'https://api.twitter.com')->toAsciiString(); //works
|
||||||
|
echo $uriTemplate->expandToUrl($params); //will throw
|
||||||
|
~~~
|
||||||
|
|
||||||
|
<p class="message-info">You may return a different <code>Psr7\Http\Message\UriInterface</code> object if you
|
||||||
|
provide to the <code>expandToPsr7Uri</code> your own <code>Psr\Http\Message\UriFactoryInterface</code> class
|
||||||
|
as the third optional argument.</p>
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
use League\Uri\UriTemplate;
|
||||||
|
use Laminas\Diactoros\UriFactory;
|
||||||
|
|
||||||
|
$template = '/{version}/search/{term:1}/{term}/{?q*,limit}';
|
||||||
|
//the template represents a non-absolute URL
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => 'john',
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
'version' => '2.0'
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template, ['version' => '1.1']);
|
||||||
|
$uri = $uriTemplate->expandToPsr7Uri($params, 'https://api.twitter.com', new UriFactory());
|
||||||
|
$uri::class; // returns 'Laminas\Diactoros\Uri'
|
||||||
|
~~~
|
||||||
|
|
||||||
|
By default, if no factory is provided, the returned PSR-7 `UriInterface` object is a `League\Uri\Http` instance.
|
||||||
|
|
||||||
|
### Updating Variables
|
||||||
|
|
||||||
|
At any given time you may update your default variables but since the `UriTemplate`
|
||||||
|
is an immutable object instead of modifying the current instance, a new
|
||||||
|
instance with the modified default variables will be returned.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://api.twitter.com/{version}/search/{term:1}/{term}/{?q*,limit}';
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => 'john',
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
'version' => '2.0'
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template, ['version' => '1.0', 'foo' => 'bar']);
|
||||||
|
$uriTemplate->getDefaultVariables(); //returns new VariableBag(['version' => '1.0'])
|
||||||
|
$newUriTemplate = $uriTemplate->withDefaultVariables(['version' => '1.1']);
|
||||||
|
$newUriTemplate->getDefaultVariables(); //returns new VariableBag(['version' => '1.1'])
|
||||||
|
~~~
|
||||||
|
|
||||||
|
<p class="message-warning">Following RFC6570 requirements means not support for
|
||||||
|
nested array like the one used with <code>http_build_query</code></p>
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://example.com/hotels/{hotel}/book{?query*}';
|
||||||
|
$params = [
|
||||||
|
'hotel' => 'Rest & Relax',
|
||||||
|
'query' => [
|
||||||
|
'period' => [
|
||||||
|
'start' => '2020-01-12',
|
||||||
|
'end' => '2020-01-15',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
$uriTemplate->expand($params);
|
||||||
|
// will throw a League\Uri\UriTemplate\TemplateCanNotBeExpanded when trying to expand the `period` value.
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
### Prefix Modifier
|
||||||
|
|
||||||
|
While the RFC does not forbid this, the `UriTemplate` class will throw an exception
|
||||||
|
if an attempt is made to use the prefix modifier with a list of value. Other
|
||||||
|
implementations will silently ignore the modifier **but** this package will
|
||||||
|
trigger the exception to alert the user that something might be wrong and
|
||||||
|
that the generated URI might not be the one expected.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://api.twitter.com/{version}/search/{term:1}/{term}/{?q*,limit}';
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'term' => ['john', 'doe'],
|
||||||
|
'q' => ['a', 'b'],
|
||||||
|
'limit' => '10',
|
||||||
|
'version' => '2.0'
|
||||||
|
];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
echo $uriTemplate->expand($params), PHP_EOL;
|
||||||
|
// throw a League\Uri\UriTemplate\TemplateCanNotBeExpanded because the term variable is a list and not a string.
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Braces Usage
|
||||||
|
|
||||||
|
The following implementation disallows the use of braces `{` or `}` outside of being URI
|
||||||
|
template expression delimiters. If not used as the boundary of an expression an
|
||||||
|
exception will be triggered.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://example.com/hotels/{/book{?query*}';
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
// will throw a League\Uri\Exceptions\SyntaxError on instantiation
|
||||||
|
~~~
|
||||||
|
|
||||||
|
If your template do require them you should URL encode them.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
$template = 'https://example.com/hotels/%7B/{hotel}';
|
||||||
|
$params = ['booking' => 42, 'hotel' => 'Rest & Relax'];
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate($template);
|
||||||
|
echo $uriTemplate->expand($params), PHP_EOL;
|
||||||
|
// https://example.com/hotels/%7B/Rest%20%26%20Relax
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Interoperability
|
||||||
|
|
||||||
|
<p class="message-notice">Available since <code>version 7.6</code></p>
|
||||||
|
|
||||||
|
To allow easier integration with other PHP packages and especially [PSR-13](https://www.php-fig.org/psr/psr-13/)
|
||||||
|
the `UriTemplate` class implements the `Stringable` interface.
|
||||||
|
|
||||||
|
~~~php
|
||||||
|
use League\Uri\UriTemplate;
|
||||||
|
use Symfony\Component\WebLink\Link;
|
||||||
|
|
||||||
|
$uriTemplate = new UriTemplate('https://google.com/search{?q*}');
|
||||||
|
|
||||||
|
$link = (new Link())
|
||||||
|
->withHref($uriTemplate)
|
||||||
|
->withRel('next')
|
||||||
|
->withAttribute('me', 'you');
|
||||||
|
|
||||||
|
// Once serialized will return
|
||||||
|
// '<https://google.com/search{?q*}>; rel="next"; me="you"'
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The `Symfony\Component\WebLink\Link` package implements `PSR-13` interfaces.
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* League.Uri (https://uri.thephpleague.com)
|
||||||
|
*
|
||||||
|
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace League\Uri\UriTemplate;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
use Deprecated;
|
||||||
|
use League\Uri\Exceptions\SyntaxError;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
use function array_filter;
|
||||||
|
use function array_map;
|
||||||
|
use function array_reduce;
|
||||||
|
use function array_unique;
|
||||||
|
use function preg_match_all;
|
||||||
|
use function preg_replace;
|
||||||
|
use function str_replace;
|
||||||
|
use function strpbrk;
|
||||||
|
|
||||||
|
use const PREG_SET_ORDER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal The class exposes the internal representation of a Template and its usage
|
||||||
|
*/
|
||||||
|
final class Template implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Expression regular expression pattern.
|
||||||
|
*/
|
||||||
|
private const REGEXP_EXPRESSION_DETECTOR = '/(?<expression>\{[^}]*})/x';
|
||||||
|
|
||||||
|
/** @var array<Expression> */
|
||||||
|
private readonly array $expressions;
|
||||||
|
/** @var array<string> */
|
||||||
|
public readonly array $variableNames;
|
||||||
|
|
||||||
|
private function __construct(public readonly string $value, Expression ...$expressions)
|
||||||
|
{
|
||||||
|
$this->expressions = $expressions;
|
||||||
|
$this->variableNames = array_unique(
|
||||||
|
array_merge(
|
||||||
|
...array_map(
|
||||||
|
static fn (Expression $expression): array => $expression->variableNames,
|
||||||
|
$expressions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SyntaxError if the template contains invalid expressions
|
||||||
|
* @throws SyntaxError if the template contains invalid variable specification
|
||||||
|
*/
|
||||||
|
public static function new(BackedEnum|Stringable|string $template): self
|
||||||
|
{
|
||||||
|
if ($template instanceof BackedEnum) {
|
||||||
|
$template = $template->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = (string) $template;
|
||||||
|
/** @var string $remainder */
|
||||||
|
$remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
|
||||||
|
false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
|
||||||
|
|
||||||
|
preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $founds, PREG_SET_ORDER);
|
||||||
|
|
||||||
|
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']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $carry;
|
||||||
|
}, [])
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TemplateCanNotBeExpanded if the variables are invalid
|
||||||
|
*/
|
||||||
|
public function expand(iterable $variables = []): string
|
||||||
|
{
|
||||||
|
if (!$variables instanceof VariableBag) {
|
||||||
|
$variables = new VariableBag($variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expandAll($variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws TemplateCanNotBeExpanded if the variables are invalid or missing
|
||||||
|
*/
|
||||||
|
public function expandOrFail(iterable $variables = []): string
|
||||||
|
{
|
||||||
|
if (!$variables instanceof VariableBag) {
|
||||||
|
$variables = new VariableBag($variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
$missing = array_filter($this->variableNames, fn (string $name): bool => !isset($variables[$name]));
|
||||||
|
if ([] !== $missing) {
|
||||||
|
throw TemplateCanNotBeExpanded::dueToMissingVariables(...$missing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expandAll($variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function expandAll(VariableBag $variables): string
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEPRECATION WARNING! This method will be removed in the next major point release.
|
||||||
|
*
|
||||||
|
* @throws SyntaxError if the template contains invalid expressions
|
||||||
|
* @throws SyntaxError if the template contains invalid variable specification
|
||||||
|
* @deprecated Since version 7.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @see Template::new()
|
||||||
|
*
|
||||||
|
* Create a new instance from a string.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#[Deprecated(message:'use League\Uri\UriTemplate\Template::new() instead', since:'league/uri:7.0.0')]
|
||||||
|
public static function createFromString(Stringable|string $template): self
|
||||||
|
{
|
||||||
|
return self::new($template);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* League.Uri (https://uri.thephpleague.com)
|
||||||
|
*
|
||||||
|
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace League\Uri\UriTemplate;
|
||||||
|
|
||||||
|
use JsonException;
|
||||||
|
use League\Uri\Exceptions\SyntaxError;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use const JSON_THROW_ON_ERROR;
|
||||||
|
|
||||||
|
#[CoversClass(Template::class)]
|
||||||
|
final class TemplateTest extends TestCase
|
||||||
|
{
|
||||||
|
private static string $rootPath = __DIR__.'/../../vendor/uri-templates/uritemplate-test';
|
||||||
|
|
||||||
|
/** @var array<string> */
|
||||||
|
private static array $testFilenames = [
|
||||||
|
'spec-examples.json',
|
||||||
|
'negative-tests.json',
|
||||||
|
'extended-tests.json',
|
||||||
|
];
|
||||||
|
|
||||||
|
#[DataProvider('uriTemplateSpecificationDataProvider')]
|
||||||
|
#[Test]
|
||||||
|
public function testItCompliesWithUriTemplatesExpansionTests(
|
||||||
|
array $variables,
|
||||||
|
string $input,
|
||||||
|
string|array|false $expected
|
||||||
|
): void {
|
||||||
|
if (false === $expected) {
|
||||||
|
$this->expectException(Throwable::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = Template::new($input)->expand(new VariableBag($variables));
|
||||||
|
|
||||||
|
if (is_array($expected)) {
|
||||||
|
self::assertContains($result, $expected);
|
||||||
|
} else {
|
||||||
|
self::assertSame($expected, $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws JsonException
|
||||||
|
* @throws RuntimeException
|
||||||
|
* @return iterable<string, array{
|
||||||
|
* variables:array{string, string|int},
|
||||||
|
* input:string,
|
||||||
|
* expected:string|array<string>|false
|
||||||
|
* }>
|
||||||
|
*/
|
||||||
|
public static function uriTemplateSpecificationDataProvider(): iterable
|
||||||
|
{
|
||||||
|
foreach (static::$testFilenames as $path) {
|
||||||
|
$path = static::$rootPath.'/'.ltrim($path, '/');
|
||||||
|
if (false === $content = file_get_contents($path)) {
|
||||||
|
throw new RuntimeException("unable to connect to the path `$path`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array $records */
|
||||||
|
$records = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
foreach ($records as $title => $testSuite) {
|
||||||
|
$level = $testSuite['level'] ?? null;
|
||||||
|
$variables = $testSuite['variables'];
|
||||||
|
foreach ($testSuite['testcases'] as $offset => [$input, $expected]) {
|
||||||
|
yield $title.' - '.$level.' # '.($offset + 1) => [
|
||||||
|
'variables' => $variables,
|
||||||
|
'input' => $input,
|
||||||
|
'expected' => $expected,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('providesValidNotation')]
|
||||||
|
public function testItCanBeInstantiatedWithAValidNotation(string $notation): void
|
||||||
|
{
|
||||||
|
self::assertSame($notation, Template::new($notation)->value);
|
||||||
|
self::assertSame($notation, (string) Template::new($notation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function providesValidNotation(): iterable
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'complex template' => ['http://example.com{+path}{/segments}{?query,more*,foo[]*}'],
|
||||||
|
'template without expression' => ['foobar'],
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('providesInvalidNotation')]
|
||||||
|
public function testItFailsToInstantiatedWithAnInvalidNotation(string $notation): void
|
||||||
|
{
|
||||||
|
self::expectException(SyntaxError::class);
|
||||||
|
|
||||||
|
Template::new($notation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function providesInvalidNotation(): iterable
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['fooba{r'],
|
||||||
|
['fooba}r'],
|
||||||
|
['fooba}{r'],
|
||||||
|
['{foo{bar'],
|
||||||
|
['{foo}}bar'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('expectedVariableNames')]
|
||||||
|
public function testGetVariableNames(string $template, array $expected): void
|
||||||
|
{
|
||||||
|
self::assertSame($expected, Template::new($template)->variableNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function expectedVariableNames(): iterable
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'template' => '',
|
||||||
|
'expected' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '{foo}{bar}{420}',
|
||||||
|
'expected' => ['foo', 'bar', '420'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '{foo}{foo:2}{+foo}',
|
||||||
|
'expected' => ['foo'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '{bar}{foo}',
|
||||||
|
'expected' => ['bar', 'foo'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('providesExpansion')]
|
||||||
|
public function testItCanExpandVariables(string $notation, array $variables, string $expected): void
|
||||||
|
{
|
||||||
|
self::assertSame($expected, Template::new($notation)->expand($variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function providesExpansion(): iterable
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'with variables' => [
|
||||||
|
'notation' => 'foobar{var}',
|
||||||
|
'variables' => ['var' => 'yolo'],
|
||||||
|
'expected' => 'foobaryolo',
|
||||||
|
],
|
||||||
|
'with no variables' => [
|
||||||
|
'notation' => 'foobar',
|
||||||
|
'variables' => [],
|
||||||
|
'expected' => 'foobar',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExpandOrFailIfAtLeastOneVariableIsMissing(): void
|
||||||
|
{
|
||||||
|
$this->expectException(TemplateCanNotBeExpanded::class);
|
||||||
|
|
||||||
|
Template::new('{var}{baz}')->expandOrFail(['var' => 'bar']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function it_can_expand_with_expand_or_fail_when_all_variables_are_present(): void
|
||||||
|
{
|
||||||
|
self::assertSame('barfoo', Template::new('{var}{baz}')->expandOrFail(['var' => 'bar', 'baz' => 'foo']));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user