diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig
index 819711eff7..983a6f39f0 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/mailer.html.twig
@@ -94,7 +94,7 @@
{% for transport in events.transports %}
{{ events.messages(transport)|length }}
- {{ events.messages(transport)|length == 1 ? 'message' : 'messages' }}
+ {{ events.messages(transport)|length == 1 ? 'message' : 'messages' }}
{% endfor %}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php
index da909ae489..7e76064ce6 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php
@@ -78,8 +78,18 @@ class DecoratorServicePass implements CompilerPassInterface
if (isset($decoratingDefinitions[$inner])) {
$decoratingDefinition = $decoratingDefinitions[$inner];
- $definition->setTags(array_merge($decoratingDefinition->getTags(), $definition->getTags()));
- $decoratingDefinition->setTags([]);
+
+ $decoratingTags = $decoratingDefinition->getTags();
+ $resetTags = [];
+
+ if (isset($decoratingTags['container.service_locator'])) {
+ // container.service_locator has special logic and it must not be transferred out to decorators
+ $resetTags = ['container.service_locator' => $decoratingTags['container.service_locator']];
+ unset($decoratingTags['container.service_locator']);
+ }
+
+ $definition->setTags(array_merge($decoratingTags, $definition->getTags()));
+ $decoratingDefinition->setTags($resetTags);
$decoratingDefinitions[$inner] = $definition;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php
index fbd19ab3f4..ed111d6d2c 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php
@@ -223,6 +223,25 @@ class DecoratorServicePassTest extends TestCase
$this->assertEquals(['bar' => ['attr' => 'baz']], $container->getDefinition('deco2')->getTags());
}
+ public function testProcessLeavesServiceLocatorTagOnOriginalDefinition()
+ {
+ $container = new ContainerBuilder();
+ $container
+ ->register('foo')
+ ->setTags(['container.service_locator' => [0 => []], 'bar' => ['attr' => 'baz']])
+ ;
+ $container
+ ->register('baz')
+ ->setTags(['foobar' => ['attr' => 'bar']])
+ ->setDecoratedService('foo')
+ ;
+
+ $this->process($container);
+
+ $this->assertEquals(['container.service_locator' => [0 => []]], $container->getDefinition('baz.inner')->getTags());
+ $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar']], $container->getDefinition('baz')->getTags());
+ }
+
protected function process(ContainerBuilder $container)
{
$pass = new DecoratorServicePass();
diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php
index a36372c619..b076e9a7b5 100644
--- a/src/Symfony/Component/HttpClient/CurlHttpClient.php
+++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php
@@ -116,7 +116,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$url = implode('', $url);
if (!isset($options['normalized_headers']['user-agent'])) {
- $options['normalized_headers']['user-agent'][] = $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl';
+ $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl';
}
$curlopts = [
@@ -217,8 +217,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$curlopts[CURLOPT_NOSIGNAL] = true;
}
- if (!isset($options['normalized_headers']['accept-encoding']) && CURL_VERSION_LIBZ & self::$curlVersion['features']) {
- $curlopts[CURLOPT_ENCODING] = 'gzip'; // Expose only one encoding, some servers mess up when more are provided
+ if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) {
+ $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided
}
foreach ($options['headers'] as $header) {
diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php
index 9e95d1170e..d60f541527 100644
--- a/src/Symfony/Component/HttpClient/NativeHttpClient.php
+++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php
@@ -77,7 +77,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$options['headers'][] = 'Content-Type: application/x-www-form-urlencoded';
}
- if ($gzipEnabled = \extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) {
+ if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) {
// gzip is the most widely available algo, no need to deal with deflate
$options['headers'][] = 'Accept-Encoding: gzip';
}
@@ -227,7 +227,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$context = stream_context_create($context, ['notification' => $notification]);
self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, $noProxy);
- return new NativeResponse($this->multi, $context, implode('', $url), $options, $gzipEnabled, $info, $resolveRedirect, $onProgress, $this->logger);
+ return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolveRedirect, $onProgress, $this->logger);
}
/**
diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php
index 6188587288..e1699b9b2b 100644
--- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php
@@ -52,6 +52,7 @@ final class CurlResponse implements ResponseInterface
$this->id = $id = (int) $ch;
$this->logger = $logger;
+ $this->shouldBuffer = $options['buffer'] ?? true;
$this->timeout = $options['timeout'] ?? null;
$this->info['http_method'] = $method;
$this->info['user_data'] = $options['user_data'] ?? null;
@@ -65,50 +66,25 @@ final class CurlResponse implements ResponseInterface
curl_setopt($ch, CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter
}
- if (null === $content = &$this->content) {
- $content = null === $options || true === $options['buffer'] ? fopen('php://temp', 'w+') : (\is_resource($options['buffer']) ? $options['buffer'] : null);
- } else {
- // Move the pushed response to the activity list
- $buffer = $options['buffer'];
-
- if ('H' !== curl_getinfo($ch, CURLINFO_PRIVATE)[0]) {
- if ($options['buffer'] instanceof \Closure) {
- try {
- [$content, $buffer] = [null, $content];
- [$content, $buffer] = [$buffer, $options['buffer']($headers)];
- } catch (\Throwable $e) {
- $multi->handlesActivity[$id][] = null;
- $multi->handlesActivity[$id][] = $e;
- [$content, $buffer] = [$buffer, false];
- }
- }
-
- if (ftell($content)) {
- rewind($content);
- $multi->handlesActivity[$id][] = stream_get_contents($content);
- }
- }
-
- if (\is_resource($buffer)) {
- $content = $buffer;
- } elseif (true !== $buffer) {
- $content = null;
- }
- }
-
- curl_setopt($ch, CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger, &$content): int {
- return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger, $content);
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int {
+ return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
});
if (null === $options) {
// Pushed response: buffer until requested
- curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use (&$content): int {
- return fwrite($content, $data);
+ curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
+ $multi->handlesActivity[$id][] = $data;
+ curl_pause($ch, CURLPAUSE_RECV);
+
+ return \strlen($data);
});
return;
}
+ $this->inflate = !isset($options['normalized_headers']['accept-encoding']);
+ curl_pause($ch, CURLPAUSE_CONT);
+
if ($onProgress = $options['on_progress']) {
$url = isset($info['url']) ? ['url' => $info['url']] : [];
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
@@ -128,33 +104,16 @@ final class CurlResponse implements ResponseInterface
});
}
- curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use (&$content, $multi, $id): int {
+ curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
$multi->handlesActivity[$id][] = $data;
- return null !== $content ? fwrite($content, $data) : \strlen($data);
+ return \strlen($data);
});
$this->initializer = static function (self $response) {
- if (null !== $response->info['error']) {
- throw new TransportException($response->info['error']);
- }
-
$waitFor = curl_getinfo($ch = $response->handle, CURLINFO_PRIVATE);
- if ('H' === $waitFor[0] || 'D' === $waitFor[0]) {
- try {
- foreach (self::stream([$response]) as $chunk) {
- if ($chunk->isFirst()) {
- break;
- }
- }
- } catch (\Throwable $e) {
- // Persist timeouts thrown during initialization
- $response->info['error'] = $e->getMessage();
- $response->close();
- throw $e;
- }
- }
+ return 'H' === $waitFor[0] || 'D' === $waitFor[0];
};
// Schedule the request in a non-blocking way
@@ -241,6 +200,7 @@ final class CurlResponse implements ResponseInterface
*/
private function close(): void
{
+ $this->inflate = null;
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
curl_setopt($this->handle, CURLOPT_PRIVATE, '_0');
@@ -419,22 +379,6 @@ final class CurlResponse implements ResponseInterface
}
curl_setopt($ch, CURLOPT_PRIVATE, $waitFor);
-
- try {
- if (!$content && $options['buffer'] instanceof \Closure && $content = $options['buffer']($headers) ?: null) {
- $content = \is_resource($content) ? $content : fopen('php://temp', 'w+');
- }
-
- if (null !== $info['error']) {
- throw new TransportException($info['error']);
- }
- } catch (\Throwable $e) {
- $multi->handlesActivity[$id] = $multi->handlesActivity[$id] ?? [new FirstChunk()];
- $multi->handlesActivity[$id][] = null;
- $multi->handlesActivity[$id][] = $e;
-
- return 0;
- }
} elseif (null !== $info['redirect_url'] && $logger) {
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));
}
diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php
index a420914958..b458665d44 100644
--- a/src/Symfony/Component/HttpClient/Response/MockResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php
@@ -94,6 +94,7 @@ class MockResponse implements ResponseInterface
*/
protected function close(): void
{
+ $this->inflate = null;
$this->body = [];
}
@@ -105,22 +106,9 @@ class MockResponse implements ResponseInterface
$response = new self([]);
$response->requestOptions = $options;
$response->id = ++self::$idSequence;
-
- if (!($options['buffer'] ?? null) instanceof \Closure) {
- $response->content = true === ($options['buffer'] ?? true) ? fopen('php://temp', 'w+') : (\is_resource($options['buffer']) ? $options['buffer'] : null);
- }
+ $response->shouldBuffer = $options['buffer'] ?? true;
$response->initializer = static function (self $response) {
- if (null !== $response->info['error']) {
- throw new TransportException($response->info['error']);
- }
-
- if (\is_array($response->body[0] ?? null)) {
- foreach (self::stream([$response]) as $chunk) {
- if ($chunk->isFirst()) {
- break;
- }
- }
- }
+ return \is_array($response->body[0] ?? null);
};
$response->info['redirect_count'] = 0;
@@ -198,11 +186,6 @@ class MockResponse implements ResponseInterface
} else {
// Data or timeout chunk
$multi->handlesActivity[$id][] = $chunk;
-
- if (\is_string($chunk) && null !== $response->content) {
- // Buffer response body
- fwrite($response->content, $chunk);
- }
}
}
}
diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php
index db0540d28b..7abe18abab 100644
--- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php
@@ -32,7 +32,6 @@ final class NativeResponse implements ResponseInterface
private $onProgress;
private $remaining;
private $buffer;
- private $inflate;
private $multi;
private $debugBuffer;
private $shouldBuffer;
@@ -40,7 +39,7 @@ final class NativeResponse implements ResponseInterface
/**
* @internal
*/
- public function __construct(NativeClientState $multi, $context, string $url, $options, bool $gzipEnabled, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger)
+ public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger)
{
$this->multi = $multi;
$this->id = (int) $context;
@@ -51,28 +50,17 @@ final class NativeResponse implements ResponseInterface
$this->info = &$info;
$this->resolveRedirect = $resolveRedirect;
$this->onProgress = $onProgress;
- $this->content = true === $options['buffer'] ? fopen('php://temp', 'w+') : (\is_resource($options['buffer']) ? $options['buffer'] : null);
- $this->shouldBuffer = $options['buffer'] instanceof \Closure ? $options['buffer'] : null;
+ $this->inflate = !isset($options['normalized_headers']['accept-encoding']);
+ $this->shouldBuffer = $options['buffer'] ?? true;
- // Temporary resources to dechunk/inflate the response stream
+ // Temporary resource to dechunk the response stream
$this->buffer = fopen('php://temp', 'w+');
- $this->inflate = $gzipEnabled ? inflate_init(ZLIB_ENCODING_GZIP) : null;
$info['user_data'] = $options['user_data'];
++$multi->responseCount;
$this->initializer = static function (self $response) {
- if (null !== $response->info['error']) {
- throw new TransportException($response->info['error']);
- }
-
- if (null === $response->remaining) {
- foreach (self::stream([$response]) as $chunk) {
- if ($chunk->isFirst()) {
- break;
- }
- }
- }
+ return null === $response->remaining;
};
}
@@ -169,7 +157,7 @@ final class NativeResponse implements ResponseInterface
stream_set_blocking($h, false);
$this->context = $this->resolveRedirect = null;
- // Create dechunk and inflate buffers
+ // Create dechunk buffers
if (isset($this->headers['content-length'])) {
$this->remaining = (int) $this->headers['content-length'][0];
} elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) {
@@ -179,27 +167,6 @@ final class NativeResponse implements ResponseInterface
$this->remaining = -2;
}
- if ($this->inflate && 'gzip' !== ($this->headers['content-encoding'][0] ?? null)) {
- $this->inflate = null;
- }
-
- try {
- if (null !== $this->shouldBuffer && null === $this->content && $this->content = ($this->shouldBuffer)($this->headers) ?: null) {
- $this->content = \is_resource($this->content) ? $this->content : fopen('php://temp', 'w+');
- }
-
- if (null !== $this->info['error']) {
- throw new TransportException($this->info['error']);
- }
- } catch (\Throwable $e) {
- $this->close();
- $this->multi->handlesActivity[$this->id] = [new FirstChunk()];
- $this->multi->handlesActivity[$this->id][] = null;
- $this->multi->handlesActivity[$this->id][] = $e;
-
- return;
- }
-
$this->multi->handlesActivity[$this->id] = [new FirstChunk()];
if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) {
@@ -209,7 +176,7 @@ final class NativeResponse implements ResponseInterface
return;
}
- $this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->inflate, $this->content, $this->onProgress, &$this->remaining, &$this->info];
+ $this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info];
}
/**
@@ -249,15 +216,15 @@ final class NativeResponse implements ResponseInterface
$multi->handles = [];
}
- foreach ($multi->openHandles as $i => [$h, $buffer, $inflate, $content, $onProgress]) {
+ foreach ($multi->openHandles as $i => [$h, $buffer, $onProgress]) {
$hasActivity = false;
- $remaining = &$multi->openHandles[$i][5];
- $info = &$multi->openHandles[$i][6];
+ $remaining = &$multi->openHandles[$i][3];
+ $info = &$multi->openHandles[$i][4];
$e = null;
// Read incoming buffer and write it to the dechunk one
try {
- while ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) {
+ if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) {
fwrite($buffer, $data);
$hasActivity = true;
$multi->sleep = false;
@@ -285,16 +252,8 @@ final class NativeResponse implements ResponseInterface
rewind($buffer);
ftruncate($buffer, 0);
- if (null !== $inflate && false === $data = @inflate_add($inflate, $data)) {
- $e = new TransportException('Error while processing content unencoding.');
- }
-
- if ('' !== $data && null === $e) {
+ if (null === $e) {
$multi->handlesActivity[$i][] = $data;
-
- if (null !== $content && \strlen($data) !== fwrite($content, $data)) {
- $e = new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($data)));
- }
}
}
diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
index 71a04f1e8a..1222a4b1e9 100644
--- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
+++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
@@ -43,11 +43,6 @@ trait ResponseTrait
*/
private $initializer;
- /**
- * @var resource A php://temp stream typically
- */
- private $content;
-
private $info = [
'response_headers' => [],
'http_code' => 0,
@@ -59,6 +54,9 @@ trait ResponseTrait
private $handle;
private $id;
private $timeout = 0;
+ private $inflate;
+ private $shouldBuffer;
+ private $content;
private $finalInfo;
private $offset = 0;
private $jsonData;
@@ -69,8 +67,7 @@ trait ResponseTrait
public function getStatusCode(): int
{
if ($this->initializer) {
- ($this->initializer)($this);
- $this->initializer = null;
+ self::initialize($this);
}
return $this->info['http_code'];
@@ -82,8 +79,7 @@ trait ResponseTrait
public function getHeaders(bool $throw = true): array
{
if ($this->initializer) {
- ($this->initializer)($this);
- $this->initializer = null;
+ self::initialize($this);
}
if ($throw) {
@@ -99,8 +95,7 @@ trait ResponseTrait
public function getContent(bool $throw = true): string
{
if ($this->initializer) {
- ($this->initializer)($this);
- $this->initializer = null;
+ self::initialize($this);
}
if ($throw) {
@@ -231,6 +226,30 @@ trait ResponseTrait
*/
abstract protected static function select(ClientState $multi, float $timeout): int;
+ private static function initialize(self $response): void
+ {
+ if (null !== $response->info['error']) {
+ throw new TransportException($response->info['error']);
+ }
+
+ try {
+ if (($response->initializer)($response)) {
+ foreach (self::stream([$response]) as $chunk) {
+ if ($chunk->isFirst()) {
+ break;
+ }
+ }
+ }
+ } catch (\Throwable $e) {
+ // Persist timeouts thrown during initialization
+ $response->info['error'] = $e->getMessage();
+ $response->close();
+ throw $e;
+ }
+
+ $response->initializer = null;
+ }
+
private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void
{
foreach ($responseHeaders as $h) {
@@ -276,8 +295,7 @@ trait ResponseTrait
private function doDestruct()
{
if ($this->initializer && null === $this->info['error']) {
- ($this->initializer)($this);
- $this->initializer = null;
+ self::initialize($this);
$this->checkStatusCode();
}
}
@@ -329,6 +347,16 @@ trait ResponseTrait
$isTimeout = false;
if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) {
+ if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) {
+ $multi->handlesActivity[$j] = [null, new TransportException('Error while processing content unencoding.')];
+ continue;
+ }
+
+ if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) {
+ $multi->handlesActivity[$j] = [null, new TransportException('Failed writing %d bytes to the response buffer.', \strlen($chunk))];
+ continue;
+ }
+
$response->offset += \strlen($chunk);
$chunk = new DataChunk($response->offset, $chunk);
} elseif (null === $chunk) {
@@ -356,6 +384,28 @@ trait ResponseTrait
$response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url']));
}
+ $response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(ZLIB_ENCODING_GZIP) : null;
+
+ if ($response->shouldBuffer instanceof \Closure) {
+ try {
+ $response->shouldBuffer = ($response->shouldBuffer)($response->headers);
+
+ if (null !== $response->info['error']) {
+ throw new TransportException($response->info['error']);
+ }
+ } catch (\Throwable $e) {
+ $response->close();
+ $multi->handlesActivity[$j] = [null, $e];
+ }
+ }
+
+ if (true === $response->shouldBuffer) {
+ $response->content = fopen('php://temp', 'w+');
+ } elseif (\is_resource($response->shouldBuffer)) {
+ $response->content = $response->shouldBuffer;
+ }
+ $response->shouldBuffer = null;
+
yield $response => $chunk;
if ($response->initializer && null === $response->info['error']) {