Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 99290c6

Browse files
Merge branch '4.4' into 5.3
* 4.4: [Cache] Make sure PdoAdapter::prune() always returns a bool [HttpKernel] Fix broken mock [HttpClient] Fix handling timeouts when responses are destructed [PropertyInfo] Support for intersection types
2 parents b08a570 + 3bf2d14 commit 99290c6

File tree

16 files changed

+129
-18
lines changed

16 files changed

+129
-18
lines changed

psalm.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,13 @@
1717
<directory name="vendor" />
1818
</ignoreFiles>
1919
</projectFiles>
20+
21+
<issueHandlers>
22+
<UndefinedClass>
23+
<errorLevel type="suppress">
24+
<!-- Class has been added in PHP 8.1 -->
25+
<referencedClass name="ReflectionIntersectionType"/>
26+
</errorLevel>
27+
</UndefinedClass>
28+
</issueHandlers>
2029
</psalm>

src/Symfony/Component/Cache/Adapter/PdoAdapter.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Doctrine\DBAL\Exception\TableNotFoundException;
2020
use Doctrine\DBAL\ParameterType;
2121
use Doctrine\DBAL\Schema\Schema;
22+
use Doctrine\DBAL\Statement;
2223
use Symfony\Component\Cache\Exception\InvalidArgumentException;
2324
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
2425
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
@@ -208,6 +209,13 @@ public function prune()
208209
$delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), $useDbalConstants ? ParameterType::STRING : \PDO::PARAM_STR);
209210
}
210211
try {
212+
// Doctrine DBAL ^2.13 || >= 3.1
213+
if ($delete instanceof Statement && method_exists($delete, 'executeStatement')) {
214+
$delete->executeStatement();
215+
216+
return true;
217+
}
218+
211219
return $delete->execute();
212220
} catch (TableNotFoundException $e) {
213221
return true;

src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public function testPrune()
227227
$doSet('qux', 'qux-val', new \DateInterval('PT20S'));
228228

229229
sleep(30);
230-
$cache->prune();
230+
$this->assertTrue($cache->prune());
231231
$this->assertTrue($this->isPruned($cache, 'foo'));
232232
$this->assertTrue($this->isPruned($cache, 'bar'));
233233
$this->assertTrue($this->isPruned($cache, 'baz'));
@@ -238,27 +238,27 @@ public function testPrune()
238238
$doSet('baz', 'baz-val', new \DateInterval('PT40S'));
239239
$doSet('qux', 'qux-val', new \DateInterval('PT80S'));
240240

241-
$cache->prune();
241+
$this->assertTrue($cache->prune());
242242
$this->assertFalse($this->isPruned($cache, 'foo'));
243243
$this->assertFalse($this->isPruned($cache, 'bar'));
244244
$this->assertFalse($this->isPruned($cache, 'baz'));
245245
$this->assertFalse($this->isPruned($cache, 'qux'));
246246

247247
sleep(30);
248-
$cache->prune();
248+
$this->assertTrue($cache->prune());
249249
$this->assertFalse($this->isPruned($cache, 'foo'));
250250
$this->assertTrue($this->isPruned($cache, 'bar'));
251251
$this->assertFalse($this->isPruned($cache, 'baz'));
252252
$this->assertFalse($this->isPruned($cache, 'qux'));
253253

254254
sleep(30);
255-
$cache->prune();
255+
$this->assertTrue($cache->prune());
256256
$this->assertFalse($this->isPruned($cache, 'foo'));
257257
$this->assertTrue($this->isPruned($cache, 'baz'));
258258
$this->assertFalse($this->isPruned($cache, 'qux'));
259259

260260
sleep(30);
261-
$cache->prune();
261+
$this->assertTrue($cache->prune());
262262
$this->assertFalse($this->isPruned($cache, 'foo'));
263263
$this->assertTrue($this->isPruned($cache, 'qux'));
264264
}

src/Symfony/Component/HttpClient/Internal/ClientState.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ class ClientState
2222
{
2323
public $handlesActivity = [];
2424
public $openHandles = [];
25+
public $lastTimeout;
2526
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ public function __construct(HttpClientInterface $client, string $method, string
5757
}
5858
$this->response = $client->request($method, $url, ['buffer' => false] + $options);
5959
$this->passthru = $passthru;
60-
$this->initializer = static function (self $response) {
60+
$this->initializer = static function (self $response, float $timeout = null) {
6161
if (null === $response->shouldBuffer) {
6262
return false;
6363
}
6464

6565
while (true) {
66-
foreach (self::stream([$response]) as $chunk) {
66+
foreach (self::stream([$response], $timeout) as $chunk) {
6767
if ($chunk->isTimeout() && $response->passthru) {
6868
foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) {
6969
if ($chunk->isFirst()) {
@@ -179,6 +179,7 @@ public function __destruct()
179179

180180
if ($this->initializer && null === $this->getInfo('error')) {
181181
try {
182+
self::initialize($this, -0.0);
182183
$this->getHeaders(true);
183184
} catch (HttpExceptionInterface $httpException) {
184185
// no-op

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ public function __wakeup()
145145
*/
146146
abstract protected function close(): void;
147147

148-
private static function initialize(self $response): void
148+
private static function initialize(self $response, float $timeout = null): void
149149
{
150150
if (null !== $response->getInfo('error')) {
151151
throw new TransportException($response->getInfo('error'));
152152
}
153153

154154
try {
155-
if (($response->initializer)($response)) {
156-
foreach (self::stream([$response]) as $chunk) {
155+
if (($response->initializer)($response, $timeout)) {
156+
foreach (self::stream([$response], $timeout) as $chunk) {
157157
if ($chunk->isFirst()) {
158158
break;
159159
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private function doDestruct()
138138
$this->shouldBuffer = true;
139139

140140
if ($this->initializer && null === $this->info['error']) {
141-
self::initialize($this);
141+
self::initialize($this, -0.0);
142142
$this->checkStatusCode();
143143
}
144144
}
@@ -159,6 +159,12 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
159159
$lastActivity = microtime(true);
160160
$elapsedTimeout = 0;
161161

162+
if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) {
163+
$timeout = null;
164+
} elseif ($fromLastTimeout = 0 > $timeout) {
165+
$timeout = -$timeout;
166+
}
167+
162168
while (true) {
163169
$hasActivity = false;
164170
$timeoutMax = 0;
@@ -172,15 +178,21 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
172178
foreach ($responses as $j => $response) {
173179
$timeoutMax = $timeout ?? max($timeoutMax, $response->timeout);
174180
$timeoutMin = min($timeoutMin, $response->timeout, 1);
181+
182+
if ($fromLastTimeout && null !== $multi->lastTimeout) {
183+
$elapsedTimeout = microtime(true) - $multi->lastTimeout;
184+
}
185+
175186
$chunk = false;
176187

177188
if (isset($multi->handlesActivity[$j])) {
178-
// no-op
189+
$multi->lastTimeout = null;
179190
} elseif (!isset($multi->openHandles[$j])) {
180191
unset($responses[$j]);
181192
continue;
182193
} elseif ($elapsedTimeout >= $timeoutMax) {
183194
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
195+
$multi->lastTimeout ?? $multi->lastTimeout = $lastActivity;
184196
} else {
185197
continue;
186198
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
namespace Symfony\Component\HttpClient\Tests;
1313

1414
use Symfony\Component\HttpClient\AsyncDecoratorTrait;
15+
use Symfony\Component\HttpClient\CurlHttpClient;
1516
use Symfony\Component\HttpClient\DecoratorTrait;
17+
use Symfony\Component\HttpClient\HttpClient;
1618
use Symfony\Component\HttpClient\Response\AsyncContext;
1719
use Symfony\Component\HttpClient\Response\AsyncResponse;
1820
use Symfony\Contracts\HttpClient\ChunkInterface;
@@ -29,6 +31,10 @@ protected function getHttpClient(string $testCase, \Closure $chunkFilter = null,
2931
$this->markTestSkipped("AsyncDecoratorTrait doesn't cache handles");
3032
}
3133

34+
if ('testTimeoutOnDestruct' === $testCase) {
35+
return new CurlHttpClient();
36+
}
37+
3238
$chunkFilter = $chunkFilter ?? static function (ChunkInterface $chunk, AsyncContext $context) { yield $chunk; };
3339

3440
return new class($decoratedClient ?? parent::getHttpClient($testCase), $chunkFilter) implements HttpClientInterface {
@@ -49,6 +55,15 @@ public function request(string $method, string $url, array $options = []): Respo
4955
};
5056
}
5157

58+
public function testTimeoutOnDestruct()
59+
{
60+
if (HttpClient::create() instanceof NativeHttpClient) {
61+
parent::testTimeoutOnDestruct();
62+
} else {
63+
HttpClientTestCase::testTimeoutOnDestruct();
64+
}
65+
}
66+
5267
public function testRetry404()
5368
{
5469
$client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
3232
{
3333
private static $vulcainStarted = false;
3434

35+
public function testTimeoutOnDestruct()
36+
{
37+
if (!method_exists(parent::class, 'testTimeoutOnDestruct')) {
38+
$this->markTestSkipped('BaseHttpClientTestCase doesn\'t have testTimeoutOnDestruct().');
39+
}
40+
41+
parent::testTimeoutOnDestruct();
42+
}
43+
3544
public function testAcceptHeader()
3645
{
3746
$client = $this->getHttpClient(__FUNCTION__);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface
236236
$this->markTestSkipped('Real transport required');
237237
break;
238238

239+
case 'testTimeoutOnDestruct':
240+
$this->markTestSkipped('Real transport required');
241+
break;
242+
239243
case 'testDestruct':
240244
$this->markTestSkipped("MockHttpClient doesn't timeout on destruct");
241245
break;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public function testInformationalResponseStream()
2626
$this->markTestSkipped('NativeHttpClient doesn\'t support informational status codes.');
2727
}
2828

29+
public function testTimeoutOnDestruct()
30+
{
31+
$this->markTestSkipped('NativeHttpClient doesn\'t support opening concurrent requests.');
32+
}
33+
2934
public function testHttp2PushVulcain()
3035
{
3136
$this->markTestSkipped('NativeHttpClient doesn\'t support HTTP/2.');

src/Symfony/Component/HttpKernel/Tests/CacheClearer/Psr6CacheClearerTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ public function testClearPool()
3232
$pool = $this->createMock(CacheItemPoolInterface::class);
3333
$pool
3434
->expects($this->once())
35-
->method('clear');
35+
->method('clear')
36+
->willReturn(true)
37+
;
3638

37-
(new Psr6CacheClearer(['pool' => $pool]))->clearPool('pool');
39+
$this->assertTrue((new Psr6CacheClearer(['pool' => $pool]))->clearPool('pool'));
3840
}
3941

4042
public function testClearPoolThrowsExceptionOnUnreferencedPool()

src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,8 @@ private function extractFromReflectionType(\ReflectionType $reflectionType, \Ref
552552
$types = [];
553553
$nullable = $reflectionType->allowsNull();
554554

555-
foreach ($reflectionType instanceof \ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType] as $type) {
556-
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string) $type;
555+
foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) {
556+
$phpTypeOrClass = $type->getName();
557557
if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
558558
continue;
559559
}

src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,20 @@ public function php80TypesProvider()
280280
}
281281

282282
/**
283+
* @dataProvider php81TypesProvider
283284
* @requires PHP 8.1
284285
*/
285-
public function testExtractPhp81Type()
286+
public function testExtractPhp81Type($property, array $type = null)
286287
{
287-
$this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy', 'nothing', []));
288+
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy', $property, []));
289+
}
290+
291+
public function php81TypesProvider()
292+
{
293+
return [
294+
['nothing', null],
295+
['collection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Traversable'), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Countable')]],
296+
];
288297
}
289298

290299
/**

src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php81Dummy.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ public function getNothing(): never
88
{
99
throw new \Exception('Oops');
1010
}
11+
12+
public function getCollection(): \Traversable&\Countable
13+
{
14+
}
1115
}

src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,38 @@ public function testTimeoutWithActiveConcurrentStream()
835835
}
836836
}
837837

838+
public function testTimeoutOnDestruct()
839+
{
840+
$p1 = TestHttpServer::start(8067);
841+
$p2 = TestHttpServer::start(8077);
842+
843+
$client = $this->getHttpClient(__FUNCTION__);
844+
$start = microtime(true);
845+
$responses = [];
846+
847+
$responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]);
848+
$responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]);
849+
$responses[] = $client->request('GET', 'http://localhost:8067/timeout-header', ['timeout' => 0.25]);
850+
$responses[] = $client->request('GET', 'http://localhost:8077/timeout-header', ['timeout' => 0.25]);
851+
852+
try {
853+
while ($response = array_shift($responses)) {
854+
try {
855+
unset($response);
856+
$this->fail(TransportExceptionInterface::class.' expected');
857+
} catch (TransportExceptionInterface $e) {
858+
}
859+
}
860+
861+
$duration = microtime(true) - $start;
862+
863+
$this->assertLessThan(1.0, $duration);
864+
} finally {
865+
$p1->stop();
866+
$p2->stop();
867+
}
868+
}
869+
838870
public function testDestruct()
839871
{
840872
$client = $this->getHttpClient(__FUNCTION__);

0 commit comments

Comments
 (0)