Expiring Signed URIs

Baptiste Contreras
Contributed by Baptiste Contreras in #51502

Signed URIs include a hash value that depends on the contents of the URI to ensure their integrity. Symfony provides a service to sign URIs and in Symfony 7.1, we're improving it so you can also make those URIs expire after some time.

Use the new optional second argument of sign() to define the URI expiration date:

1
2
$url = $this->urlGenerator->generate('document_show', ['id' => $document->getUuid()]);
$signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('+10 minutes'));

Expiring URIs include an _expiration query parameter that you can check on the server to decide if the URI is still valid.

Improved Linter for Expressions

Fabien Potencier
Contributed by Fabien Potencier in #53806

The ExpressionLanguage component provides a lint() method to check if the expression syntax is valid. This method allows to ignore unknown variables but you can't ignore unknown functions. That's why in Symfony 7.1, we're improving the linting feature to add more configurability to it:

1
2
3
4
5
6
7
// Before (null means that you decided to ignore unknown variables)
$parser->lint($expr, null);

// After
$parser->lint($expr, [], Parser::IGNORE_UNKNOWN_VARIABLES);
$parser->lint($expr, [], Parser::IGNORE_UNKNOWN_FUNCTIONS);
$parser->lint($expr, [], Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS);

These flags are also available in the parse() method.

Mapping Boolean Query String Parameters

Hubert Lenoir
Contributed by Hubert Lenoir in #54153

The #[MapQueryString] attribute, introduced in Symfony 6.3, takes the data from the $_GET PHP superglobal and tries to populate a given typed object with it. In Symfony 7.1 we're improving the support of boolean query parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// this is the DTO that the app wants to get from the query string data
final readonly class SearchDto
{
    public function __construct(public ?bool $restricted = null)
    {
    }
}

// this is the controller that will map the query string into the DTO
final class MyController
{
    #[Route(name: 'search', path: '/search')]
    public function __invoke(#[MapQueryString] SearchDto $search): Response
    {
        // ...
    }
}

The URL will be like /search?restricted=... and this is the logic used to transform the query string value:

  • If the value is 'true', 1, yes or on, it will be cast to (bool) true
  • If the value is 'false', 0, no or off, it will be cast to (bool) false
  • Otherwise, it will be cast to null

Create from Timestamp Polyfill

Alexander M. Turek
Contributed by Alexander M. Turek in #54442

PHP 8.4, to be released on November 21, 2024, will add the createFromTimestamp(int|float $timestamp) method to the DateTime and DateTimeImmutable classes. In Symfony 7.1, we've added this method as a polyfill in the Clock component so you can start using them today while keeping your apps future-proof.

Uid Conversion Methods

Thomas Calvet Nicolas Grekas
Contributed by Thomas Calvet and Nicolas Grekas in #53060 and #53382

The Uid component provides utilities to create and work with UUIDs and ULIDs. In Symfony 7.1 we've added some methods to perform more conversions:

1
2
3
4
// UUID/ULID already had a magic __toString() method; now, they also
// have the explicit toString() method
$ulidAsString = (new Ulid())->toString();
$uuidAsString = Uuid::v4()->toString();

The official specification defines eight types of UUIDs. As explained in the Symfony Docs, some of them are no longer recommended (like UUIDv1 and UUIDv6) because there are better alternatives (e.g. UUIDv7).

That's why in Symfony 7.1 we've added some methods to convert between UUID variants so you can migrate to the recommended versions:

1
2
3
4
5
$uuid = Uuid::v1();
$uuidV7 = $uuid->toV7();

$uuid = Uuid::v6();
$uuidV7 = $uuid->toV7();

Throttling HTTP Client

HypeMC
Contributed by HypeMC in #53550

Many third-party APIs limit the number of requests that you can make over a period of time. In Symfony 7.1, we're improving the HttpClient component to implement a throttling client thanks to the RateLimiter component.

First, you must define a rate limiter with the limits imposed by that external API (in this example, we don't want to make more than 10 requests every 5 seconds):

1
2
3
4
5
6
7
8
# config/packages/framework.yaml
framework:
    # ...
    rate_limiter:
        acme_api_limiter:
            policy: 'token_bucket'
            limit: 10
            rate: { interval: '5 seconds', amount: 10 }

Then, associate this rate limiter with your HTTP client using the new rate_limiter configuration option:

1
2
3
4
5
6
7
8
# config/packages/framework.yaml
framework:
    # ...
    http_client:
        scoped_clients:
            acme_api.client:
                base_uri: 'https://...'
                rate_limiter: acme_api_limiter

You can now inject this HTTP client anywhere and make requests respecting the limits of the API:

1
2
3
4
5
6
7
8
9
class SomeService
{
    public function __construct(
        #[Target('acme_api.client')] private HttpClientInterface $httpClient,
    ) {
    }

    // ...
}
Published in #Living on the edge