diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index ec8f7933f9..fa03960ad4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -60,6 +60,13 @@ class EntityTypeTest extends BaseTypeTest protected static $supportedFeatureSetVersion = 404; + public static function setUpBeforeClass(): void + { + if (\PHP_VERSION_ID >= 80000) { + self::markTestSkipped('Doctrine DBAL 2.x is incompatible with PHP 8.'); + } + } + protected function setUp(): void { $this->em = DoctrineTestHelper::createTestEntityManager(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 10ff2ff259..cb809abbaf 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -24,6 +24,13 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; class EntityUserProviderTest extends TestCase { + public static function setUpBeforeClass(): void + { + if (\PHP_VERSION_ID >= 80000) { + self::markTestSkipped('Doctrine DBAL 2.x is incompatible with PHP 8.'); + } + } + public function testRefreshUserGetsUserByPrimaryKey() { $em = DoctrineTestHelper::createTestEntityManager(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index e9e905c89c..985d4601bf 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -59,6 +59,13 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase protected $repositoryFactory; + public static function setUpBeforeClass(): void + { + if (\PHP_VERSION_ID >= 80000) { + self::markTestSkipped('Doctrine DBAL 2.x is incompatible with PHP 8.'); + } + } + protected function setUp(): void { $this->repositoryFactory = new TestRepositoryFactory(); diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 6d8754ac5e..e24f6f1912 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -30,9 +30,9 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "~1.6", - "doctrine/dbal": "~2.5", - "predis/predis": "~1.1", + "doctrine/cache": "^1.6", + "doctrine/dbal": "^2.5|^3.0", + "predis/predis": "^1.1", "psr/simple-cache": "^1.0", "symfony/config": "^4.2|^5.0", "symfony/dependency-injection": "^3.4|^4.1|^5.0", diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php index 48fdcd69b0..f386e4524f 100644 --- a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -135,7 +135,11 @@ class ReflectionClassResource implements SelfCheckingResourceInterface $defaults = $class->getDefaultProperties(); foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { - yield $p->getDocComment().$p; + yield $p->getDocComment(); + yield $p->isDefault() ? '' : ''; + yield $p->isPublic() ? 'public' : 'protected'; + yield $p->isStatic() ? 'static' : ''; + yield '$'.$p->name; yield print_r(isset($defaults[$p->name]) && !\is_object($defaults[$p->name]) ? $defaults[$p->name] : null, true); } } diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index fcead4f84e..2ba84c171b 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -103,9 +103,14 @@ class ErrorHandlerTest extends TestCase $this->fail('ErrorException expected'); } catch (\ErrorException $exception) { // if an exception is thrown, the test passed - $this->assertEquals(E_NOTICE, $exception->getSeverity()); + if (\PHP_VERSION_ID < 80000) { + $this->assertEquals(E_NOTICE, $exception->getSeverity()); + $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); + } else { + $this->assertEquals(E_WARNING, $exception->getSeverity()); + $this->assertRegExp('/^Warning: Undefined variable \$(foo|bar)/', $exception->getMessage()); + } $this->assertEquals(__FILE__, $exception->getFile()); - $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); $trace = $exception->getTrace(); @@ -249,11 +254,17 @@ class ErrorHandlerTest extends TestCase $line = null; $logArgCheck = function ($level, $message, $context) use (&$line) { - $this->assertEquals('Notice: Undefined variable: undefVar', $message); $this->assertArrayHasKey('exception', $context); $exception = $context['exception']; + + if (\PHP_VERSION_ID < 80000) { + $this->assertEquals('Notice: Undefined variable: undefVar', $message); + $this->assertSame(E_NOTICE, $exception->getSeverity()); + } else { + $this->assertEquals('Warning: Undefined variable $undefVar', $message); + $this->assertSame(E_WARNING, $exception->getSeverity()); + } $this->assertInstanceOf(SilencedErrorContext::class, $exception); - $this->assertSame(E_NOTICE, $exception->getSeverity()); $this->assertSame(__FILE__, $exception->getFile()); $this->assertSame($line, $exception->getLine()); $this->assertNotEmpty($exception->getTrace()); @@ -267,8 +278,13 @@ class ErrorHandlerTest extends TestCase ; $handler = ErrorHandler::register(); - $handler->setDefaultLogger($logger, E_NOTICE); - $handler->screamAt(E_NOTICE); + if (\PHP_VERSION_ID < 80000) { + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->screamAt(E_NOTICE); + } else { + $handler->setDefaultLogger($logger, E_WARNING); + $handler->screamAt(E_WARNING); + } unset($undefVar); $line = __LINE__ + 1; @$undefVar++; diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php b/src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php index d449c40cc7..bb2fba51f0 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/ErrorHandlerThatUsesThePreviousOne.php @@ -15,8 +15,8 @@ class ErrorHandlerThatUsesThePreviousOne return $handler; } - public function handleError($type, $message, $file, $line, $context) + public function handleError() { - return \call_user_func(self::$previous, $type, $message, $file, $line, $context); + return \call_user_func_array(self::$previous, \func_get_args()); } } diff --git a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php index 36fd1e65cf..5d5063e46b 100644 --- a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php +++ b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php @@ -31,7 +31,7 @@ use Symfony\Component\Mime\MimeTypes; */ class UploadedFile extends File { - private $test = false; + private $test; private $originalName; private $mimeType; private $error; @@ -83,7 +83,7 @@ class UploadedFile extends File * It is extracted from the request from which the file has been uploaded. * Then it should not be considered as a safe value. * - * @return string|null The original name + * @return string The original name */ public function getClientOriginalName() { @@ -112,7 +112,7 @@ class UploadedFile extends File * For a trusted mime type, use getMimeType() instead (which guesses the mime * type based on the file content). * - * @return string|null The mime type + * @return string The mime type * * @see getMimeType() */ diff --git a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php index 9802a66b9a..ba94ce041e 100644 --- a/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/NumberFormatter/AbstractNumberFormatterTest.php @@ -602,7 +602,7 @@ abstract class AbstractNumberFormatterTest extends TestCase $r = new \ReflectionProperty('Symfony\Component\Intl\NumberFormatter\NumberFormatter', 'enSymbols'); $r->setAccessible(true); - $expected = $r->getValue('Symfony\Component\Intl\NumberFormatter\NumberFormatter'); + $expected = $r->getValue(); for ($i = 0; $i <= 17; ++$i) { $this->assertSame($expected[1][$i], $decimalFormatter->getSymbol($i)); @@ -619,7 +619,7 @@ abstract class AbstractNumberFormatterTest extends TestCase $r = new \ReflectionProperty('Symfony\Component\Intl\NumberFormatter\NumberFormatter', 'enTextAttributes'); $r->setAccessible(true); - $expected = $r->getValue('Symfony\Component\Intl\NumberFormatter\NumberFormatter'); + $expected = $r->getValue(); for ($i = 0; $i <= 5; ++$i) { $this->assertSame($expected[1][$i], $decimalFormatter->getTextAttribute($i)); diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index c9c092a6bb..bc2546397a 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -180,7 +180,7 @@ class OptionsResolver implements Options $reflClosure = new \ReflectionFunction($value); $params = $reflClosure->getParameters(); - if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && Options::class === $class->name) { + if (isset($params[0]) && Options::class === $this->getParameterClassName($params[0])) { // Initialize the option if no previous value exists if (!isset($this->defaults[$option])) { $this->defaults[$option] = null; @@ -1217,4 +1217,13 @@ class OptionsResolver implements Options return implode('", "', $options); } + + private function getParameterClassName(\ReflectionParameter $parameter): ?string + { + if (!($type = $parameter->getType()) || $type->isBuiltin()) { + return null; + } + + return $type->getName(); + } } diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index cac049a0a6..2d47e3d256 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -183,12 +183,15 @@ class PropertyAccessor implements PropertyAccessorInterface private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, \Throwable $previous = null): void { - // the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method) - if (0 !== strpos($message, 'Argument ')) { + if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) { return; } - if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file']) { + if (\PHP_VERSION_ID < 80000) { + if (0 !== strpos($message, 'Argument ')) { + return; + } + $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface ')); $pos += \strlen($delim); $j = strpos($message, ',', $pos); @@ -197,6 +200,12 @@ class PropertyAccessor implements PropertyAccessorInterface throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath), 0, $previous); } + + if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) { + list(, $expectedType, $actualType) = $matches; + + throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given.', $expectedType, 'NULL' === $actualType ? 'null' : $actualType), 0, $previous); + } } /** @@ -384,7 +393,7 @@ class PropertyAccessor implements PropertyAccessorInterface $result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}(); } catch (\TypeError $e) { // handle uninitialized properties in PHP >= 7 - if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of the type (\w+), null returned$/', preg_quote(\get_class($object)), $access[self::ACCESS_NAME])), $e->getMessage(), $matches)) { + if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of (?:the )?type (\w+), null returned$/', preg_quote(\get_class($object)), $access[self::ACCESS_NAME])), $e->getMessage(), $matches)) { throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', \get_class($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e); } diff --git a/src/Symfony/Component/Validator/Constraints/LengthValidator.php b/src/Symfony/Component/Validator/Constraints/LengthValidator.php index f49c681bf8..104895d120 100644 --- a/src/Symfony/Component/Validator/Constraints/LengthValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LengthValidator.php @@ -48,8 +48,14 @@ class LengthValidator extends ConstraintValidator $stringValue = ($constraint->normalizer)($stringValue); } - if (!$invalidCharset = !@mb_check_encoding($stringValue, $constraint->charset)) { - $length = mb_strlen($stringValue, $constraint->charset); + try { + $invalidCharset = !@mb_check_encoding($stringValue, $constraint->charset); + } catch (\ValueError $e) { + if (!str_starts_with($e->getMessage(), 'mb_check_encoding(): Argument #2 ($encoding) must be a valid encoding')) { + throw $e; + } + + $invalidCharset = true; } if ($invalidCharset) { @@ -63,6 +69,8 @@ class LengthValidator extends ConstraintValidator return; } + $length = mb_strlen($stringValue, $constraint->charset); + if (null !== $constraint->max && $length > $constraint->max) { $this->context->buildViolation($constraint->min == $constraint->max ? $constraint->exactMessage : $constraint->maxMessage) ->setParameter('{{ value }}', $this->formatValue($stringValue)) diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index 45bd5e6ddf..c301df3622 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -376,6 +376,10 @@ class ReflectionCaster private static function addMap(array &$a, \Reflector $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) { foreach ($map as $k => $m) { + if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { + continue; + } + if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; }