Skip to content

Commit

Permalink
Improve Modifier usage and implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Jan 29, 2025
1 parent a8f40d6 commit e6f43d0
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 53 deletions.
83 changes: 50 additions & 33 deletions components/Modifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace League\Uri;

use BadMethodCallException;
use Deprecated;
use JsonSerializable;
use League\Uri\Components\DataPath;
Expand All @@ -37,28 +36,17 @@
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;

use function array_map;
use function filter_var;
use function get_object_vars;
use function is_bool;
use function ltrim;
use function rtrim;
use function sprintf;
use function str_ends_with;
use function str_starts_with;

use const FILTER_FLAG_IPV4;
use const FILTER_VALIDATE_IP;

/**
* @method static withScheme(Stringable|string|null $scheme) returns a new instance with the specified scheme.
* @method static withUserInfo(Stringable|string|null $user, Stringable|string|null $password = null) returns a new instance with the specified user info.
* @method static withHost(Stringable|string|null $host) returns a new instance with the specified host.
* @method static withPort(?int $port) returns a new instance with the specified port.
* @method static withPath(Stringable|string $path) returns a new instance with the specified path.
* @method static withQuery(Stringable|string|null $query) returns a new instance with the specified query.
* @method static withFragment(Stringable|string|null $fragment) returns a new instance with the specified fragment.
*/
class Modifier implements Stringable, JsonSerializable, UriAccess, Conditionable
{
final public function __construct(protected readonly Psr7UriInterface|UriInterface $uri)
Expand All @@ -85,37 +73,65 @@ public function getUri(): Psr7UriInterface|UriInterface

public function getUriString(): string
{
return $this->uri->__toString();
return $this->toString();
}

public function jsonSerialize(): string
{
return $this->uri->__toString();
return $this->toString();
}

public function __toString(): string
{
return $this->toString();
}

public function toString(): string
{
return $this->uri->__toString();
}

final public function __call(string $name, array $arguments): static
public function toDisplayString(): string
{
static $allowedMethods = [
'withScheme',
'withUserInfo',
'withHost',
'withPort',
'withPath',
'withQuery',
'withFragment',
];
return ($this->uri instanceof UriRenderer) ? $this->uri->toDisplayString() : Uri::new($this->uri)->toDisplayString();
}

return match (true) {
!in_array($name, $allowedMethods, true) => throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', self::class, $name)),
$this->uri instanceof UriInterface,
'withPort' === $name => new static($this->uri->$name(...$arguments)),
default => new static($this->uri->$name(...array_map(fn (Stringable|string|null $value): string => (string) $value, $arguments))),
};
public function withScheme(Stringable|string|null $scheme): static
{
return new static($this->uri->withScheme(self::normalizeComponent($scheme, $this->uri)));
}

public function withUserInfo(Stringable|string|null $user, Stringable|string|null $password): static
{
return new static($this->uri->withUserInfo(
self::normalizeComponent($user, $this->uri),
$password instanceof Stringable ? $password->__toString() : $password
));
}

public function withQuery(Stringable|string|null $query): static
{
return new static($this->uri->withQuery(self::normalizeComponent($query, $this->uri)));
}

public function withHost(Stringable|string|null $host): static
{
return new static($this->uri->withHost(self::normalizeComponent($host, $this->uri)));
}

public function withFragment(Stringable|string|null $fragment): static
{
return new static($this->uri->withFragment(self::normalizeComponent($fragment, $this->uri)));
}

public function withPort(?int $port): static
{
return new static($this->uri->withPort($port));
}

public function withPath(Stringable|string $path): static
{
return new static($this->uri->withPath((string) $path));
}

final public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static
Expand Down Expand Up @@ -603,7 +619,7 @@ public function replaceLabel(int $offset, Stringable|string|null $label): static
));
}

public function whatWgHost(): static
public function whatwgHost(): static
{
$host = $this->uri->getHost();
try {
Expand Down Expand Up @@ -837,10 +853,11 @@ final protected static function normalizePath(Psr7UriInterface|UriInterface $uri
*
* null value MUST be converted to the empty string if a Psr7 UriInterface is being manipulated.
*/
final protected static function normalizeComponent(?string $component, Psr7UriInterface|UriInterface $uri): ?string
final protected static function normalizeComponent(Stringable|string|null $component, Psr7UriInterface|UriInterface $uri): ?string
{
return match (true) {
$uri instanceof Psr7UriInterface => (string) $component,
$uri instanceof Psr7UriInterface,
$component instanceof Stringable => (string) $component,
default => $component,
};
}
Expand Down
36 changes: 34 additions & 2 deletions components/ModifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ public function testRemoveBasePathWithRelativePath(): void
}

#[\PHPUnit\Framework\Attributes\DataProvider('validwithoutSegmentProvider')]
public function testwithoutSegment(array $keys, string $expected): void
public function testWithoutSegment(array $keys, string $expected): void
{
self::assertSame($expected, $this->modifier->removeSegments(...$keys)->getUri()->getPath());
}
Expand Down Expand Up @@ -890,9 +890,41 @@ public function it_will_convert_uri_host_following_whatwg_rules(): void
self::assertSame(
'192.168.2.13',
Modifier::from(Http::new('https://0:0@0xc0a8020d/0?0#0'))
->whatWgHost()
->whatwgHost()
->getUri()
->getHost()
);
}

#[Test]
#[DataProvider('providesUriToDisplay')]
public function it_will_allow_direct_string_conversion(
string $uri,
string $expectedString,
string $expectedDisplayString
): void {
self::assertSame($expectedString, Modifier::from($uri)->toString());
self::assertSame($expectedDisplayString, Modifier::from($uri)->toDisplayString());
}

public static function providesUriToDisplay(): iterable
{
yield 'uri is unchanged' => [
'uri' => 'https://127.0.0.1/foo/bar',
'expectedString' => 'https://127.0.0.1/foo/bar',
'expectedDisplayString' => 'https://127.0.0.1/foo/bar',
];

yield 'idn host are changed' => [
'uri' => 'http://bébé.be',
'expectedString' => 'http://xn--bb-bjab.be',
'expectedDisplayString' => 'http://bébé.be',
];

yield 'other components are changed' => [
'uri' => 'http://bébé.be:80?q=toto%20le%20h%C3%A9ros',
'expectedString' => 'http://xn--bb-bjab.be?q=toto%20le%20h%C3%A9ros',
'expectedDisplayString' => 'http://bébé.be?q=toto le héros',
];
}
}
22 changes: 11 additions & 11 deletions docs/components/7.0/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ implements the `Stringable` and the `JsonSerializable` interface to improve deve
Under the hood the `Modifier` class intensively uses the [URI components objects](/components/7.0/)
to apply changes to the submitted URI object.

<p class="message-notice">The <code>when</code> method is available since version <code>7.6.0</code></p>
<p class="message-notice">The <code>when</code>, <code>toString</code> and <code>toDisplayString</code> methods are available since version <code>7.6.0</code></p>

To ease modifying URI since version 7.6.0, the `when` method is added and the modifier methods from
the underlying URI object are now accessible via proxy.
To ease modifying URI since version 7.6.0 you can directly access the modifier methods from the underlying
URI object. The methods behave as their owner so T

```php
use League\Uri\Modifier;
Expand All @@ -99,19 +99,19 @@ $foo = '';
echo Modifier::from('http://bébé.be')
->when(
'' !== $foo,
fn (Modifier $uri) => $uri->withQuery('firstname=jane&lastname=Doe'), //on true
fn (Modifier $uri) => $uri->mergeQueryParameters(['firstname' => 'john', 'lastname' => 'Doe']), //on false
fn (Modifier $uri) => $uri->withQuery('fname=jane&lname=Doe'), //on true
fn (Modifier $uri) => $uri->mergeQueryParameters(['fname' => 'john', 'lname' => 'Doe']), //on false
)
->appendSegment('toto')
->addRootLabel()
->prependLabel('shop')
->appendQuery('foo=toto&foo=tata')
->withFragment('chapter1')
->getUriIdnString();
// returns 'http://shop.bébé.be./toto?firstname=john&lastname=Doe&foo=toto&foo=tata#chapter1';
->toDisplayString();
// returns 'http://shop.bébé.be./toto?fname=john&lname=Doe&foo=toto&foo=tata#chapter1';
```

### Available methods
### Available modifiers

<div class="flex flex-row flex-wrap">
<div>
Expand Down Expand Up @@ -145,7 +145,7 @@ echo Modifier::from('http://bébé.be')
<li><a href="#modifierreplacelabel">replaceLabel</a></li>
<li><a href="#modifierremovelabels">removeLabels</a></li>
<li><a href="#modifierslicelabels">sliceLabels</a></li>
<li><a href="#modifierwhatwghost">whatWgHost</a></li>
<li><a href="#modifierwhatwgHost">whatwgHost</a></li>
</ul>
</div>
<div>
Expand Down Expand Up @@ -605,15 +605,15 @@ echo Modifier::from($uri)->sliceLabels(1, 1)->getUriString();

<p class="message-info">This modifier supports negative offset</p>

### Modifier::whatWgHost
### Modifier::whatwgHost

Returns the host as formatted following WHATWG host formatting

<p class="message-notice">available since version <code>7.6.0</code></p>

~~~php
$uri = "https://0:0@0:0";
echo Modifier::from($uri)->whatWgHost()->getUriString();
echo Modifier::from($uri)->whatwgHost()->getUriString();
//display "https://0:[email protected]:0"
~~~

Expand Down
32 changes: 29 additions & 3 deletions docs/uri/7.0/psr-compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ PSR interoperability
=======

As we are dealing with URI, the package provides classes compliant
with [PSR-7](https://www.php-fig.org/psr/psr-7/) and
[PSR-17](https://www.php-fig.org/psr/psr-17/). This is done to allow
more interoperability amongs PHP packages.
with [PSR-7](https://www.php-fig.org/psr/psr-7/) and [PSR-17](https://www.php-fig.org/psr/psr-17/) and [PSR-13](https://www.php-fig.org/psr/psr-13/). This
is done to allow more interoperability amongs PHP packages.

## PSR-7 compatibility

Expand Down Expand Up @@ -132,3 +131,30 @@ $uriFactory = new HttpFactory();
$uri = $uriFactory->createUri('http://example.com/path/to?q=foo%20bar#section-42');
echo $uri::class; // display League\Uri\Http
~~~

## PSR-13 compatibility

<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.

<p class="message-info">You could already use a <code>Uri</code> instance if the link must use a
concrete URI instead as the class also implements the <code>Stringable</code> interface.</p>
4 changes: 0 additions & 4 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ parameters:
- message: '#function gmp_(.*)? expects (GMP|int)#'
path: interfaces/IPv4/GMPCalculator.php
- identifier: missingType.iterableValue
- message: '#Variable method call on League\\Uri\\Contracts\\UriInterface.#'
path: components/Modifier.php
- message: '#Variable method call on Psr\\Http\\Message\\UriInterface.#'
path: components/Modifier.php
- '#Attribute class Deprecated does not exist.#'
- '#deprecated class League\\Uri\\BaseUri#'
- '#\Dom\\HTMLDocument#'
Expand Down

0 comments on commit e6f43d0

Please sign in to comment.