8000 Merge branch '5.4' into 6.0 · symfony/symfony@4e69ecd · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 4e69ecd

Browse files
Merge branch '5.4' into 6.0
* 5.4: [HttpClient] fix RetryableHttpClient when a response is canceled Deprecate passing null as $requestIp to IpUtils::checkIp(), checkIp4() and checkIp6() [Uid] fix 4 missing bits of entropy in UUIDv4 Add a warning in WDT if using symfony/symfony [Notifier][Twilio] Ensure from/sender is valid via regex Lower log level in case of retry GuardEvent::getTransition() cannot return null [String] Add `trimSuffix()` and `trimPrefix()` methods [DependencyInjection] autowire union and intersection types [Runtime] Drop class validation of composer "extra.runtime.class"
2 parents a5b810b + 7a7240f commit 4e69ecd

31 files changed

+484
-64
lines changed

UPGRADE-5.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ HttpKernel
3939
HttpFoundation
4040
--------------
4141

42+
* Deprecate passing `null` as `$requestIp` to `IpUtils::checkIp()`, `IpUtils::checkIp4()` or `IpUtils::checkIp6()`, pass an empty string instead.
4243
* Mark `Request::get()` internal, use explicit input sources instead
4344
* Deprecate `upload_progress.*` and `url_rewriter.tags` session options
4445

src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\WebProfilerBundle\Controller;
1313

14+
use Symfony\Bundle\FullStack;
1415
use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler;
1516
use Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager;
1617
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -152,6 +153,7 @@ public function toolbarAction(Request $request, string $token = null): Response
152153
}
153154

154155
return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/toolbar.html.twig', [
156+
'full_stack' => class_exists(FullStack::class),
155157
'request' => $request,
156158
'profile' => $profile,
157159
'templates' => $this->getTemplateManager()->getNames($profile),

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,11 @@ div.sf-toolbar .sf-toolbar-block a:hover {
545545
margin-right: 10px;
546546
}
547547

548+
.sf-full-stack {
549+
left: 0px;
550+
font-size: 12px;
551+
}
552+
548553
/***** Media query print: Do not print the Toolbar. *****/
549554
@media print {
550555
.sf-toolbar {

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.html.twig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@
2222
{% endwith %}
2323
{% endif %}
2424
{% endfor %}
25+
{% if full_stack %}
26+
<div class="sf-full-stack sf-toolbar-block sf-toolbar-block-full-stack sf-toolbar-status-red sf-toolbar-block-right">
27+
<div class="sf-toolbar-icon">
28+
<span class="sf-toolbar-value">Using symfony/symfony is NOT supported</span>
29+
</div>
30+
<div class="sf-toolbar-info sf-toolbar-status-red">
31+
<p>This project is using Symfony via the "symfony/symfony" package.</p>
32+
<p>This is NOT supported anymore since Symfony 4.0.</p>
33+
<p>Even if it seems to work well, it has some important limitations with no workarounds.</p>
34+
<p>Using this package also makes your project slower.</p>
35+
36+
<strong>Please, stop using this package and replace it with individual packages instead.</strong>
37+
</div>
38+
<div></div>
39+
</div>
40+
{% endif %}
2541

2642
<button class="hide-button" type="button" id="sfToolbarHideButton-{{ token }}" title="Close Toolbar" accesskey="D" aria-expanded="true" aria-controls="sfToolbarMainContent-{{ token }}">
2743
{{ include('@WebProfiler/Icon/close.svg') }}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ CHANGELOG
1616
* Add `service_closure()` to the PHP-DSL
1717
* Add support for autoconfigurable attributes on methods, properties and parameters
1818
* Make auto-aliases private by default
19+
* Add support for autowiring union and intersection types
1920

2021
5.3
2122
---

src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class AutowirePass extends AbstractRecursivePass
4545
private ?int $decoratedMethodIndex = null;
4646
private ?int $decoratedMethodArgumentIndex = null;
4747
private ?self $typesClone = null;
48+
private array $combinedAliases;
4849

4950
public function __construct(bool $throwOnAutowireException = true)
5051
{
@@ -60,6 +61,8 @@ public function __construct(bool $throwOnAutowireException = true)
6061
*/
6162
public function process(ContainerBuilder $container)
6263
{
64+
$this->populateCombinedAliases($container);
65+
6366
try {
6467
$this->typesClone = clone $this;
6568
parent::process($container);
@@ -72,6 +75,7 @@ public function process(ContainerBuilder $container)
7275
$this->decoratedMethodIndex = null;
7376
$this->decoratedMethodArgumentIndex = null;
7477
$this->typesClone = null;
78+
$this->combinedAliases = [ F438 ];
7579
}
7680
}
7781

@@ -358,8 +362,12 @@ private function getAutowiredReference(TypedReference $reference): ?TypedReferen
358362
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
359363
}
360364

365+
if (null !== ($alias = $this->combinedAliases[$alias] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
366+
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
367+
}
368+
361369
if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) {
362-
foreach ($this->container->getAliases() as $id => $alias) {
370+
foreach ($this->container->getAliases() + $this->combinedAliases as $id => $alias) {
363371
if ($name === (string) $alias && str_starts_with($id, $type.' $')) {
364372
return new TypedReference($name, $type, $reference->getInvalidBehavior());
365373
}
@@ -371,6 +379,10 @@ private function getAutowiredReference(TypedReference $reference): ?TypedReferen
371379
return new TypedReference($type, $type, $reference->getInvalidBehavior());
372380
}
373381

382+
if (null !== ($alias = $this->combinedAliases[$type] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
383+
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
384+
}
385+
374386
return null;
375387
}
376388

@@ -560,4 +572,45 @@ private function populateAutowiringAlias(string $id): void
560572
$this->autowiringAliases[$type][$name] = $name;
561573
}
562574
}
575+
576+
private function populateCombinedAliases(ContainerBuilder $container): void
577+
{
578+
$this->combinedAliases = [];
579+
$reverseAliases = [];
580+
581+
foreach ($container->getAliases() as $id => $alias) {
582+
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) {
583+
continue;
584+
}
585+
586+
$type = $m[2];
587+
$name = $m[3] ?? '';
588+
$reverseAliases[(string) $alias][$name][] = $type;
589+
}
590+
591+
foreach ($reverseAliases as $alias => $names) {
592+
foreach ($names as $name => $types) {
593+
if (2 > $count = \count($types)) {
594+
continue;
595+
}
596+
sort($types);
597+
$i = 1 << $count;
598+
599+
// compute the powerset of the list of types
600+
while ($i--) {
601+
$set = [];
602+
for ($j = 0; $j < $count; ++$j) {
603+
if ($i & (1 << $j)) {
604+
$set[] = $types[$j];
605+
}
606+
}
607+
608+
if (2 <= \count($set)) {
609+
$this->combinedAliases[implode('&', $set).('' === $name ? '' : ' $'.$name)] = $alias;
610+
$this->combinedAliases[implode('|', $set).('' === $name ? '' : ' $'.$name)] = $alias;
611+
}
612+
}
613+
}
614+
}
615+
}
563616
}

src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionPa
7070
}
7171
}
7272

73+
sort($types);
74+
7375
return $types ? implode($glue, $types) : null;
7476
}
7577
}

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,28 @@ public function testTypeNotGuessableUnionType()
254254
$pass->process($container);
255255
}
256256

257+
public function testGuessableUnionType()
258+
{
259+
$container = new ContainerBuilder();
260+
261+
$container->register('b', \stcClass::class);
262+
$container->setAlias(CollisionA::class.' $collision', 'b');
263+
$container->setAlias(CollisionB::class.' $collision', 'b');
264+
265+
$aDefinition = $container->register('a', UnionClasses::class);
266+
$aDefinition->setAutowired(true);
267+
268+
$pass = new AutowirePass();
269+
$pass->process($container);
270+
271+
$this->assertSame('b', (string) $aDefinition->getArgument(0));
272+
}
273+
257274
/**
258275
* @requires PHP 8.1
259276
*/
260277
public function testTypeNotGuessableIntersectionType()
261278
{
262-
$this->expectException(AutowiringFailedException::class);
263-
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\IntersectionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface&Symfony\Component\DependencyInjection\Tests\Compiler\AnotherInterface" but this class was not found.');
264279
$container = new ContainerBuilder();
265280

266281
$container->register(CollisionInterface::class);
@@ -269,8 +284,32 @@ public function testTypeNotGuessableIntersectionType()
269284
$aDefinition = $container->register('a', IntersectionClasses::class);
270285
$aDefinition->setAutowired(true);
271286

287+
$pass = new AutowirePass();
288+
289+
$this->expectException(AutowiringFailedException::class);
290+
$this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\IntersectionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\AnotherInterface&Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" but this class was not found.');
291+
$pass->process($container);
292+
}
293+
294+
/**
295+
* @requires PHP 8.1
296+
*/
297+
public function testGuessableIntersectionType()
298+
{
299+
$container = new ContainerBuilder();
300+
301+
$container->register('b', \stcClass::class);
302+
$container->setAlias(CollisionInterface::class, 'b');
303+
$container->setAlias(AnotherInterface::class, 'b');
304+
$container->setAlias(DummyInterface::class, 'b');
305+
306+
$aDefinition = $container->register('a', IntersectionClasses::class);
307+
$aDefinition->setAutowired(true);
308+
272309
$pass = new AutowirePass();
273310
$pass->process($container);
311+
312+
$this->assertSame('b', (string) $aDefinition->getArgument(0));
274313
}
275314

276315
public function testTypeNotGuessableWithTypeSet()
@@ -503,7 +542,7 @@ public function testScalarArgsCannotBeAutowired()
503542
public function testUnionScalarArgsCannotBeAutowired()
504543
{
505544
$this->expectException(AutowiringFailedException::class);
506-
$this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" is type-hinted "int|float", you should configure its value explicitly.');
545+
$this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" is type-hinted "float|int", you should configure its value explicitly.');
507546
$container = new ContainerBuilder();
508547

509548
$container->register('union_scalars', UnionScalars::class)

src/Symfony/Component/HttpClient/EventSourceHttpClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function request(string $method, string $url, array $options = []): Respo
7878
try {
7979
$isTimeout = $chunk->isTimeout();
8080

81-
if (null !== $chunk->getInformationalStatus()) {
81+
if (null !== $chunk->getInformationalStatus() || $context->getInfo('canceled')) {
8282
yield $chunk;
8383

8484
return;

src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function request(string $method, string $url, array $options = []): Respo
7676

7777
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastPrimaryIp): void {
7878
if ($info['primary_ip'] !== $lastPrimaryIp) {
79-
if (IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) {
79+
if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) {
8080
throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url']));
8181
}
8282

src/Symfony/Component/HttpClient/Response/AsyncResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public static function stream(iterable $responses, float $timeout = null, string
247247
}
248248
}
249249

250-
if (!$client) {
250+
if (!$client || !$wrappedResponses) {
251251
return;
252252
}
253253

src/Symfony/Component/HttpClient/Response/MockResponse.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ public function cancel(): void
105105
{
106106
$this->info['canceled'] = true;
107107
$this->info['error'] = 'Response has been canceled.';
108-
unset($this->body);
108+
try {
109+
unset($this->body);
110+
} catch (TransportException $e) {
111+
// ignore errors when canceling
112+
}
109113
}
110114

111115
/**

src/Symfony/Component/HttpClient/RetryableHttpClient.php

Lines changed: 24 additions & 18 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function request(string $method, string $url, array $options = []): Respo
5959
return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$retryCount, &$content, &$firstChunk) {
6060
$exception = null;
6161
try {
62-
if ($chunk->isTimeout() || null !== $chunk->getInformationalStatus()) {
62+
if ($chunk->isTimeout() || null !== $chunk->getInformationalStatus() || $context->getInfo('canceled')) {
6363
yield $chunk;
6464

6565
return;
@@ -76,23 +76,14 @@ public function request(string $method, string $url, array $options = []): Respo
7676
}
7777

7878
if (false === $shouldRetry) {
79-
$context->passthru();
80-
if (null !== $firstChunk) {
81-
yield $firstChunk;
82-
yield $context->createChunk($content);
83-
yield $chunk;
84-
} else {
85-
yield $chunk;
86-
}
87-
$content = '';
79+
yield from $this->passthru($context, $firstChunk, $content, $chunk);
8880

8981
return;
9082
}
9183
}
9284
} elseif ($chunk->isFirst()) {
9385
if (false === $shouldRetry = $this->strategy->shouldRetry($context, null, null)) {
94-
$context->passthru();
95-
yield $chunk;
86+
yield from $this->passthru($context, $firstChunk, $content, $chunk);
9687

9788
return;
9889
}
@@ -105,9 +96,9 @@ public function request(string $method, string $url, array $options = []): Respo
10596
return;
10697
}
10798
} else {
108-
$content .= $chunk->getContent();
109-
11099
if (!$chunk->isLast()) {
100+
$content .= $chunk->getContent();
101+
111102
return;
112103
}
113104

@@ -116,10 +107,7 @@ public function request(string $method, string $url, array $options = []): Respo
116107
}
117108

118109
if (false === $shouldRetry) {
119-
$context->passthru();
120-
yield $firstChunk;
121-
yield $context->createChunk($content);
122-
$content = '';
110+
yield from $this->passthru($context, $firstChunk, $content, $chunk);
123111

124112
return;
125113
}
@@ -159,4 +147,22 @@ private function getDelayFromHeader(array $headers): ?int
159147

160148
return null;
161149
}
150+
151+
private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, string &$content, ChunkInterface $lastChunk): \Generator
152+
{
153+
$context->passthru();
154+
155+
if (null !== $firstChunk) {
156+
yield $firstChunk;
157+
}
158+
159+
if ('' !== $content) {
160+
$chunk = $context->createChunk($content);
161+
$content = '';
162+
163+
yield $chunk;
164+
}
165+
166+
yield $lastChunk;
167+
}
162168
}

src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPUnit\Framework\TestCase;
66
use Symfony\Component\HttpClient\Exception\ServerException;
7+
use Symfony\Component\HttpClient\HttpClient;
78
use Symfony\Component\HttpClient\MockHttpClient;
89
use Symfony\Component\HttpClient\NativeHttpClient;
910
use Symfony\Component\HttpClient\Response\AsyncContext;
@@ -159,4 +160,22 @@ public function shouldRetry(AsyncContext $context, ?string $responseContent, ?Tr
159160
$this->assertCount(2, $logger->logs);
160161
$this->assertSame('Try #{count} after {delay}ms: Could not resolve host "does.not.exists".', $logger->logs[0]);
161162
}
163+
164+
public function testCancelOnTimeout()
165+
{
166+
$client = HttpClient::create();
167+
168+
if ($client instanceof NativeHttpClient) {
169+
$this->markTestSkipped('NativeHttpClient cannot timeout before receiving headers');
170+
}
171+
172+
$client = new RetryableHttpClient($client);
173+
174+
$response = $client->request('GET', 'https://example.com/');
175+
176+
foreach ($client->stream($response, 0) as $chunk) {
177+
$this->assertTrue($chunk->isTimeout());
178+
$response->cancel();
179+
}
180+
}
162181
}

0 commit comments

Comments
 (0)
0