Merge branch '5.4' into 6.0

* 5.4:
  [HttpClient] fix resetting DNS/etc when calling CurlHttpClient::reset()
  Fix invalid guess with enumType
  [HttpClient] Remove deprecated usage of GuzzleHttp\Promise\promise_for
This commit is contained in:
Nicolas Grekas 2022-01-17 11:54:40 +01:00
commit adc8a55bad
13 changed files with 216 additions and 84 deletions

View File

@ -135,14 +135,17 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
} }
if ($metadata->hasField($property)) { if ($metadata->hasField($property)) {
$nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
if (null !== $enumClass = $metadata->getFieldMapping($property)['enumType'] ?? null) {
return [new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $enumClass)];
}
$typeOfField = $metadata->getTypeOfField($property); $typeOfField = $metadata->getTypeOfField($property);
if (!$builtinType = $this->getPhpType($typeOfField)) { if (!$builtinType = $this->getPhpType($typeOfField)) {
return null; return null;
} }
$nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
switch ($builtinType) { switch ($builtinType) {
case Type::BUILTIN_TYPE_OBJECT: case Type::BUILTIN_TYPE_OBJECT:
switch ($typeOfField) { switch ($typeOfField) {

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class DoctrineLoaderEnum
{
/**
* @ORM\Id
* @ORM\Column
*/
public $id;
/**
* @ORM\Column(type="string", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString", length=1)
*/
public $enumString;
/**
* @ORM\Column(type="integer", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt")
*/
public $enumInt;
}

View File

@ -14,12 +14,16 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\Tools\Setup;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEnum;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString;
use Symfony\Component\PropertyInfo\Type; use Symfony\Component\PropertyInfo\Type;
/** /**
@ -124,6 +128,18 @@ class DoctrineExtractorTest extends TestCase
$this->assertEquals($expectedTypes, $actualTypes); $this->assertEquals($expectedTypes, $actualTypes);
} }
/**
* @requires PHP 8.1
*/
public function testExtractEnum()
{
if (!property_exists(Column::class, 'enumType')) {
$this->markTestSkipped('The "enumType" requires doctrine/orm 2.11.');
}
$this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, EnumString::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumString', []));
$this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, EnumInt::class)], $this->createExtractor()->getTypes(DoctrineEnum::class, 'enumInt', []));
}
public function typesProvider() public function typesProvider()
{ {
$provider = [ $provider = [

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
/**
* @Entity
*/
class DoctrineEnum
{
/**
* @Id
* @Column(type="smallint")
*/
public $id;
/**
* @Column(type="string", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString")
*/
protected $enumString;
/**
* @Column(type="integer", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt")
*/
protected $enumInt;
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
enum EnumInt: int
{
case Foo = 0;
case Bar = 1;
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
enum EnumString: string
{
case Foo = 'f';
case Bar = 'b';
}

View File

@ -11,11 +11,13 @@
namespace Symfony\Bridge\Doctrine\Tests\Validator; namespace Symfony\Bridge\Doctrine\Tests\Validator;
use Doctrine\ORM\Mapping\Column;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper;
use Symfony\Bridge\Doctrine\Tests\Fixtures\BaseUser; use Symfony\Bridge\Doctrine\Tests\Fixtures\BaseUser;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEmbed; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEmbed;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEnum;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNestedEmbed; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNestedEmbed;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNoAutoMappingEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNoAutoMappingEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderParentEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderParentEntity;
@ -149,6 +151,31 @@ class DoctrineLoaderTest extends TestCase
$this->assertSame(AutoMappingStrategy::DISABLED, $noAutoMappingMetadata[0]->getAutoMappingStrategy()); $this->assertSame(AutoMappingStrategy::DISABLED, $noAutoMappingMetadata[0]->getAutoMappingStrategy());
} }
/**
* @requires PHP 8.1
*/
public function testExtractEnum()
{
if (!property_exists(Column::class, 'enumType')) {
$this->markTestSkipped('The "enumType" requires doctrine/orm 2.11.');
}
$validator = Validation::createValidatorBuilder()
->addMethodMapping('loadValidatorMetadata')
->enableAnnotationMapping()
->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}'))
->getValidator()
;
$classMetadata = $validator->getMetadataFor(new DoctrineLoaderEnum());
$enumStringMetadata = $classMetadata->getPropertyMetadata('enumString');
$this->assertCount(0, $enumStringMetadata); // asserts the length constraint is not added to an enum
$enumStringMetadata = $classMetadata->getPropertyMetadata('enumInt');
$this->assertCount(0, $enumStringMetadata); // asserts the length constraint is not added to an enum
}
public function testFieldMappingsConfiguration() public function testFieldMappingsConfiguration()
{ {
$validator = Validation::createValidatorBuilder() $validator = Validation::createValidatorBuilder()

View File

@ -16,6 +16,7 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
use Doctrine\Persistence\Mapping\MappingException; use Doctrine\Persistence\Mapping\MappingException;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy; use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
@ -99,7 +100,7 @@ final class DoctrineLoader implements LoaderInterface
$loaded = true; $loaded = true;
} }
if (null === ($mapping['length'] ?? null) || !\in_array($mapping['type'], ['string', 'text'], true)) { if (null === ($mapping['length'] ?? null) || null !== ($mapping['enumType'] ?? null) || !\in_array($mapping['type'], ['string', 'text'], true)) {
continue; continue;
} }

View File

@ -166,7 +166,6 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) { if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
// DNS cache removals require curl 7.42 or higher // DNS cache removals require curl 7.42 or higher
// On lower versions, we have to create a new multi handle
$this->multi->reset(); $this->multi->reset();
} }
@ -283,6 +282,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
if (!$pushedResponse) { if (!$pushedResponse) {
$ch = curl_init(); $ch = curl_init();
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url)); $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
$curlopts += [\CURLOPT_SHARE => $this->multi->share];
} }
foreach ($curlopts as $opt => $value) { foreach ($curlopts as $opt => $value) {
@ -304,9 +304,9 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$responses = [$responses]; $responses = [$responses];
} }
if (($mh = $this->multi->handles[0] ?? null) instanceof \CurlMultiHandle) { if ($this->multi->handle instanceof \CurlMultiHandle) {
$active = 0; $active = 0;
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $active)) { while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) {
} }
} }

View File

@ -23,11 +23,11 @@ use Symfony\Component\HttpClient\Response\CurlResponse;
*/ */
final class CurlClientState extends ClientState final class CurlClientState extends ClientState
{ {
/** @var array<\CurlMultiHandle> */ public \CurlMultiHandle $handle;
public array $handles = []; public \CurlShareHandle $share;
/** @var PushedResponse[] */ /** @var PushedResponse[] */
public array $pushedResponses = []; public array $pushedResponses = [];
public $dnsCache; public DnsCache $dnsCache;
/** @var float[] */ /** @var float[] */
public array $pauseExpiries = []; public array $pauseExpiries = [];
public int $execCounter = \PHP_INT_MIN; public int $execCounter = \PHP_INT_MIN;
@ -35,27 +35,23 @@ final class CurlClientState extends ClientState
public static array $curlVersion; public static array $curlVersion;
private int $maxHostConnections;
private int $maxPendingPushes;
public function __construct(int $maxHostConnections, int $maxPendingPushes) public function __construct(int $maxHostConnections, int $maxPendingPushes)
{ {
self::$curlVersion = self::$curlVersion ?? curl_version(); self::$curlVersion = self::$curlVersion ?? curl_version();
array_unshift($this->handles, $mh = curl_multi_init()); $this->handle = curl_multi_init();
$this->dnsCache = new DnsCache(); $this->dnsCache = new DnsCache();
$this->maxHostConnections = $maxHostConnections; $this->reset();
$this->maxPendingPushes = $maxPendingPushes;
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
if (\defined('CURLPIPE_MULTIPLEX')) { if (\defined('CURLPIPE_MULTIPLEX')) {
curl_multi_setopt($mh, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX); curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
} }
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
$maxHostConnections = curl_multi_setopt($mh, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections; $maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections;
} }
if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) { if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) {
curl_multi_setopt($mh, \CURLMOPT_MAXCONNECTS, $maxHostConnections); curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
} }
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535 // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
@ -68,17 +64,8 @@ final class CurlClientState extends ClientState
return; return;
} }
// Clone to prevent a circular reference curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, function ($parent, $pushed, array $requestHeaders) use ($maxPendingPushes) {
$multi = clone $this; return $this->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
$multi->handles = [$mh];
$multi->pushedResponses = &$this->pushedResponses;
$multi->logger = &$this->logger;
$multi->handlesActivity = &$this->handlesActivity;
$multi->openHandles = &$this->openHandles;
$multi->lastTimeout = &$this->lastTimeout;
curl_multi_setopt($mh, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) {
return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
}); });
} }
@ -86,10 +73,7 @@ final class CurlClientState extends ClientState
{ {
foreach ($this->pushedResponses as $url => $response) { foreach ($this->pushedResponses as $url => $response) {
$this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); $this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
curl_multi_remove_handle($this->handle, $response->handle);
foreach ($this->handles as $mh) {
curl_multi_remove_handle($mh, $response->handle);
}
curl_close($response->handle); curl_close($response->handle);
} }
@ -97,11 +81,14 @@ final class CurlClientState extends ClientState
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals; $this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
$this->dnsCache->removals = $this->dnsCache->hostnames = []; $this->dnsCache->removals = $this->dnsCache->hostnames = [];
if (\defined('CURLMOPT_PUSHFUNCTION')) { $this->share = curl_share_init();
curl_multi_setopt($this->handles[0], \CURLMOPT_PUSHFUNCTION, null);
}
$this->__construct($this->maxHostConnections, $this->maxPendingPushes); curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
if (\defined('CURL_LOCK_DATA_CONNECT')) {
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT);
}
} }
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int

View File

@ -108,9 +108,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
if (0 < $duration) { if (0 < $duration) {
if ($execCounter === $multi->execCounter) { if ($execCounter === $multi->execCounter) {
$multi->execCounter = !\is_float($execCounter) ? 1 + $execCounter : \PHP_INT_MIN; $multi->execCounter = !\is_float($execCounter) ? 1 + $execCounter : \PHP_INT_MIN;
foreach ($multi->handles as $mh) { curl_multi_remove_handle($multi->handle, $ch);
curl_multi_remove_handle($mh, $ch);
}
} }
$lastExpiry = end($multi->pauseExpiries); $lastExpiry = end($multi->pauseExpiries);
@ -122,7 +120,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
} else { } else {
unset($multi->pauseExpiries[(int) $ch]); unset($multi->pauseExpiries[(int) $ch]);
curl_pause($ch, \CURLPAUSE_CONT); curl_pause($ch, \CURLPAUSE_CONT);
curl_multi_add_handle($multi->handles[0], $ch); curl_multi_add_handle($multi->handle, $ch);
} }
}; };
@ -176,7 +174,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
// Schedule the request in a non-blocking way // Schedule the request in a non-blocking way
$multi->lastTimeout = null; $multi->lastTimeout = null;
$multi->openHandles[$id] = [$ch, $options]; $multi->openHandles[$id] = [$ch, $options];
curl_multi_add_handle($multi->handles[0], $ch); curl_multi_add_handle($multi->handle, $ch);
$this->canary = new Canary(static function () use ($ch, $multi, $id) { $this->canary = new Canary(static function () use ($ch, $multi, $id) {
unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]); unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]);
@ -186,9 +184,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
return; return;
} }
foreach ($multi->handles as $mh) { curl_multi_remove_handle($multi->handle, $ch);
curl_multi_remove_handle($mh, $ch);
}
curl_setopt_array($ch, [ curl_setopt_array($ch, [
\CURLOPT_NOPROGRESS => true, \CURLOPT_NOPROGRESS => true,
\CURLOPT_PROGRESSFUNCTION => null, \CURLOPT_PROGRESSFUNCTION => null,
@ -270,7 +266,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
*/ */
private static function schedule(self $response, array &$runningResponses): void private static function schedule(self $response, array &$runningResponses): void
{ {
if (isset($runningResponses[$i = (int) $response->multi->handles[0]])) { if (isset($runningResponses[$i = (int) $response->multi->handle])) {
$runningResponses[$i][1][$response->id] = $response; $runningResponses[$i][1][$response->id] = $response;
} else { } else {
$runningResponses[$i] = [$response->multi, [$response->id => $response]]; $runningResponses[$i] = [$response->multi, [$response->id => $response]];
@ -303,47 +299,39 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
try { try {
self::$performing = true; self::$performing = true;
++$multi->execCounter; ++$multi->execCounter;
$active = 0;
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) {
}
foreach ($multi->handles as $i => $mh) { if (\CURLM_OK !== $err) {
$active = 0; throw new TransportException(curl_multi_strerror($err));
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($mh, $active))) { }
while ($info = curl_multi_info_read($multi->handle)) {
if (\CURLMSG_DONE !== $info['msg']) {
continue;
} }
$result = $info['result'];
$id = (int) $ch = $info['handle'];
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
if (\CURLM_OK !== $err) { if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
throw new TransportException(curl_multi_strerror($err)); curl_multi_remove_handle($multi->handle, $ch);
} $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
while ($info = curl_multi_info_read($mh)) { if (0 === curl_multi_add_handle($multi->handle, $ch)) {
if (\CURLMSG_DONE !== $info['msg']) {
continue; continue;
} }
$result = $info['result'];
$id = (int) $ch = $info['handle'];
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
curl_multi_remove_handle($mh, $ch);
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
if (0 === curl_multi_add_handle($mh, $ch)) {
continue;
}
}
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
$multi->handlesActivity[$id][] = new FirstChunk();
}
$multi->handlesActivity[$id][] = null;
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
} }
if (!$active && 0 < $i) { if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
curl_multi_close($mh); $multi->handlesActivity[$id][] = new FirstChunk();
unset($multi->handles[$i]);
} }
$multi->handlesActivity[$id][] = null;
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
} }
} finally { } finally {
self::$performing = false; self::$performing = false;
@ -368,11 +356,11 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
unset($multi->pauseExpiries[$id]); unset($multi->pauseExpiries[$id]);
curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT); curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT);
curl_multi_add_handle($multi->handles[0], $multi->openHandles[$id][0]); curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]);
} }
} }
if (0 !== $selected = curl_multi_select($multi->handles[array_key_last($multi->handles)], $timeout)) { if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) {
return $selected; return $selected;
} }

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\HttpClient\Response; namespace Symfony\Component\HttpClient\Response;
use function GuzzleHttp\Promise\promise_for; use GuzzleHttp\Promise\Create;
use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface; use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface;
use Http\Promise\Promise as HttplugPromiseInterface; use Http\Promise\Promise as HttplugPromiseInterface;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
@ -74,7 +74,7 @@ final class HttplugPromise implements HttplugPromiseInterface
} }
return static function ($value) use ($callback) { return static function ($value) use ($callback) {
return promise_for($callback($value)); return Create::promiseFor($callback($value));
}; };
} }
} }

View File

@ -62,9 +62,9 @@ class CurlHttpClientTest extends HttpClientTestCase
$r = new \ReflectionProperty($httpClient, 'multi'); $r = new \ReflectionProperty($httpClient, 'multi');
$r->setAccessible(true); $r->setAccessible(true);
$clientState = $r->getValue($httpClient); $clientState = $r->getValue($httpClient);
$initialHandleId = (int) $clientState->handles[0]; $initialShareId = $clientState->share;
$httpClient->reset(); $httpClient->reset();
self::assertNotSame($initialHandleId, (int) $clientState->handles[0]); self::assertNotSame($initialShareId, $clientState->share);
} }
public function testProcessAfterReset() public function testProcessAfterReset()