Skip to content

Commit 82a12a4

Browse files
Merge branch '6.4' into 7.2
* 6.4: [WebProfilerBundle] Fix tests [Cache] Tests for Redis Replication with cache [BrowserKit] Fix submitting forms with empty file fields [WebProfilerBundle] Fix interception for non conventional redirects [DependencyInjection] Do not preload functions [DependencyInjection] Fix cloned lazy services not sharing their dependencies when dumped with PhpDumper [HttpClient] Fix activity tracking leading to negative timeout errors [Security] Return null instead of empty username to fix deprecation notice [DependencyInjection] Fix env default processor with scalar node
2 parents 6262b65 + 4897024 commit 82a12a4

31 files changed

+515
-82
lines changed

.github/workflows/integration-tests.yml

+12
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ jobs:
8181
REDIS_MASTER_HOST: redis
8282
REDIS_MASTER_SET: redis_sentinel
8383
REDIS_SENTINEL_QUORUM: 1
84+
redis-primary:
85+
image: redis:latest
86+
hostname: redis-primary
87+
ports:
88+
- 16381:6379
89+
90+
redis-replica:
91+
image: redis:latest
92+
ports:
93+
- 16382:6379
94+
command: redis-server --slaveof redis-primary 6379
8495
memcached:
8596
image: memcached:1.6.5
8697
ports:
@@ -239,6 +250,7 @@ jobs:
239250
REDIS_CLUSTER_HOSTS: 'localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005'
240251
REDIS_SENTINEL_HOSTS: 'unreachable-host:26379 localhost:26379 localhost:26379'
241252
REDIS_SENTINEL_SERVICE: redis_sentinel
253+
REDIS_REPLICATION_HOSTS: 'localhost:16381 localhost:16382'
242254
MESSENGER_REDIS_DSN: redis://127.0.0.1:7006/messages
243255
MESSENGER_AMQP_DSN: amqp://localhost/%2f/messages
244256
MESSENGER_SQS_DSN: "sqs://localhost:4566/messages?sslmode=disable&poll_timeout=0.01"

src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public function onKernelResponse(ResponseEvent $event): void
9999
return;
100100
}
101101

102-
if ($response->headers->has('X-Debug-Token') && $response->isRedirect() && $this->interceptRedirects && 'html' === $request->getRequestFormat()) {
102+
if ($response->headers->has('X-Debug-Token') && $response->isRedirect() && $this->interceptRedirects && 'html' === $request->getRequestFormat() && $response->headers->has('Location')) {
103103
if ($request->hasSession() && ($session = $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) {
104104
// keep current flashes for one more request if using AutoExpireFlashBag
105105
$session->getFlashBag()->setAll($session->getFlashBag()->peekAll());

src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public static function getInjectToolbarTests()
6363
public function testHtmlRedirectionIsIntercepted($statusCode)
6464
{
6565
$response = new Response('Some content', $statusCode);
66+
$response->headers->set('Location', 'https://example.com/');
6667
$response->headers->set('X-Debug-Token', 'xxxxxxxx');
6768
$event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response);
6869

@@ -76,6 +77,7 @@ public function testHtmlRedirectionIsIntercepted($statusCode)
7677
public function testNonHtmlRedirectionIsNotIntercepted()
7778
{
7879
$response = new Response('Some content', '301');
80+
$response->headers->set('Location', 'https://example.com/');
7981
$response->headers->set('X-Debug-Token', 'xxxxxxxx');
8082
$event = new ResponseEvent($this->createMock(Kernel::class), new Request([], [], ['_format' => 'json']), HttpKernelInterface::MAIN_REQUEST, $response);
8183

@@ -139,6 +141,7 @@ public function testToolbarIsNotInjectedOnContentDispositionAttachment()
139141
public function testToolbarIsNotInjectedOnRedirection($statusCode)
140142
{
141143
$response = new Response('<html><head></head><body></body></html>', $statusCode);
144+
$response->headers->set('Location', 'https://example.com/');
142145
$response->headers->set('X-Debug-Token', 'xxxxxxxx');
143146
$event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response);
144147

src/Symfony/Component/BrowserKit/HttpBrowser.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,15 @@ private function getUploadedFiles(array $files): array
145145
}
146146
if (!isset($file['tmp_name'])) {
147147
$uploadedFiles[$name] = $this->getUploadedFiles($file);
148+
continue;
148149
}
149-
if (isset($file['tmp_name'])) {
150-
$uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
150+
151+
if ('' === $file['tmp_name']) {
152+
$uploadedFiles[$name] = new DataPart('', '');
153+
continue;
151154
}
155+
156+
$uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
152157
}
153158

154159
return $uploadedFiles;

src/Symfony/Component/BrowserKit/Tests/HttpBrowserTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\BrowserKit\CookieJar;
1515
use Symfony\Component\BrowserKit\History;
1616
use Symfony\Component\BrowserKit\HttpBrowser;
17+
use Symfony\Component\HttpClient\MockHttpClient;
18+
use Symfony\Component\HttpClient\Response\MockResponse;
1719
use Symfony\Contracts\HttpClient\HttpClientInterface;
1820
use Symfony\Contracts\HttpClient\ResponseInterface;
1921

@@ -208,6 +210,37 @@ public static function forwardSlashesRequestPathProvider()
208210
];
209211
}
210212

213+
public function testEmptyUpload()
214+
{
215+
$client = new MockHttpClient(function ($method, $url, $options) {
216+
$this->assertSame('POST', $method);
217+
$this->assertSame('http://localhost/', $url);
218+
$this->assertStringStartsWith('Content-Type: multipart/form-data; boundary=', $options['normalized_headers']['content-type'][0]);
219+
220+
$body = '';
221+
while ('' !== $data = $options['body'](1024)) {
222+
$body .= $data;
223+
}
224+
225+
$expected = <<<EOTXT
226+
--%s\r
227+
Content-Type: application/octet-stream\r
228+
Content-Transfer-Encoding: 8bit\r
229+
Content-Disposition: form-data; name="file"; filename=""\r
230+
\r
231+
\r
232+
--%s--\r
233+
234+
EOTXT;
235+
$this->assertStringMatchesFormat($expected, $body);
236+
237+
return new MockResponse();
238+
});
239+
240+
$browser = new HttpBrowser($client);
241+
$browser->request('POST', '/', [], ['file' => ['tmp_name' => '', 'name' => 'file']]);
242+
}
243+
211244
private function uploadFile(string $data): string
212245
{
213246
$path = tempnam(sys_get_temp_dir(), 'http');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
use Symfony\Component\Cache\Adapter\RedisAdapter;
15+
16+
/**
17+
* @group integration
18+
*/
19+
class PredisRedisReplicationAdapterTest extends AbstractRedisAdapterTestCase
20+
{
21+
public static function setUpBeforeClass(): void
22+
{
23+
if (!$hosts = getenv('REDIS_REPLICATION_HOSTS')) {
24+
self::markTestSkipped('REDIS_REPLICATION_HOSTS env var is not defined.');
25+
}
26+
27+
self::$redis = RedisAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).'][alias]=master', ['class' => \Predis\Client::class, 'prefix' => 'prefix_']);
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
/**
15+
* @group integration
16+
*/
17+
class PredisReplicationAdapterTest extends AbstractRedisAdapterTestCase
18+
{
19+
public static function setUpBeforeClass(): void
20+
{
21+
parent::setUpBeforeClass();
22+
self::$redis = new \Predis\Client(array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => 6379]), ['prefix' => 'prefix_']);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
16+
17+
/**
18+
* @group integration
19+
*/
20+
class PredisTagAwareReplicationAdapterTest extends PredisReplicationAdapterTest
21+
{
22+
use TagAwareTestTrait;
23+
24+
protected function setUp(): void
25+
{
26+
parent::setUp();
27+
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
28+
}
29+
30+
public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface
31+
{
32+
$this->assertInstanceOf(\Predis\Client::class, self::$redis);
33+
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
34+
35+
return $adapter;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\Component\Cache\Adapter\AbstractAdapter;
16+
use Symfony\Component\Cache\Adapter\RedisAdapter;
17+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
18+
use Symfony\Component\Cache\Traits\RedisClusterProxy;
19+
20+
/**
21+
* @group integration
22+
*/
23+
class RedisReplicationAdapterTest extends AbstractRedisAdapterTestCase
24+
{
25+
public static function setUpBeforeClass(): void
26+
{
27+
if (!$hosts = getenv('REDIS_REPLICATION_HOSTS')) {
28+
self::markTestSkipped('REDIS_REPLICATION_HOSTS env var is not defined.');
29+
}
30+
31+
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).'][alias]=master', ['lazy' => true]);
32+
self::$redis->setOption(\Redis::OPT_PREFIX, 'prefix_');
33+
}
34+
35+
public function createCachePool(int $defaultLifetime = 0, ?string $testMethod = null): CacheItemPoolInterface
36+
{
37+
if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) {
38+
self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX);
39+
}
40+
41+
$this->assertInstanceOf(RedisClusterProxy::class, self::$redis);
42+
$adapter = new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
43+
44+
return $adapter;
45+
}
46+
47+
/**
48+
* @dataProvider provideFailedCreateConnection
49+
*/
50+
public function testFailedCreateConnection(string $dsn)
51+
{
52+
$this->expectException(InvalidArgumentException::class);
53+
$this->expectExceptionMessage('Redis connection ');
54+
RedisAdapter::createConnection($dsn);
55+
}
56+
57+
public static function provideFailedCreateConnection(): array
58+
{
59+
return [
60+
['redis://localhost:1234'],
61+
['redis://foo@localhost?role=master'],
62+
['redis://localhost/123?role=master'],
63+
];
64+
}
65+
}

src/Symfony/Component/Cache/Traits/RedisTrait.php

+11-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Predis\Connection\Aggregate\ReplicationInterface;
1818
use Predis\Connection\Cluster\ClusterInterface as Predis2ClusterInterface;
1919
use Predis\Connection\Cluster\RedisCluster as Predis2RedisCluster;
20+
use Predis\Connection\Replication\ReplicationInterface as Predis2ReplicationInterface;
2021
use Predis\Response\ErrorInterface;
2122
use Predis\Response\Status;
2223
use Relay\Relay;
@@ -479,9 +480,16 @@ protected function doClear(string $namespace): bool
479480
$cleared = true;
480481
$hosts = $this->getHosts();
481482
$host = reset($hosts);
482-
if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
483-
// Predis supports info command only on the master in replication environments
484-
$hosts = [$host->getClientFor('master')];
483+
if ($host instanceof \Predis\Client) {
484+
$connection = $host->getConnection();
485+
486+
if ($connection instanceof ReplicationInterface) {
487+
$hosts = [$host->getClientFor('master')];
488+
} elseif ($connection instanceof Predis2ReplicationInterface) {
489+
$connection->switchToMaster();
490+
491+
$hosts = [$host];
492+
}
485493
}
486494

487495
foreach ($hosts as $host) {

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ private function isInlineableDefinition(string $id, Definition $definition): boo
223223
return false;
224224
}
225225

226-
return $this->container->getDefinition($srcId)->isShared();
226+
$srcDefinition = $this->container->getDefinition($srcId);
227+
228+
return $srcDefinition->isShared() && !$srcDefinition->isLazy();
227229
}
228230
}

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

+48-11
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,8 @@ public function process(ContainerBuilder $container): void
4646
$defaultBag = new ParameterBag($resolvingBag->all());
4747
$envTypes = $resolvingBag->getProvidedTypes();
4848
foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) {
49-
$values = [];
50-
if (false === $i = strpos($env, ':')) {
51-
$default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string'];
52-
$defaultType = null !== $default ? get_debug_type($default) : 'string';
53-
$values[$defaultType] = $default;
54-
} else {
55-
$prefix = substr($env, 0, $i);
56-
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
57-
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
58-
}
59-
}
49+
$values = $this->getPlaceholderValues($env, $defaultBag, $envTypes);
50+
6051
foreach ($placeholders as $placeholder) {
6152
BaseNode::setPlaceholder($placeholder, $values);
6253
}
@@ -97,4 +88,50 @@ public function getExtensionConfig(): array
9788
$this->extensionConfig = [];
9889
}
9990
}
91+
92+
/**
93+
* @param array<string, list<string>> $envTypes
94+
*
95+
* @return array<string, mixed>
96+
*/
97+
private function getPlaceholderValues(string $env, ParameterBag $defaultBag, array $envTypes): array
98+
{
99+
if (false === $i = strpos($env, ':')) {
100+
[$default, $defaultType] = $this->getParameterDefaultAndDefaultType("env($env)", $defaultBag);
101+
102+
return [$defaultType => $default];
103+
}
104+
105+
$prefix = substr($env, 0, $i);
106+
if ('default' === $prefix) {
107+
$parts = explode(':', $env);
108+
array_shift($parts); // Remove 'default' prefix
109+
$parameter = array_shift($parts); // Retrieve and remove parameter
110+
111+
[$defaultParameter, $defaultParameterType] = $this->getParameterDefaultAndDefaultType($parameter, $defaultBag);
112+
113+
return [
114+
$defaultParameterType => $defaultParameter,
115+
...$this->getPlaceholderValues(implode(':', $parts), $defaultBag, $envTypes),
116+
];
117+
}
118+
119+
$values = [];
120+
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
121+
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
122+
}
123+
124+
return $values;
125+
}
126+
127+
/**
128+
* @return array{0: string, 1: string}
129+
*/
130+
private function getParameterDefaultAndDefaultType(string $name, ParameterBag $defaultBag): array
131+
{
132+
$default = $defaultBag->has($name) ? $defaultBag->get($name) : self::TYPE_FIXTURES['string'];
133+
$defaultType = null !== $default ? get_debug_type($default) : 'string';
134+
135+
return [$default, $defaultType];
136+
}
100137
}

0 commit comments

Comments
 (0)