8000 Merge branch '6.0' into 6.1 · symfony/symfony@6be4729 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6be4729

Browse files
committed
Merge branch '6.0' into 6.1
* 6.0: [Validator] Allow Sequence constraint to be applied onto class as an attribute Try making tests a bit less transient Fix CI on macos-11 [Serializer] Fix denormalizing custom class in UidNormalizer [Config] In XmlUtils, avoid converting from octal every string starting with a 0 Make RateLimiter resilient to timeShifting
2 parents bf0091e + d3a33b9 commit 6be4729

File tree

20 files changed

+124
-41
lines changed

20 files changed

+124
-41
lines changed

.appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ test_script:
6262
- copy /Y c:\php\php.ini-min c:\php\php.ini
6363
- IF %APPVEYOR_REPO_BRANCH:~-2% neq .x (rm -Rf src\Symfony\Bridge\PhpUnit)
6464
- mv src\Symfony\Component\HttpClient\phpunit.xml.dist src\Symfony\Component\HttpClient\phpunit.xml
65-
- php phpunit src\Symfony --exclude-group tty,benchmark,intl-data || SET X=!errorlevel!
65+
- php phpunit src\Symfony --exclude-group tty,benchmark,intl-data,network,transient-on-windows || SET X=!errorlevel!
6666
- php phpunit src\Symfony\Component\HttpClient || SET X=!errorlevel!
6767
- copy /Y c:\php\php.ini-max c:\php\php.ini
68-
- php phpunit src\Symfony --exclude-group tty,benchmark,intl-data || SET X=!errorlevel!
68+
- php phpunit src\Symfony --exclude-group tty,benchmark,intl-data,network,transient-on-windows || SET X=!errorlevel!
6969
- php phpunit src\Symfony\Component\HttpClient || SET X=!errorlevel!
7070
- exit %X%

.github/workflows/unit-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ jobs:
146146
echo "::endgroup::"
147147
148148
- name: Patch return types
149-
if: "matrix.php == '8.1' && ! matrix.mode && matrix.os == 'ubuntu-20.04'"
149+
if: "matrix.php == '8.1' && ! matrix.mode && matrix.os != 'macos-11'"
150150
run: |
151151
patch -sp1 < .github/expected-missing-return-types.diff
152152
git add .
@@ -224,7 +224,7 @@ jobs:
224224
[[ ! $X ]] || (exit 1)
225225
226226
- name: Run tests with SIGCHLD enabled PHP
227-
if: "matrix.php == '8.0' && ! matrix.mode"
227+
if: "matrix.php == '8.0' && ! matrix.mode && matrix.os != 'macos-11'"
228228
run: |
229229
mkdir build
230230
cd build

src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ public function getDataForPhpize(): array
169169
[1, '1'],
170170
[-1, '-1'],
171171
[0777, '0777'],
172+
[-511, '-0777'],
173+
['0877', '0877'],
172174
[255, '0xFF'],
173175
[100.0, '1e2'],
174176
[-120.0, '-1.2E2'],

src/Symfony/Component/Config/Util/XmlUtils.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,11 @@ public static function phpize(string|\Stringable $value): mixed
216216
case 'null' === $lowercaseValue:
217217
return null;
218218
case ctype_digit($value):
219-
$raw = $value;
220-
$cast = (int) $value;
221-
222-
return '0' == $value[0] ? octdec($value) : (($raw === (string) $cast) ? $cast : $raw);
223219
case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
224220
$raw = $value;
225221
$cast = (int) $value;
226222

227-
return '0' == $value[1] ? octdec($value) : (($raw === (string) $cast) ? $cast : $raw);
223+
return self::isOctal($value) ? \intval($value, 8) : (($raw === (string) $cast) ? $cast : $raw);
228224
case 'true' === $lowercaseValue:
229225
return true;
230226
case 'false' === $lowercaseValue:
@@ -261,4 +257,13 @@ protected static function getXmlErrors(bool $internalErrors)
261257

262258
return $errors;
263259
}
260+
261+
private static function isOctal(string $str): bool
262+
{
263+
if ('-' === $str[0]) {
264+
$str = substr($str, 1);
265+
}
266+
267+
return $str === '0'.decoct(\intval($str, 8));
268+
}
264269
}

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,9 @@ public function testRenderExceptionLineBreaks()
883883
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/application_renderexception_linebreaks.txt', $tester->getDisplay(true), '->renderException() keep multiple line breaks');
884884
}
885885

886+
/**
887+
* @group transient-on-windows
888+
*/
886889
public function testRenderAnonymousException()
887890
{
888891
$application = new Application();
@@ -906,6 +909,9 @@ public function testRenderAnonymousException()
906909
$this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
907910
}
908911

912+
/**
913+
* @group transient-on-windows
914+
*/
909915
public function testRenderExceptionStackTraceContainsRootException()
910916
{
911917
$application = new Application();

src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class RecursiveDirectoryIteratorTest extends IteratorTestCase
2121
public function testRewindOnFtp()
2222
{
2323
try {
24-
$i = new RecursiveDirectoryIterator('ftp://speedtest.tele2.net/', \RecursiveDirectoryIterator::SKIP_DOTS);
24+
$i = new RecursiveDirectoryIterator('ftp://speedtest:speedtest@ftp.otenet.gr/', \RecursiveDirectoryIterator::SKIP_DOTS);
2525
} catch (\UnexpectedValueException $e) {
2626
$this->markTestSkipped('Unsupported stream "ftp".');
2727
}
@@ -37,14 +37,14 @@ public function testRewindOnFtp()
3737
public function testSeekOnFtp()
3838
{
3939
try {
40-
$i = new RecursiveDirectoryIterator('ftp://speedtest.tele2.net/', \RecursiveDirectoryIterator:: 1241 SKIP_DOTS);
40+
$i = new RecursiveDirectoryIterator('ftp://speedtest:speedtest@ftp.otenet.gr/', \RecursiveDirectoryIterator::SKIP_DOTS);
4141
} catch (\UnexpectedValueException $e) {
4242
$this->markTestSkipped('Unsupported stream "ftp".');
4343
}
4444

4545
$contains = [
46-
'ftp://speedtest.tele2.net'.\DIRECTORY_SEPARATOR.'1000GB.zip',
47-
'ftp://speedtest.tele2.net'.\DIRECTORY_SEPARATOR.'100GB.zip',
46+
'ftp://speedtest:speedtest@ftp.otenet.gr'.\DIRECTORY_SEPARATOR.'test100Mb.db',
47+
'ftp://speedtest:speedtest@ftp.otenet.gr'.\DIRECTORY_SEPARATOR.'test100k.db',
4848
];
4949
$actual = [];
5050

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function reserve(int $tokens = 1, float $maxTime = null): Reservation
6060
$now = microtime(true);
6161
$availableTokens = $window->getAvailableTokens($now);
6262
if ($availableTokens >= $tokens) {
63-
$window->add($tokens);
63+
$window->add($tokens, $now);
6464

6565
$reservation = new Reservation($now, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now)), true, $this->limit));
6666
} else {
@@ -71,7 +71,7 @@ public function reserve(int $tokens = 1, float $maxTime = null): Reservation
7171
throw new MaxWaitDurationExceededException(sprintf('The rate limiter wait time ("%d" seconds) is longer than the provided maximum time ("%d" seconds).', $waitDuration, $maxTime), new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->limit));
7272
}
7373

74-
$window->add($tokens);
74+
$window->add($tokens, $now);
7575

7676
$reservation = new Reservation($now + $waitDuration, new RateLimit($window->getAvailableTokens($now), \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->limit));
7777
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function setTokens(int $tokens): void
6969

7070
public function getAvailableTokens(float $now): int
7171
{
72-
$elapsed = $now - $this->timer;
72+
$elapsed = max(0, $now - $this->timer);
7373

7474
return min($this->burstSize, $this->tokens + $this->rate->calculateNewTokensDuringInterval($elapsed));
7575
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ public function reserve(int $tokens = 1, float $maxTime = null): Reservation
8686

8787
// at $now + $waitDuration all tokens will be reserved for this process,
8888
// so no tokens are left for other processes.
89-
$bucket->setTokens(0);
90-
$bucket->setTimer($now + $waitDuration);
89+
$bucket->setTokens($availableTokens - $tokens);
90+
$bucket->setTimer($now);
9191

92-
$reservation = new Reservation($bucket->getTimer(), new RateLimit(0, \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->maxBurst));
92+
$reservation = new Reservation($now + $waitDuration, new RateLimit(0, \DateTimeImmutable::createFromFormat('U', floor($now + $waitDuration)), false, $this->maxBurst));
9393
}
9494

9595
$this->storage->save($bucket);

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@ public function getHitCount(): int
6363

6464
public function getAvailableTokens(float $now)
6565
{
66-
// if timer is in future, there are no tokens available anymore
67-
if ($this->timer > $now) {
68-
return 0;
69-
}
70-
7166
// if now is more than the window interval in the past, all tokens are available
7267
if (($now - $this->timer) > $this->intervalInSeconds) {
7368
return $this->maxSize;

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Bridge\PhpUnit\ClockMock;
1616
use Symfony\Component\RateLimiter\Policy\FixedWindowLimiter;
17+
use Symfony\Component\RateLimiter\Policy\Window;
1718
use Symfony\Component\RateLimiter\RateLimit;
1819
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
1920
use Symfony\Component\RateLimiter\Tests\Resources\DummyWindow;
@@ -94,9 +95,17 @@ public function testWrongWindowFromCache()
9495
$this->assertEquals(9, $rateLimit->getRemainingTokens());
9596
}
9697

97-
private function createLimiter(string $dateIntervalString = 'PT1M'): FixedWindowLimiter
98+
public function testWindowResilientToTimeShifting()
9899
{
99-
return new FixedWindowLimiter('test', 10, new \DateInterval($dateIntervalString), $this->storage);
100+
$serverOneClock = microtime(true) - 1;
101+
$serverTwoClock = microtime(true) + 1;
102+
$window = new Window('id', 300, 100, $serverTwoClock);
103+
$this->assertSame(100, $window->getAvailableTokens($serverTwoClock));
104+
$this->assertSame(100, $window->getAvailableTokens($serverOneClock));
105+
106+
$window = new Window('id', 300, 100, $serverOneClock);
107+
$this->assertSame(100, $window->getAvailableTokens($serverTwoClock));
108+
$this->assertSame(100, $window->getAvailableTokens($serverOneClock));
100109
}
101110

102111
public function provideConsumeOutsideInterval(): \Generator
@@ -111,4 +120,9 @@ public function provideConsumeOutsideInterval(): \Generator
111120

112121
yield ['P1Y'];
113122
}
123+
124+
private function createLimiter(string $dateIntervalString = 'PT1M'): FixedWindowLimiter
125+
{
126+
return new FixedWindowLimiter('test', 10, new \DateInterval($dateIntervalString), $this->storage);
127+
}
114128
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ public function testWrongWindowFromCache()
114114
$this->assertEquals(9, $rateLimit->getRemainingTokens());
115115
}
116116

117+
public function testBucketResilientToTimeShifting()
118+
{
119+
$serverOneClock = microtime(true) - 1;
120+
$serverTwoClock = microtime(true) + 1;
121+
122+
$bucket = new TokenBucket('id', 100, new Rate(\DateInterval::createFromDateString('5 minutes'), 10), $serverTwoClock);
123+
$this->assertSame(100, $bucket->getAvailableTokens($serverTwoClock));
124+
$this->assertSame(100, $bucket->getAvailableTokens($serverOneClock));
125+
126+
$bucket = new TokenBucket('id', 100, new Rate(\DateInterval::createFromDateString('5 minutes'), 10), $serverOneClock);
127+
$this->assertSame(100, $bucket->getAvailableTokens($serverTwoClock));
128+
$this->assertSame(100, $bucket->getAvailableTokens($serverOneClock));
129+
}
130+
117131
private function createLimiter($initialTokens = 10, Rate $rate = null)
118132
{
119133
return new TokenBucketLimiter('test', $initialTokens, $rate ?? Rate::perSecond(10), $this->storage);

src/Symfony/Component/Serializer/Normalizer/UidNormalizer.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\Component\Serializer\Exception\LogicException;
1616
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1717
use Symfony\Component\Uid\AbstractUid;
18-
use Symfony\Component\Uid\Ulid;
1918
use Symfony\Component\Uid\Uuid;
2019

2120
final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface
@@ -71,11 +70,17 @@ public function supportsNormalization(mixed $data, string $format = null): bool
7170
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
7271
{
7372
try {
74-
return Ulid::class === $type ? Ulid::fromString($data) : Uuid::fromString($data);
73+
return AbstractUid::class !== $type ? $type::fromString($data) : Uuid::fromString($data);
7574
} catch (\InvalidArgumentException $exception) {
7675
throw NotNormalizableValueException::createForUnexpectedDataType('The data is not a valid UUID string representation.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
7776
} catch (\TypeError $exception) {
7877
throw NotNormalizableValueException::createForUnexpectedDataType('The data is not a valid UUID string representation.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
78+
} catch (\Error $e) {
79+
if (str_starts_with($e->getMessage(), 'Cannot instantiate abstract class')) {
80+
return $this->denormalize($data, AbstractUid::class, $format, $context);
81+
}
82+
83+
throw $e;
7984
}
8085
}
8186

src/Symfony/Component/Serializer/Tests/Normalizer/UidNormalizerTest.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ public function dataProvider()
116116
['4126dbc1-488e-4f6e-aadd-775dcbac482e', UuidV4::class],
117117
['18cdf3d3-ea1b-5b23-a9c5-40abd0e2df22', UuidV5::class],
118118
['1ea6ecef-eb9a-66fe-b62b-957b45f17e43', UuidV6::class],
119-
['1ea6ecef-eb9a-66fe-b62b-957b45f17e43', AbstractUid::class],
120119
['01E4BYF64YZ97MDV6RH0HAMN6X', Ulid::class],
120+
['01FPT3YXZXJ1J437FES7CR5BCB', TestCustomUid::class],
121121
];
122122
}
123123

@@ -134,16 +134,32 @@ public function testSupportsDenormalizationForNonUid()
134134
$this->assertFalse($this->normalizer->supportsDenormalization('foo', \stdClass::class));
135135
}
136136

137+
public function testSupportOurAbstractUid()
138+
{
139+
$this->assertTrue($this->normalizer->supportsDenormalization('1ea6ecef-eb9a-66fe-b62b-957b45f17e43', AbstractUid::class));
140+
}
141+
142+
public function testSupportCustomAbstractUid()
143+
{
144+
$this->assertTrue($this->normalizer->supportsDenormalization('ccc', TestAbstractCustomUid::class));
145+
}
146+
137147
/**
138148
* @dataProvider dataProvider
139149
*/
140150
public function testDenormalize($uuidString, $class)
141151
{
142-
if (Ulid::class === $class) {
143-
$this->assertEquals(new Ulid($uuidString), $this->normalizer->denormalize($uuidString, $class));
144-
} else {
145-
$this->assertEquals(Uuid::fromString($uuidString), $this->normalizer->denormalize($uuidString, $class));
146-
}
152+
$this->assertEquals($class::fromString($uuidString), $this->normalizer->denormalize($uuidString, $class));
153+
}
154+
155+
public function testDenormalizeOurAbstractUid()
156+
{
157+
$this->assertEquals(Uuid::fromString($uuidString = '1ea6ecef-eb9a-66fe-b62b-957b45f17e43'), $this->normalizer->denormalize($uuidString, AbstractUid::class));
158+
}
159+
160+
public function testDenormalizeCustomAbstractUid()
161+
{
162+
$this->assertEquals(Uuid::fromString($uuidString = '1ea6ecef-eb9a-66fe-b62b-957b45f17e43'), $this->normalizer->denormalize($uuidString, TestAbstractCustomUid::class));
147163
}
148164

149165
public function testNormalizeWithNormalizationFormatPassedInConstructor()
@@ -169,3 +185,11 @@ public function testNormalizeWithNormalizationFormatNotValid()
169185
]);
170186
}
171187
}
188+
189+
class TestCustomUid extends Ulid
190+
{
191+
}
192+
193+
abstract class TestAbstractCustomUid extends Ulid
194+
{
195+
}

src/Symfony/Component/Validator/Constraints/Sequentially.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
2222
*/
23-
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
23+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
2424
class Sequentially extends Composite
2525
{
2626
public $constraints = [];

src/Symfony/Component/Validator/Tests/Fixtures/Annotation/Entity.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
* @Symfony\Component\Validator\Tests\Fixtures\ConstraintA
2020
* @Assert\GroupSequence({"Foo", "Entity"})
2121
* @Assert\Callback({"Symfony\Component\Validator\Tests\Fixtures\CallbackClass", "callback"})
22+
* @Assert\Sequentially({
23+
* @Assert\Expression("this.getFirstName() != null")
24+
* })
2225
*/
2326
class Entity extends EntityParent implements EntityInterfaceB
2427
{

src/Symfony/Component/Validator/Tests/Fixtures/Attribute/Entity.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
Assert\GroupSequence(['Foo', 'Entity']),
2323
Assert\Callback([CallbackClass::class, 'callback']),
2424
]
25+
/**
26+
* @Assert\Sequentially({
27+
* @Assert\Expression("this.getFirstName() != null")
28+
* })
29+
*/
2530
class Entity extends EntityParent implements EntityInterfaceB
2631
{
2732
/**

src/Symfony/Component/Validator/Tests/Fixtures/NestedAttribute/Entity.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
ConstraintA,
2323
Assert\GroupSequence(['Foo', 'Entity']),
2424
Assert\Callback([CallbackClass::class, 'callback']),
25+
Assert\Sequentially([
26+
new Assert\Expression('this.getFirstName() != null')
27+
])
2528
]
2629
class Entity extends EntityParent implements EntityInterfaceB
2730
{

src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Validator\Constraints\Choice;
2020
use Symfony\Component\Validator\Constraints\Collection;
2121
use Symfony\Component\Validator\Constraints\Email;
22+
use Symfony\Component\Validator\Constraints\Expression;
2223
use Symfony\Component\Validator\Constraints\IsTrue;
2324
use Symfony\Component\Validator\Constraints\NotBlank;
2425
use Symfony\Component\Validator\Constraints\NotNull;
@@ -65,6 +66,9 @@ public function testLoadClassMetadata(string $namespace)
6566
$expected->setGroupSequence(['Foo', 'Entity']);
6667
$expected->addConstraint(new ConstraintA());
6768
$expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback']));
69+
$expected->addConstraint(new Sequentially([
70+
new Expression('this.getFirstName() != null'),
71+
]));
6872
$expected->addConstraint(new Callback(['callback' => 'validateMe', 'payload' => 'foo']));
6973
$expected->addConstraint(new Callback('validateMeStatic'));
7074
$expected->addPropertyConstraint('firstName', new NotNull());
@@ -151,6 +155,9 @@ public function testLoadClassMetadataAndMerge(string $namespace)
151155
$expected->setGroupSequence(['Foo', 'Entity']);
152156
$expected->addConstraint(new ConstraintA());
153157
$expected->addConstraint(new Callback(['Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback']));
158+
$expected->addConstraint(new Sequentially([
159+
new Expression('this.getFirstName() != null'),
160+
]));
154161
$expected->addConstraint(new Callback(['callback' => 'validateMe', 'payload' => 'foo']));
155162
$expected->addConstraint(new Callback('validateMeStatic'));
156163
$expected->addPropertyConstraint('firstName', new NotNull());

0 commit comments

Comments
 (0)
0