Skip to content

Commit 9efcfac

Browse files
Merge branch '6.4' into 7.0
* 6.4: [Cache] fix the previous fix [Cache] Fix expiration time for CouchbaseCollection [FrameworkBundle] Update docblock AbstractController [HttpFoundation][FrameworkBundle] Fix default locale is ignored when set_locale_from_accept_language is used add missing translations [Validator] updated Lithuanian translation [Validator] fix some non-sense Lithuanian translations [Validator] updated Slovenian translation [Validator] updated Finnish translation [RateLimit] Test and fix peeking behavior on rate limit policies [Validator] Add `Charset` french translation [Tests] Streamline CompiledUrlGenerator tests [Serializer] Skip uninitialized properties with deep_object_to_populate fix Constraints\Email::ERROR_NAMES
2 parents 68aac37 + 15ead1f commit 9efcfac

File tree

21 files changed

+156
-50
lines changed

21 files changed

+156
-50
lines changed

src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ protected function generateUrl(string $route, array $parameters = [], int $refer
108108
/**
109109
* Forwards the request to another controller.
110110
*
111-
* @param string $controller The controller name (a string like Bundle\BlogBundle\Controller\PostController::indexAction)
111+
* @param string $controller The controller name (a string like "App\Controller\PostController::index" or "App\Controller\PostController" if it is invokable)
112112
*/
113113
protected function forward(string $controller, array $path = [], array $query = []): Response
114114
{

src/Symfony/Component/Cache/Adapter/CouchbaseCollectionAdapter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class CouchbaseCollectionAdapter extends AbstractAdapter
3535
public function __construct(Collection $connection, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
3636
{
3737
if (!static::isSupported()) {
38-
throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.');
38+
throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.');
3939
}
4040

4141
$this->maxIdLength = static::MAX_KEY_LENGTH;
@@ -54,7 +54,7 @@ public static function createConnection(#[\SensitiveParameter] array|string $dsn
5454
}
5555

5656
if (!static::isSupported()) {
57-
throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.');
57+
throw new CacheException('Couchbase >= 3.0.5 < 4.0.0 is required.');
5858
}
5959

6060
set_error_handler(static fn ($type, $msg, $file, $line) => throw new \ErrorException($msg, 0, $type, $file, $line));
@@ -183,7 +183,7 @@ protected function doSave(array $values, $lifetime): array|bool
183183
}
184184

185185
$upsertOptions = new UpsertOptions();
186-
$upsertOptions->expiry($lifetime);
186+
$upsertOptions->expiry(\DateTimeImmutable::createFromFormat('U', time() + $lifetime));
187187

188188
$ko = [];
189189
foreach ($values as $key => $value) {

src/Symfony/Component/Cache/Tests/Adapter/CouchbaseCollectionAdapterTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
/**
2020
* @requires extension couchbase <4.0.0
21-
* @requires extension couchbase >=3.0.0
21+
* @requires extension couchbase >=3.0.5
2222
*
2323
* @group integration
2424
*
@@ -35,7 +35,7 @@ class CouchbaseCollectionAdapterTest extends AdapterTestCase
3535
public static function setUpBeforeClass(): void
3636
{
3737
if (!CouchbaseCollectionAdapter::isSupported()) {
38-
self::markTestSkipped('Couchbase >= 3.0.0 < 4.0.0 is required.');
38+
self::markTestSkipped('Couchbase >= 3.0.5 < 4.0.0 is required.');
3939
}
4040

4141
self::$client = AbstractAdapter::createConnection('couchbase://'.getenv('COUCHBASE_HOST').'/cache',
@@ -46,7 +46,7 @@ public static function setUpBeforeClass(): void
4646
public function createCachePool($defaultLifetime = 0): CacheItemPoolInterface
4747
{
4848
if (!CouchbaseCollectionAdapter::isSupported()) {
49-
self::markTestSkipped('Couchbase >= 3.0.0 < 4.0.0 is required.');
49+
self::markTestSkipped('Couchbase >= 3.0.5 < 4.0.0 is required.');
5050
}
5151

5252
$client = $defaultLifetime

src/Symfony/Component/HttpKernel/EventListener/LocaleListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private function setLocale(Request $request): void
6969
if ($locale = $request->attributes->get('_locale')) {
7070
$request->setLocale($locale);
7171
} elseif ($this->useAcceptLanguageHeader) {
72-
if ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales)) {
72+
if ($request->getLanguages() && $preferredLanguage = $request->getPreferredLanguage($this->enabledLocales)) {
7373
$request->setLocale($preferredLanguage);
7474
}
7575
$request->attributes->set('_vary_by_language', true);

src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,28 @@ public function testRequestPreferredLocaleFromAcceptLanguageHeader()
131131
$this->assertEquals('fr', $request->getLocale());
132132
}
133133

134+
public function testRequestDefaultLocaleIfNoAcceptLanguageHeaderIsPresent()
135+
{
136+
$request = new Request();
137+
$listener = new LocaleListener($this->requestStack, 'de', null, true, ['lt', 'de']);
138+
$event = $this->getEvent($request);
139+
140+
$listener->setDefaultLocale($event);
141+
$listener->onKernelRequest($event);
142+
$this->assertEquals('de', $request->getLocale());
143+
}
144+
145+
public function testRequestVaryByLanguageAttributeIsSetIfUsingAcceptLanguageHeader()
146+
{
147+
$request = new Request();
148+
$listener = new LocaleListener($this->requestStack, 'de', null, true, ['lt', 'de']);
149+
$event = $this->getEvent($request);
150+
151+
$listener->setDefaultLocale($event);
152+
$listener->onKernelRequest($event);
153+
$this->assertTrue($request->attributes->get('_vary_by_language'));
154+
}
155+
134156
public function testRequestSecondPreferredLocaleFromAcceptLanguageHeader()
135157
{
136158
$request = Request::create('/');

src/Symfony/Component/RateLimiter/Policy/FixedWindowLimiter.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,15 @@ public function reserve(int $tokens = 1, float $maxTime = null): Reservation
5959
$now = microtime(true);
6060
$availableTokens = $window->getAvailableTokens($now);
6161

62-
if ($availableTokens >= max(1, $tokens)) {
62+
if (0 === $tokens) {
63+
$waitDuration = $window->calculateTimeForTokens(1, $now);
64+
$reservation = new Reservation($now + $waitDuration, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), true, $this->limit));
65+
} elseif ($availableTokens >= $tokens) {
6366
$window->add($tokens, $now);
6467

6568
$reservation = new Reservation($now, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now)), true, $this->limit));
6669
} else {
67-
$waitDuration = $window->calculateTimeForTokens(max(1, $tokens), $now);
70+
$waitDuration = $window->calculateTimeForTokens($tokens, $now);
6871

6972
if (null !== $maxTime && $waitDuration > $maxTime) {
7073
// process needs to wait longer than set interval

src/Symfony/Component/RateLimiter/Policy/TokenBucketLimiter.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,20 @@ public function reserve(int $tokens = 1, float $maxTime = null): Reservation
6767
$now = microtime(true);
6868
$availableTokens = $bucket->getAvailableTokens($now);
6969

70-
if ($availableTokens >= max(1, $tokens)) {
70+
if ($availableTokens >= $tokens) {
7171
// tokens are now available, update bucket
7272
$bucket->setTokens($availableTokens - $tokens);
7373

74-
$reservation = new Reservation($now, new RateLimit($bucket->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now)), true, $this->maxBurst));
74+
if (0 === $availableTokens) {
75+
// This means 0 tokens where consumed (discouraged in most cases).
76+
// Return the first time a new token is available
77+
$waitDuration = $this->rate->calculateTimeForTokens(1);
78+
$waitTime = \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration));
79+
} else {
80+
$waitTime = \DateTimeImmutable::createFromFormat('U', floor($now));
81+
}
82+
83+
$reservation = new Reservation($now, new RateLimit($bucket->getAvailableTokens($now), $waitTime, true, $this->maxBurst));
7584
} else {
7685
$remainingTokens = $tokens - $availableTokens;
7786
$waitDuration = $this->rate->calculateTimeForTokens($remainingTokens);

src/Symfony/Component/RateLimiter/Tests/Policy/FixedWindowLimiterTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,21 @@ public function testPeekConsume()
123123
$rateLimit = $limiter->consume(0);
124124
$this->assertSame(10, $rateLimit->getLimit());
125125
$this->assertTrue($rateLimit->isAccepted());
126+
$this->assertEquals(
127+
\DateTimeImmutable::createFromFormat('U', (string) floor(microtime(true))),
128+
$rateLimit->getRetryAfter()
129+
);
126130
}
131+
132+
$limiter->consume();
133+
134+
$rateLimit = $limiter->consume(0);
135+
$this->assertEquals(0, $rateLimit->getRemainingTokens());
136+
$this->assertTrue($rateLimit->isAccepted());
137+
$this->assertEquals(
138+
\DateTimeImmutable::createFromFormat('U', (string) floor(microtime(true) + 60)),
139+
$rateLimit->getRetryAfter()
140+
);
127141
}
128142

129143
public static function provideConsumeOutsideInterval(): \Generator

src/Symfony/Component/RateLimiter/Tests/Policy/TokenBucketLimiterTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,26 @@ public function testPeekConsume()
134134

135135
$limiter->consume(9);
136136

137+
// peek by consuming 0 tokens twice (making sure peeking doesn't claim a token)
137138
for ($i = 0; $i < 2; ++$i) {
138139
$rateLimit = $limiter->consume(0);
139140
$this->assertTrue($rateLimit->isAccepted());
140141
$this->assertSame(10, $rateLimit->getLimit());
142+
$this->assertEquals(
143+
\DateTimeImmutable::createFromFormat('U', (string) floor(microtime(true))),
144+
$rateLimit->getRetryAfter()
145+
);
141146
}
147+
148+
$limiter->consume();
149+
150+
$rateLimit = $limiter->consume(0);
151+
$this->assertEquals(0, $rateLimit->getRemainingTokens());
152+
$this->assertTrue($rateLimit->isAccepted());
153+
$this->assertEquals(
154+
\DateTimeImmutable::createFromFormat('U', (string) floor(microtime(true) + 1)),
155+
$rateLimit->getRetryAfter()
156+
);
142157
}
143158

144159
public function testBucketRefilledWithStrictFrequency()

src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,16 @@ public function testDumpWithSimpleLocalizedRoutes()
104104

105105
public function testDumpWithRouteNotFoundLocalizedRoutes()
106106
{
107-
$this->expectException(RouteNotFoundException::class);
108-
$this->expectExceptionMessage('Unable to generate a URL for the named route "test" as such route does not exist.');
109107
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test')->setRequirement('_locale', 'en'));
110108

111109
$code = $this->generatorDumper->dump();
112110
file_put_contents($this->testTmpFilepath, $code);
113111

114112
$projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'), null, 'pl_PL');
113+
114+
$this->expectException(RouteNotFoundException::class);
115+
$this->expectExceptionMessage('Unable to generate a URL for the named route "test" as such route does not exist.');
116+
115117
$projectUrlGenerator->generate('test');
116118
}
117119

@@ -163,22 +165,25 @@ public function testDumpWithTooManyRoutes()
163165

164166
public function testDumpWithoutRoutes()
165167
{
166-
$this->expectException(\InvalidArgumentException::class);
167168
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
168169

169170
$projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext('/app.php'));
170171

172+
$this->expectException(\InvalidArgumentException::class);
173+
171174
$projectUrlGenerator->generate('Test', []);
172175
}
173176

174177
public function testGenerateNonExistingRoute()
175178
{
176-
$this->expectException(RouteNotFoundException::class);
177179
$this->routeCollection->add('Test', new Route('/test'));
178180

179181
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
180182

181183
$projectUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
184+
185+
$this->expectException(RouteNotFoundException::class);
186+
182187
$projectUrlGenerator->generate('NonExisting', []);
183188
}
184189

@@ -267,66 +272,72 @@ public function testAliases()
267272

268273
public function testTargetAliasNotExisting()
269274
{
270-
$this->expectException(RouteNotFoundException::class);
271-
272-
$this->routeCollection->addAlias('a', 'not-existing');
275+
$this->routeCollection->add('not-existing', new Route('/not-existing'));
276+
$this->routeCollection->addAlias('alias', 'not-existing');
273277

274278
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
275279

276-
$compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
280+
$compiledRoutes = require $this->testTmpFilepath;
281+
unset($compiledRoutes['alias']);
277282

283+
$this->expectException(RouteNotFoundException::class);
284+
285+
$compiledUrlGenerator = new CompiledUrlGenerator($compiledRoutes, new RequestContext());
278286
$compiledUrlGenerator->generate('a');
279287
}
280288

281289
public function testTargetAliasWithNamePrefixNotExisting()
282290
{
283-
$this->expectException(RouteNotFoundException::class);
284-
285291
$subCollection = new RouteCollection();
286-
$subCollection->addAlias('a', 'not-existing');
292+
$subCollection->add('not-existing', new Route('/not-existing'));
293+
$subCollection->addAlias('alias', 'not-existing');
287294
$subCollection->addNamePrefix('sub_');
288295

289296
$this->routeCollection->addCollection($subCollection);
290297

291298
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
292299

293-
$compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, new RequestContext());
300+
$compiledRoutes = require $this->testTmpFilepath;
301+
unset($compiledRoutes['sub_alias']);
294302

295-
$compiledUrlGenerator->generate('sub_a');
303+
$this->expectException(RouteNotFoundException::class);
304+
305+
$compiledUrlGenerator = new CompiledUrlGenerator($compiledRoutes, new RequestContext());
306+
$compiledUrlGenerator->generate('sub_alias');
296307
}
297308

298309
public function testCircularReferenceShouldThrowAnException()
299310
{
300-
$this->expectException(RouteCircularReferenceException::class);
301-
$this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> a -> b".');
302-
303311
$this->routeCollection->addAlias('a', 'b');
304312
$this->routeCollection->addAlias('b', 'a');
305313

314+
$this->expectException(RouteCircularReferenceException::class);
315+
$this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> a -> b".');
316+
306317
$this->generatorDumper->dump();
307318
}
308319

309320
public function testDeepCircularReferenceShouldThrowAnException()
310321
{
311-
$this->expectException(RouteCircularReferenceException::class);
312-
$this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> c -> b".');
313-
314322
$this->routeCollection->addAlias('a', 'b');
315323
$this->routeCollection->addAlias('b', 'c');
316324
$this->routeCollection->addAlias('c', 'b');
317325

326+
$this->expectException(RouteCircularReferenceException::class);
327+
$this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> c -> b".');
328+
318329
$this->generatorDumper->dump();
319330
}
320331

321332
public function testIndirectCircularReferenceShouldThrowAnException()
322333
{
323-
$this->expectException(RouteCircularReferenceException::class);
324-
$this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> c -> a -> b".');
325-
326334
$this->routeCollection->addAlias('a', 'b');
327335
$this->routeCollection->addAlias('b', 'c');
328336
$this->routeCollection->addAlias('c', 'a');
329337

338+
$this->expectException(RouteCircularReferenceException::class);
339+
$this->expectExceptionMessage('Circular reference detected for route "b", path: "b -> c -> a -> b".');
340+
330341
$this->generatorDumper->dump();
331342
}
332343

0 commit comments

Comments
 (0)