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 f3d614c

Browse filesBrowse files
Merge branch '7.2' into 7.3
* 7.2: [Process] Fix process status tracking Prevent empty request body stream in HttplugClient and Psr18Client [Lock] Fix Predis error handling [HttpClient] Fix buffering AsyncResponse with no passthru [TypeInfo] Fix promoted property phpdoc reading [HttpClient] Fix retrying requests with Psr18Client and NTLM connections [HttpClient] Fix uploading files > 2GB [Mime] use isRendered method to avoid rendering an email twice
2 parents 8a0446d + c633647 commit f3d614c
Copy full SHA for f3d614c

File tree

Expand file treeCollapse file tree

15 files changed

+180
-64
lines changed
Filter options
Expand file treeCollapse file tree

15 files changed

+180
-64
lines changed

‎src/Symfony/Bridge/Twig/Mime/BodyRenderer.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function render(Message $message): void
4343
return;
4444
}
4545

46-
if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) {
46+
if ($message->isRendered()) {
4747
// email has already been rendered
4848
return;
4949
}

‎src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,14 @@ public function testRenderedOnce()
105105
;
106106
$email->textTemplate('text');
107107

108+
$this->assertFalse($email->isRendered());
108109
$renderer->render($email);
110+
$this->assertTrue($email->isRendered());
111+
109112
$this->assertEquals('Text', $email->getTextBody());
110113

111114
$email->text('reset');
115+
$this->assertTrue($email->isRendered());
112116

113117
$renderer->render($email);
114118
$this->assertEquals('reset', $email->getTextBody());

‎src/Symfony/Component/HttpClient/CurlHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+9-2Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,12 @@ public function request(string $method, string $url, array $options = []): Respo
238238
}
239239

240240
if (!\is_string($body)) {
241+
if (isset($options['auth_ntlm'])) {
242+
$curlopts[\CURLOPT_FORBID_REUSE] = true; // Reusing NTLM connections requires seeking capability, which only string bodies support
243+
}
244+
241245
if (\is_resource($body)) {
242-
$curlopts[\CURLOPT_INFILE] = $body;
246+
$curlopts[\CURLOPT_READDATA] = $body;
243247
} else {
244248
$curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) {
245249
static $eof = false;
@@ -318,6 +322,9 @@ public function request(string $method, string $url, array $options = []): Respo
318322
}
319323

320324
foreach ($curlopts as $opt => $value) {
325+
if (\CURLOPT_INFILESIZE === $opt && $value >= 1 << 31) {
326+
$opt = 115; // 115 === CURLOPT_INFILESIZE_LARGE, but it's not defined in PHP
327+
}
321328
if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) {
322329
$constantName = $this->findConstantName($opt);
323330
throw new TransportException(\sprintf('Curl option "%s" is not supported.', $constantName ?? $opt));
@@ -474,7 +481,7 @@ private function validateExtraCurlOptions(array $options): void
474481
\CURLOPT_RESOLVE => 'resolve',
475482
\CURLOPT_NOSIGNAL => 'timeout',
476483
\CURLOPT_HTTPHEADER => 'headers',
477-
\CURLOPT_INFILE => 'body',
484+
\CURLOPT_READDATA => 'body',
478485
\CURLOPT_READFUNCTION => 'body',
479486
\CURLOPT_INFILESIZE => 'body',
480487
\CURLOPT_POSTFIELDS => 'body',

‎src/Symfony/Component/HttpClient/HttplugClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/HttplugClient.php
+31-10Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -224,23 +224,44 @@ private function sendPsr7Request(RequestInterface $request, ?bool $buffer = null
224224
{
225225
try {
226226
$body = $request->getBody();
227+
$headers = $request->getHeaders();
227228

228-
if ($body->isSeekable()) {
229-
try {
230-
$body->seek(0);
231-
} catch (\RuntimeException) {
232-
// ignore
233-
}
229+
$size = $request->getHeader('content-length')[0] ?? -1;
230+
if (0 > $size && 0 < $size = $body->getSize() ?? -1) {
231+
$headers['Content-Length'] = [$size];
234232
}
235233

236-
$headers = $request->getHeaders();
237-
if (!$request->hasHeader('content-length') && 0 <= $size = $body->getSize() ?? -1) {
238-
$headers['Content-Length'] = [$size];
234+
if (0 === $size) {
235+
$body = '';
236+
} elseif (0 < $size && $size < 1 << 21) {
237+
if ($body->isSeekable()) {
238+
try {
239+
$body->seek(0);
240+
} catch (\RuntimeException) {
241+
// ignore
242+
}
243+
}
244+
245+
$body = $body->getContents();
246+
} else {
247+
$body = static function (int $size) use ($body) {
248+
if ($body->isSeekable()) {
249+
try {
250+
$body->seek(0);
251+
} catch (\RuntimeException) {
252+
// ignore
253+
}
254+
}
255+
256+
while (!$body->eof()) {
257+
yield $body->read($size);
258+
}
259+
};
239260
}
240261

241262
$options = [
242263
'headers' => $headers,
243-
'body' => static fn (int $size) => $body->read($size),
264+
'body' => $body,
244265
'buffer' => $buffer,
245266
];
246267

‎src/Symfony/Component/HttpClient/Psr18Client.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Psr18Client.php
+31-10Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,44 @@ public function sendRequest(RequestInterface $request): ResponseInterface
8888
{
8989
try {
9090
$body = $request->getBody();
91+
$headers = $request->getHeaders();
9192

92-
if ($body->isSeekable()) {
93-
try {
94-
$body->seek(0);
95-
} catch (\RuntimeException) {
96-
// ignore
97-
}
93+
$size = $request->getHeader('content-length')[0] ?? -1;
94+
if (0 > $size && 0 < $size = $body->getSize() ?? -1) {
95+
$headers['Content-Length'] = [$size];
9896
}
9997

100-
$headers = $request->getHeaders();
101-
if (!$request->hasHeader('content-length') && 0 <= $size = $body->getSize() ?? -1) {
102-
$headers['Content-Length'] = [$size];
98+
if (0 === $size) {
99+
$body = '';
100+
} elseif (0 < $size && $size < 1 << 21) {
101+
if ($body->isSeekable()) {
102+
try {
103+
$body->seek(0);
104+
} catch (\RuntimeException) {
105+
// ignore
106+
}
107+
}
108+
109+
$body = $body->getContents();
110+
} else {
111+
$body = static function (int $size) use ($body) {
112+
if ($body->isSeekable()) {
113+
try {
114+
$body->seek(0);
115+
} catch (\RuntimeException) {
116+
// ignore
117+
}
118+
}
119+
120+
while (!$body->eof()) {
121+
yield $body->read($size);
122+
}
123+
};
103124
}
104125

105126
$options = [
106127
'headers' => $headers,
107-
'body' => static fn (int $size) => $body->read($size),
128+
'body' => $body,
108129
];
109130

110131
if ('1.0' === $request->getProtocolVersion()) {

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/AsyncResponse.php
+5-12Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\HttpClient\Response;
1313

1414
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
15-
use Symfony\Component\HttpClient\Chunk\FirstChunk;
1615
use Symfony\Component\HttpClient\Chunk\LastChunk;
1716
use Symfony\Component\HttpClient\Exception\TransportException;
1817
use Symfony\Contracts\HttpClient\ChunkInterface;
@@ -245,7 +244,7 @@ public static function stream(iterable $responses, ?float $timeout = null, ?stri
245244
$wrappedResponses[] = $r->response;
246245

247246
if ($r->stream) {
248-
yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap);
247+
yield from self::passthruStream($response = $r->response, $r, $asyncMap, new LastChunk());
249248

250249
if (!isset($asyncMap[$response])) {
251250
array_pop($wrappedResponses);
@@ -276,15 +275,9 @@ public static function stream(iterable $responses, ?float $timeout = null, ?stri
276275
}
277276

278277
if (!$r->passthru) {
279-
if (null !== $chunk->getError() || $chunk->isLast()) {
280-
unset($asyncMap[$response]);
281-
} elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) {
282-
$chunk = new ErrorChunk($r->offset, new TransportException(\sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
283-
$r->info['error'] = $chunk->getError();
284-
$r->response->cancel();
285-
}
278+
$r->stream = (static fn () => yield $chunk)();
279+
yield from self::passthruStream($response, $r, $asyncMap);
286280

287-
yield $r => $chunk;
288281
continue;
289282
}
290283

@@ -347,13 +340,13 @@ private static function passthru(HttpClientInterface $client, self $r, ChunkInte
347340
}
348341
$r->stream = $stream;
349342

350-
yield from self::passthruStream($response, $r, null, $asyncMap);
343+
yield from self::passthruStream($response, $r, $asyncMap);
351344
}
352345

353346
/**
354347
* @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
355348
*/
356-
private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator
349+
private static function passthruStream(ResponseInterface $response, self $r, ?\SplObjectStorage $asyncMap, ?ChunkInterface $chunk = null): \Generator
357350
{
358351
while (true) {
359352
try {

‎src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,20 @@ public function testBufferPurePassthru()
231231

232232
$this->assertStringContainsString('SERVER_PROTOCOL', $response->getContent());
233233
$this->assertStringContainsString('HTTP_HOST', $response->getContent());
234+
235+
$client = new class(parent::getHttpClient(__FUNCTION__)) implements HttpClientInterface {
236+
use AsyncDecoratorTrait;
237+
238+
public function request(string $method, string $url, array $options = []): ResponseInterface
239+
{
240+
return new AsyncResponse($this->client, $method, $url, $options);
241+
}
242+
};
243+
244+
$response = $client->request('GET', 'http://localhost:8057/');
245+
246+
$this->assertStringContainsString('SERVER_PROTOCOL', $response->getContent());
247+
$this->assertStringContainsString('HTTP_HOST', $response->getContent());
234248
}
235249

236250
public function testRetryTimeout()

‎src/Symfony/Component/Lock/Store/RedisStore.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Store/RedisStore.php
+31-11Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Lock\Store;
1313

1414
use Predis\Response\Error;
15+
use Predis\Response\ServerException;
1516
use Relay\Relay;
1617
use Symfony\Component\Lock\Exception\InvalidTtlException;
1718
use Symfony\Component\Lock\Exception\LockConflictedException;
@@ -284,21 +285,18 @@ private function evaluate(string $script, string $resource, array $args): mixed
284285

285286
\assert($this->redis instanceof \Predis\ClientInterface);
286287

287-
$result = $this->redis->evalSha($scriptSha, 1, $resource, ...$args);
288-
if ($result instanceof Error && str_starts_with($result->getMessage(), self::NO_SCRIPT_ERROR_MESSAGE_PREFIX)) {
289-
$result = $this->redis->script('LOAD', $script);
290-
if ($result instanceof Error) {
291-
throw new LockStorageException($result->getMessage());
288+
try {
289+
return $this->handlePredisError(fn () => $this->redis->evalSha($scriptSha, 1, $resource, ...$args));
290+
} catch (LockStorageException $e) {
291+
// Fallthrough only if we need to load the script
292+
if (!str_starts_with($e->getMessage(), self::NO_SCRIPT_ERROR_MESSAGE_PREFIX)) {
293+
throw $e;
292294
}
293-
294-
$result = $this->redis->evalSha($scriptSha, 1, $resource, ...$args);
295295
}
296296

297-
if ($result instanceof Error) {
298-
throw new LockStorageException($result->getMessage());
299-
}
297+
$this->handlePredisError(fn () => $this->redis->script('LOAD', $script));
300298

301-
return $result;
299+
return $this->handlePredisError(fn () => $this->redis->evalSha($scriptSha, 1, $resource, ...$args));
302300
}
303301

304302
private function getUniqueToken(Key $key): string
@@ -347,4 +345,26 @@ private function getNowCode(): string
347345
now = math.floor(now * 1000)
348346
';
349347
}
348+
349+
/**
350+
* @template T
351+
*
352+
* @param callable(): T $callback
353+
*
354+
* @return T
355+
*/
356+
private function handlePredisError(callable $callback): mixed
357+
{
358+
try {
359+
$result = $callback();
360+
} catch (ServerException $e) {
361+
throw new LockStorageException($e->getMessage(), $e->getCode(), $e);
362+
}
363+
364+
if ($result instanceof Error) {
365+
throw new LockStorageException($result->getMessage());
366+
}
367+
368+
return $result;
369+
}
350370
}
+36Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\Lock\Tests\Store;
13+
14+
/**
15+
* @group integration
16+
*/
17+
class PredisStoreWithExceptionsTest extends AbstractRedisStoreTestCase
18+
{
19+
public static function setUpBeforeClass(): void
20+
{
21+
$redis = new \Predis\Client(array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => null]));
22+
try {
23+
$redis->connect();
24+
} catch (\Exception $e) {
25+
self::markTestSkipped($e->getMessage());
26+
}
27+
}
28+
29+
protected function getRedisConnection(): \Predis\Client
30+
{
31+
$redis = new \Predis\Client(array_combine(['host', 'port'], explode(':', getenv('REDIS_HOST')) + [1 => null]));
32+
$redis->connect();
33+
34+
return $redis;
35+
}
36+
}

‎src/Symfony/Component/Lock/Tests/Store/PredisStoreTest.php renamed to ‎src/Symfony/Component/Lock/Tests/Store/PredisStoreWithoutExceptionsTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Lock/Tests/Store/PredisStoreWithoutExceptionsTest.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*
1717
* @group integration
1818
*/
19-
class PredisStoreTest extends AbstractRedisStoreTestCase
19+
class PredisStoreWithoutExceptionsTest extends AbstractRedisStoreTestCase
2020
{
2121
public static function setUpBeforeClass(): void
2222
{

‎src/Symfony/Component/Process/Process.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Process/Process.php
+3-15Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class Process implements \IteratorAggregate
8282
private WindowsPipes|UnixPipes $processPipes;
8383

8484
private ?int $latestSignal = null;
85-
private ?int $cachedExitCode = null;
8685

8786
private static ?bool $sigchild = null;
8887
private static array $executables = [];
@@ -1324,21 +1323,10 @@ protected function updateStatus(bool $blocking): void
13241323
return;
13251324
}
13261325

1327-
$this->processInformation = proc_get_status($this->process);
1328-
$running = $this->processInformation['running'];
1329-
1330-
// In PHP < 8.3, "proc_get_status" only returns the correct exit status on the first call.
1331-
// Subsequent calls return -1 as the process is discarded. This workaround caches the first
1332-
// retrieved exit status for consistent results in later calls, mimicking PHP 8.3 behavior.
1333-
if (\PHP_VERSION_ID < 80300) {
1334-
if (!isset($this->cachedExitCode) && !$running && -1 !== $this->processInformation['exitcode']) {
1335-
$this->cachedExitCode = $this->processInformation['exitcode'];
1336-
}
1337-
1338-
if (isset($this->cachedExitCode) && !$running && -1 === $this->processInformation['exitcode']) {
1339-
$this->processInformation['exitcode'] = $this->cachedExitCode;
1340-
}
1326+
if ($this->processInformation['running'] ?? true) {
1327+
$this->processInformation = proc_get_status($this->process);
13411328
}
1329+
$running = $this->processInformation['running'];
13421330

13431331
$this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
13441332

‎src/Symfony/Component/Process/Tests/ProcessTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Process/Tests/ProcessTest.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,9 @@ public function testProcessIsSignaledIfStopped()
732732
if ('\\' === \DIRECTORY_SEPARATOR) {
733733
$this->markTestSkipped('Windows does not support POSIX signals');
734734
}
735+
if (\PHP_VERSION_ID < 80300 && isset($_SERVER['GITHUB_ACTIONS'])) {
736+
$this->markTestSkipped('Transient on GHA with PHP < 8.3');
737+
}
735738

736739
$process = $this->getProcessForCode('sleep(32);');
737740
$process->start();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.