diff --git a/.github/patch-types.php b/.github/patch-types.php index 30fc4c1573..a494253292 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -16,6 +16,7 @@ foreach ($loader->getClassMap() as $class => $file) { case false !== strpos($file = realpath($file), '/vendor/'): case false !== strpos($file, '/src/Symfony/Bridge/PhpUnit/'): case false !== strpos($file, '/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php'): + case false !== strpos($file, '/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php'): case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/BadFileName.php'): case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/BadParent.php'): case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/ParseError.php'): @@ -31,6 +32,7 @@ foreach ($loader->getClassMap() as $class => $file) { case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'): case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'): + case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php'): continue 2; } diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index 5ccb212eb1..58ee9a5f99 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -473,6 +473,15 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface case $driver instanceof \Doctrine\DBAL\Driver\PDO\SQLSrv\Driver: $this->driver = 'sqlsrv'; break; + case $driver instanceof \Doctrine\DBAL\Driver: + $this->driver = [ + 'mssql' => 'sqlsrv', + 'oracle' => 'oci', + 'postgresql' => 'pgsql', + 'sqlite' => 'sqlite', + 'mysql' => 'mysql', + ][$driver->getDatabasePlatform()->getName()] ?? \get_class($driver); + break; default: $this->driver = \get_class($driver); break; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php index 46c4599dc5..61491c7f2a 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php @@ -11,13 +11,16 @@ namespace Symfony\Component\Cache\Tests\Adapter; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractMySQLDriver; +use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Schema\Schema; use PHPUnit\Framework\SkippedTestSuiteError; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\PdoAdapter; +use Symfony\Component\Cache\Tests\Fixtures\DriverWrapper; /** * @group time-sensitive @@ -47,6 +50,31 @@ class PdoDbalAdapterTest extends AdapterTestCase return new PdoAdapter(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]), '', $defaultLifetime); } + public function testConfigureSchemaDecoratedDbalDriver() + { + $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]); + if (!interface_exists(Middleware::class)) { + $this->markTestSkipped('doctrine/dbal v2 does not support custom drivers using middleware'); + } + + $middleware = $this->createMock(Middleware::class); + $middleware + ->method('wrap') + ->willReturn(new DriverWrapper($connection->getDriver())); + + $config = new Configuration(); + $config->setMiddlewares([$middleware]); + + $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $config); + + $adapter = new PdoAdapter($connection); + $adapter->createTable(); + + $item = $adapter->getItem('key'); + $item->set('value'); + $this->assertTrue($adapter->save($item)); + } + public function testConfigureSchema() { $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]); diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php b/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php new file mode 100644 index 0000000000..bb73d8d0cf --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Fixtures; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\AbstractSchemaManager; + +class DriverWrapper implements Driver +{ + /** @var Driver */ + private $driver; + + public function __construct(Driver $driver) + { + $this->driver = $driver; + } + + public function connect(array $params, $username = null, $password = null, array $driverOptions = []): Driver\Connection + { + return $this->driver->connect($params, $username, $password, $driverOptions); + } + + public function getDatabasePlatform(): AbstractPlatform + { + return $this->driver->getDatabasePlatform(); + } + + public function getSchemaManager(Connection $conn, AbstractPlatform $platform): AbstractSchemaManager + { + return $this->driver->getSchemaManager($conn, $platform); + } + + public function getExceptionConverter(): Driver\API\ExceptionConverter + { + return $this->driver->getExceptionConverter(); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index aeb9df6069..4bd2bd523f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -99,12 +99,13 @@ class MemcachedSessionHandler extends AbstractSessionHandler } /** - * @return bool + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { // not required here because memcached will auto expire the records anyhow. - return true; + return 0; } /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php index c3e7ef6e60..a4f28ef21b 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -63,7 +63,7 @@ class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdat } /** - * @return bool + * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 9a2d7919af..514972cb1b 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -100,15 +100,14 @@ class MongoDbSessionHandler extends AbstractSessionHandler } /** - * @return bool + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { - $this->getCollection()->deleteMany([ + return $this->getCollection()->deleteMany([ $this->options['expiry_field'] => ['$lt' => new \MongoDB\BSON\UTCDateTime()], - ]); - - return true; + ])->getDeletedCount(); } /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php index bb182cd1cf..4331dbe502 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php @@ -70,11 +70,11 @@ class NullSessionHandler extends AbstractSessionHandler } /** - * @return bool + * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) { - return true; + return 0; } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 279f2b6f4a..0e6ae58225 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -290,7 +290,7 @@ class PdoSessionHandler extends AbstractSessionHandler } /** - * @return bool + * @return int|false */ #[\ReturnTypeWillChange] public function gc($maxlifetime) @@ -299,7 +299,7 @@ class PdoSessionHandler extends AbstractSessionHandler // This way, pruning expired sessions does not block them from being started while the current session is used. $this->gcCalled = true; - return true; + return 0; } /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php index 61714f1743..1a66057431 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php @@ -116,10 +116,13 @@ class RedisSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} + * + * @return int|false */ - public function gc($maxlifetime): bool + #[\ReturnTypeWillChange] + public function gc($maxlifetime) { - return true; + return 0; } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index 0bef089706..6c65f44937 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -114,7 +114,7 @@ abstract class AbstractRedisSessionHandlerTestCase extends TestCase public function testGcSession() { - $this->assertTrue($this->storage->gc(123)); + $this->assertIsInt($this->storage->gc(123)); } public function testUpdateTimestamp() diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc index a887f607e8..fd662e3a16 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc @@ -121,11 +121,12 @@ class TestSessionHandler extends AbstractSessionHandler return true; } - public function gc($maxLifetime): bool + #[\ReturnTypeWillChange] + public function gc($maxLifetime) { echo __FUNCTION__, "\n"; - return true; + return 1; } protected function doRead($sessionId): string diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php index 200567a29d..d404b74c6a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -113,7 +113,7 @@ class MemcachedSessionHandlerTest extends TestCase public function testGcSession() { - $this->assertTrue($this->storage->gc(123)); + $this->assertIsInt($this->storage->gc(123)); } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 01b92dfa22..bf8021c155 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -184,9 +184,14 @@ class MongoDbSessionHandlerTest extends TestCase ->willReturnCallback(function ($criteria) { $this->assertInstanceOf(\MongoDB\BSON\UTCDateTime::class, $criteria[$this->options['expiry_field']]['$lt']); $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000)); + + $result = $this->createMock(\MongoDB\DeleteResult::class); + $result->method('getDeletedCount')->willReturn(42); + + return $result; }); - $this->assertTrue($this->storage->gc(1)); + $this->assertSame(42, $this->storage->gc(1)); } public function testGetConnection() diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index c850e0b1d3..38445592ad 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -102,7 +102,7 @@ class ReflectionCaster $prefix.'allowsNull' => $c->allowsNull(), $prefix.'isBuiltin' => $c->isBuiltin(), ]; - } elseif ($c instanceof \ReflectionUnionType) { + } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { $a[$prefix.'allowsNull'] = $c->allowsNull(); self::addMap($a, $c, [ 'types' => 'getTypes', diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index 5aa5b37933..0423edf1f3 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -18,6 +18,7 @@ use Symfony\Component\VarDumper\Tests\Fixtures\ExtendsReflectionTypeFixture; use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo; use Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes; use Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass; +use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionIntersectionTypeFixture; use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionNamedTypeFixture; use Symfony\Component\VarDumper\Tests\Fixtures\ReflectionUnionTypeFixture; @@ -94,7 +95,7 @@ Closure($x) { $b: & 123 } file: "%sReflectionCasterTest.php" - line: "87 to 87" + line: "88 to 88" } EOTXT , $var @@ -244,6 +245,26 @@ EOTXT ); } + /** + * @requires PHP 8.1 + */ + public function testReflectionParameterIntersection() + { + $f = eval('return function (Traversable&Countable $a) {};'); + $var = new \ReflectionParameter($f, 0); + + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionParameter { + +name: "a" + position: 0 + typeHint: "Traversable&Countable" +} +EOTXT + , $var + ); + } + /** * @requires PHP 7.4 */ @@ -308,6 +329,34 @@ EOTXT ); } + /** + * @requires PHP 8.1 + */ + public function testReflectionIntersectionType() + { + $var = (new \ReflectionProperty(ReflectionIntersectionTypeFixture::class, 'a'))->getType(); + $this->assertDumpMatchesFormat( + <<<'EOTXT' +ReflectionIntersectionType { + allowsNull: false + types: array:2 [ + 0 => ReflectionNamedType { + name: "Traversable" + allowsNull: false + isBuiltin: false + } + 1 => ReflectionNamedType { + name: "Countable" + allowsNull: false + isBuiltin: false + } + ] +} +EOTXT + , $var + ); + } + /** * @requires PHP 8 */ diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php new file mode 100644 index 0000000000..d9d8d21769 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php @@ -0,0 +1,8 @@ +