Symfony version(s) affected: 3.4.29, 4.2.10, 4.3.2, 4.4-dev, 5.0-dev
Description
After upgrading to php 7.2.20 or 7.3.7, AutowirePass
starts to throw ReflectionException
s on cache warmup on installations with DoctrineBundle but without Twig and the Form and Validator components. This has been reported to the DoctrineBundle repository, but to me it rather looks like a DI issue.
How to reproduce
@teohhanhui reported that the unit tests of API platform 2.4 are failing because of a similar problem. This is what I get when I run them locally with php 7.3.7:
PHP Fatal error: During class fetch: Uncaught ReflectionException: Class Psr\SimpleCache\CacheInterface not found in /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Psr16Cache.php:27
Stack trace:
#0 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php(160): require('/Volumes/Projec...')
#1 [internal function]: Symfony\Component\Debug\DebugClassLoader->loadClass('Symfony\\Compone...')
#2 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php(21): spl_autoload_call('Symfony\\Compone...')
#3 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php(160): require('/Volumes/Projec...')
#4 [internal function]: Symfony\Component\Debug\DebugClassLoader->loadClass('Symfony\\Compone...')
#5 [internal function]: spl_autoload_call('Symfony\\Compone...')
#6 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php(78): class_exists('Symfony\\Compone...')
#7 /Volumes/Project in /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Psr16Cache.php on line 27
PHP Stack trace:
PHP 1. {main}() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/phpunit:0
PHP 2. PHPUnit\TextUI\Command::main() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/phpunit:61
PHP 3. PHPUnit\TextUI\Command->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/TextUI/Command.php:162
PHP 4. PHPUnit\TextUI\TestRunner->doRun() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/TextUI/Command.php:206
PHP 5. PHPUnit\Framework\TestSuite->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:652
PHP 6. PHPUnit\Framework\TestSuite->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestSuite.php:746
PHP 7. PHPUnit\Framework\DataProviderTestSuite->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestSuite.php:746
PHP 8. ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Filter\BooleanFilterTest->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestSuite.php:746
PHP 9. PHPUnit\Framework\TestResult->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestCase.php:796
PHP 10. ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Filter\BooleanFilterTest->runBare() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestResult.php:693
PHP 11. ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Filter\BooleanFilterTest->setUp() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestCase.php:838
PHP 12. Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::bootKernel() /Volumes/Projects/OpenSource/api-platform-core/src/Test/DoctrineOrmFilterTestCase.php:58
PHP 13. AppKernel->boot() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/framework-bundle/Test/KernelTestCase.php:69
PHP 14. AppKernel->initializeContainer() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/http-kernel/Kernel.php:133
PHP 15. AppKernel->dumpContainer() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/http-kernel/Kernel.php:571
PHP 16. Symfony\Component\DependencyInjection\Dumper\PhpDumper->dump() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/http-kernel/Kernel.php:740
PHP 17. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addServices() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:220
PHP 18. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addService() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:860
PHP 19. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addInlineService() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:725
PHP 20. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addServiceInstance() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:831
PHP 21. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addNewInstance() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:529
PHP 22. Symfony\Component\DependencyInjection\Dumper\PhpDumper->dumpValue() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:913
PHP 23. Symfony\Component\DependencyInjection\Definition->getErrors() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:1581
PHP 24. Symfony\Component\DependencyInjection\Compiler\AutowirePass->Symfony\Component\DependencyInjection\Compiler\{closure:/Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:384-386}() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Definition.php:908
PHP 25. Symfony\Component\DependencyInjection\Compiler\AutowirePass->createTypeNotFoundMessage() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:385
PHP 26. Symfony\Component\DependencyInjection\Compiler\AutowirePass->createTypeAlternatives() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:404
PHP 27. Symfony\Component\DependencyInjection\Compiler\AutowirePass->populateAvailableTypes() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:430
PHP 28. Symfony\Component\DependencyInjection\Compiler\AutowirePass->populateAvailableType() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:322
PHP 29. Symfony\Component\DependencyInjection\ContainerBuilder->getReflectionClass() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:336
PHP 30. Symfony\Component\Config\Resource\ClassExistenceResource->isFresh() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/ContainerBuilder.php:353
PHP 31. class_exists() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php:78
PHP 32. spl_autoload_call() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php:78
PHP 33. Symfony\Component\Debug\DebugClassLoader->loadClass() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php:78
PHP 34. require() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php:160
PHP 35. spl_autoload_call() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php:21
PHP 36. Symfony\Component\Debug\DebugClassLoader->loadClass() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php:21
PHP 37. require() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php:160
Fatal error: During class fetch: Uncaught ReflectionException: Class Psr\SimpleCache\CacheInterface not found in /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Psr16Cache.php:27
Stack trace:
#0 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php(160): require('/Volumes/Projec...')
#1 [internal function]: Symfony\Component\Debug\DebugClassLoader->loadClass('Symfony\\Compone...')
#2 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php(21): spl_autoload_call('Symfony\\Compone...')
#3 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php(160): require('/Volumes/Projec...')
#4 [internal function]: Symfony\Component\Debug\DebugClassLoader->loadClass('Symfony\\Compone...')
#5 [internal function]: spl_autoload_call('Symfony\\Compone...')
#6 /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php(78): class_exists('Symfony\\Compone...')
#7 /Volumes/Project in /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Psr16Cache.php on line 27
Call Stack:
0.0003 404976 1. {main}() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/phpunit:0
0.0193 1609784 2. PHPUnit\TextUI\Command::main() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/phpunit:61
0.0193 1609896 3. PHPUnit\TextUI\Command->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/TextUI/Command.php:162
1.4202 36554680 4. PHPUnit\TextUI\TestRunner->doRun() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/TextUI/Command.php:206
1.4628 37159168 5. PHPUnit\Framework\TestSuite->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:652
11.2949 67571272 6. PHPUnit\Framework\TestSuite->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestSuite.php:746
11.2967 67571800 7. PHPUnit\Framework\DataProviderTestSuite->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestSuite.php:746
11.2970 67572328 8. ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Filter\BooleanFilterTest->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestSuite.php:746
11.2970 67572328 9. PHPUnit\Framework\TestResult->run() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestCase.php:796
11.2971 67572328 10. ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Filter\BooleanFilterTest->runBare() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestResult.php:693
11.2972 67588920 11. ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Filter\BooleanFilterTest->setUp() /Volumes/Projects/OpenSource/api-platform-core/vendor/phpunit/phpunit/src/Framework/TestCase.php:838
11.2972 67588920 12. Symfony\Bundle\FrameworkBundle\Test\KernelTestCase::bootKernel() /Volumes/Projects/OpenSource/api-platform-core/src/Test/DoctrineOrmFilterTestCase.php:58
11.2973 67589320 13. AppKernel->boot() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/framework-bundle/Test/KernelTestCase.php:69
11.3328 67822144 14. AppKernel->initializeContainer() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/http-kernel/Kernel.php:133
13.4362 87715560 15. AppKernel->dumpContainer() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/http-kernel/Kernel.php:571
13.4398 88185024 16. Symfony\Component\DependencyInjection\Dumper\PhpDumper->dump() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/http-kernel/Kernel.php:740
13.8365 92596272 17. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addServices() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:220
13.8404 92758624 18. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addService() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:860
13.8405 92760224 19. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addInlineService() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:725
13.8405 92760688 20. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addServiceInstance() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:831
13.8406 92761104 21. Symfony\Component\DependencyInjection\Dumper\PhpDumper->addNewInstance() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:529
13.8406 92761560 22. Symfony\Component\DependencyInjection\Dumper\PhpDumper->dumpValue() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:913
13.8406 92761936 23. Symfony\Component\DependencyInjection\Definition->getErrors() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Dumper/PhpDumper.php:1581
13.8406 92761936 24. Symfony\Component\DependencyInjection\Compiler\AutowirePass->Symfony\Component\DependencyInjection\Compiler\{closure:/Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:384-386}() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Definition.php:908
13.8406 92761936 25. Symfony\Component\DependencyInjection\Compiler\AutowirePass->createTypeNotFoundMessage() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:385
13.8406 92762048 26. Symfony\Component\DependencyInjection\Compiler\AutowirePass->createTypeAlternatives() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:404
13.8406 92762048 27. Symfony\Component\DependencyInjection\Compiler\AutowirePass->populateAvailableTypes() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:430
13.8852 93801152 28. Symfony\Component\DependencyInjection\Compiler\AutowirePass->populateAvailableType() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:322
13.8852 93801152 29. Symfony\Component\DependencyInjection\ContainerBuilder->getReflectionClass() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/Compiler/AutowirePass.php:336
13.8852 93801232 30. Symfony\Component\Config\Resource\ClassExistenceResource->isFresh() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/dependency-injection/ContainerBuilder.php:353
13.8852 93801416 31. class_exists() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php:78
13.8852 93801496 32. spl_autoload_call() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php:78
13.8852 93801576 33. Symfony\Component\Debug\DebugClassLoader->loadClass() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/config/Resource/ClassExistenceResource.php:78
13.8856 93804456 34. require('/Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php') /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php:160
13.8858 93805248 35. spl_autoload_call() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php:21
13.8858 93805312 36. Symfony\Component\Debug\DebugClassLoader->loadClass() /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Simple/Psr6Cache.php:21
13.8866 93845512 37. require('/Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/cache/Psr16Cache.php') /Volumes/Projects/OpenSource/api-platform-core/vendor/symfony/debug/DebugClassLoader.php:160
I've managed to build a minimal reproducer.
https://github.com/derrabus/symfony-32395-reproducer
The problem seems to be ClassExistenceResource
from the Config component. When calling isFresh()
, a temporary class loader is registered:
If a class cannot be loaded because its parent is not found, a ReflectionException
is thrown:
Previously, this exception could be caught and handled. Since php 7.2.20/7.3.7 however, php seems to catch exceptions thrown during class loading and turns them into a fatal error that cannot be caught.
FYI: It is caused by https://bugs.php.net/bug.php?id=76980 which was reported because of https://github.com/symfony/symfony/issues/28748
When a class implements an interface that can not be found, PHP 7.2.20/7.3.7 throws an exception as well now. See https://github.com/symfony/symfony/issues/32396
When a class implements an interface that can not be found, PHP 7.2.20/7.3.7 throws an exception as well now. See #32396
No, php itself has always triggered an uncatchable fatal error, see https://3v4l.org/jogPE
The trick in ClassExistenceResource
was to register a temporary proxy class loader that throws an exception instead that could be caught and handled. This does not work anymore, see https://3v4l.org/sSSJQ
The new php bahavior seems fine to me. Can't we fix our code to not rely on such strange behavior?
The trick in ClassExistenceResource was to register a temporary proxy class loader that throws an exception instead that could be caught and handled.
Correct, and that worked fine in most situation.
The new php bahavior seems fine to me. Can't we fix our code to not rely on such strange behavior?
The error is now fatal, right? Why is it better, when the previous behavior allowed some resilience? About the strange code, sure, PHP is a runtime before a language. So: which code can we write to circumvent a fatal error?
I think we should ask @nikic to consider reverting the patch on PHP. There are many situations like this one in Symfony apps that used to work seamlessly, but won't now.
I hope I'm missing something and there's a workaround :)
The optional services might not rely on the container optimization removing unused services but those services should only be loaded then the dependency is present. But sadly many bundles would be affected.
I think we should ask @nikic to consider reverting the patch on PHP. There are many situations like this one in Symfony apps that used to work seamlessly, but won't now.
That depends on what you're asking for here. I can revert this change from 7.2 and 7.3. Enforcing this is not critical for those branches and if it causes breakage in Symfony I have no problem with reverting it.
For PHP 7.4 though this change is going to stay in some form. I can invest some effort to avoid throwing a fatal error, but if the request here is to restore the old behavior where you say Foo implements Bar
and you can end up with a class Foo
that does not actually implement Bar
by throwing an exception during autoloading, then no, I'm not going to restore that behavior.
Reverted from 7.2 and 7.3 via https://github.com/php/php-src/commit/22ed362810c1b3a5ecb54ebd1d50d804c7fc3159, this change is only in 7.4 now.
I can invest some effort to avoid throwing a fatal error
Having thought about this for a bit, I think that covariance support in PHP 7.4 effectively makes this impossible. Due to cyclic dependencies, we sometimes have to link classes under the provision that other classes will either also successfully link or else be unusable. This means that we can't just unregister the class stub if a parent/interface is not found, because then it would be possible to register a class with different methods under the same name and thus violate variance assumptions. The only thing we could do is leave behind a dead class stub, such that the class can neither be used, nor a class using the same name declared, which seems like a pretty bad idea.
@nikic Can we find another way to make autoloading failures recoverable somehow? To stay in your example, what the ClassExistenceResource
tries to do here is to find out if the class Foo
is available. If the answer is no because the interface Bar
is missing, that's fine. Nobody wants an incomplete class in that case.
@derrabus I don't think so. Let me try to give a specific example of the problem I have in mind, though it's somewhat convoluted:
// A.php
class A {
public function foo($x): B {}
}
// B.php
class B extends A {
public function foo($x): C {}
}
// C.php
class C extends B implements DoesNotExist {
}
// main.php
new C;
What happens now is the following:
C
. The class C
is registered provisionally.B
. The class B
is registered provisionally.A
. The class A
is registered provisionally.A
is verified (trivially) and registered fully.B
is verified and registered fully (and may already be used). During the verification it makes use of the fact that C
is a subclass of B
, otherwise the return types would not be covariant.DoesNotExist
, which throws an exception.After these steps have happened ... what can we do now? We can't fully register the class C
, because the interface it implements does not exist. We can't remove the provisionally registered class C
either, because then a new class C
that is not a subclass of B
could be registered, and thus violate the variance assumption we made above.
I don't really see a solution here that would allow us to gracefully recover while performing inheritance :(
I think the correct thing to do is to remove both B
and C
, as they're both invalid.
@teohhanhui The class B
cannot be removed at the point where we realize that the interface does not exist, because the class could already be in use. While mixing declarations and code goes against modern coding guidelines, from the language perspective writing code like this is allowed:
// B.php
class B extends A {
public function foo($x): C {}
}
$b = new B;
Of course, we can't drop the class B
after someone has already created an object of it, or similar (a more "typical" use is via class_alias.)
Does it make sense then to only fail if class B
is in use (provided there's a way to tell)?
Thank you for the detailed explanation, @nikic! Return type contravariance really complicates this. 😕
Hey @derrabus , glad that YOU are on it ;) !! We're deploying on Heroku and it's using PHP 7.2.20 / PHP 7.3.7. There's no simple option to change that. The "-latest" official docker hub release for PHP also stays at .19 / .6. Effect: we currently cannot deploy anything. Is there a chance that this is rolled back very soon (@nikic) or fixed by any change in Symfony? I'm a little shocked that this "breaking change" in PHP simply went upstream since Symfony+Doctrine is for sure not an unusual use / test case in the PHP world... 😱
@elmariachi111 I don't know how Heroku handles a situation like this, but there has to be a way to deploy to an older php release. Have you contacted their support yet?
If there really isn't another way, my suggestion would be:
symfony/form
, symfony/validator
and psr/simple-cache
are hot candidates).That'll bloat your vendors folder, but you should be able to run your application again. Please remember to revert that change once php 7.2.21 and 7.3.8 are out.
yep, that worked -> adding symfony/form contained the FormTypeGuesserInterface
that was supposedly missing but set as return type in Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser
:D
Another solution would be fixing the doctrine bundle with registering the SymfonyBridge\Doctrine\Form\DoctrineOrmTypeGuesser only as a service if the FormTypeGuesserInterface exists.
@smoench that would be an option, yes. However, there are a few more such services that were ok previously because they would be removed from the container and were never used. If this can't be solved in the DI container, I'm open for such a change, but I'd rather not do that preemptively if it can be avoided.
Also, this issue affects other bundles as well, such as API platform.
I know many other bundles would be affected. Another option would be disable autowire on those affected services. But lets wait if PHP 7.4 could provide us something.
I am experiencing this same issue after updating to 7.2.20 this morning. FYI I do have the Twig, Form, and Validator dependencies that are mentioned in the OP. I am seeing the error thrown by a Test class in a custom vendor dependency that my team is responsible for--I am not sure why a dependency's test files are being parsed for caching.
Anyways, after reading the comments above, I am slightly confused. Do we think solving this requires making a change to Symfony or to PHP itself? If the later, are we really thinking the fix would only be deployed to 7.4.x and not 7.2.x and 7.3.x, or am I misundertsanding your most recent post @smoench? If I'm not, that would be highly unfortunate...
@arderyp The issue is already resolved in the upcoming PHP 7.2 and 7.3 releases. The remaining discussion is about what to do for PHP 7.4, where under my current understanding a change on the Symfony side will be necessary.
ahh, okay @nikic, thanks a lot for the clarification, and thanks everyone else for sorting this issue out.
Instead of using PHP RefelctionClass/class_exists which triggering autoload we might could adapt https://github.com/Roave/BetterReflection. It analyse code without triggering autoload.
You can reflect on classes that are not already loaded, without loading them
Ability to reflect on classes directly from a string of PHP code
@derrabus Any updates on this issue?
Instead of using PHP RefelctionClass/class_exists which triggering autoload we might could adapt https://github.com/Roave/BetterReflection. It analyse code without triggering autoload.
And it's even compatible with PHP's Reflection API: https://github.com/Roave/BetterReflection/blob/master/docs/compatibility.md
@radyash see https://github.com/symfony/symfony/issues/32395#issuecomment-510185363
This bug takes 5 hours to get me here ;/
Just for information, I experience this bug because of symfony/security-bundle
which enforce a few services definitions to be loaded for various templating engines. In the SecurityExtension
class it calls $loader->load('templating_php.xml');
which leads the container to have services which references classes from the symfony/templating
component (which is not installed).
@pounard Hmm... I'm guessing you're on Symfony 4.2. Looks like it's been fixed already: https://github.com/symfony/symfony/pull/32379
@teohhanhui it's not released yet
@teohhanhui yes I'm using 4.2 @Tobion thanks for the info.
This topic looks like collection of complaints, but I for one am glad this PHP fix helped us discover this. It uncovered wrongly configured DI, with missing exclude folders in our case, unnecessarily making DI crawl through them. Additionally, these classes shouldn't have even be available in production composer classmap, since they are working only when no --no-dev
is used. So I applaud @nikic for keeping this in some form in PHP 7.4
@nikic regarding your example from above https://github.com/symfony/symfony/issues/32395#issuecomment-509593220
Would it be possible to mark a provisionally registered class as “in use” as soon as it is referenced somewhere?
This way we could determine in an exception case if it is OK to remove the provisionally registered class and gracefully recover or if we have to throw a fatal error.
In your example this would mean that once class B
is verified it would mark class C
as “in use”. When loading DoesNotExist
throws the exception then, we would throw a fatal error because class C
is “in use”.
This way we could gracefully recover for most cases while resulting in a fatal error for the impossible cases, which sounds like a perfect trade-off to me.
For reference, the core logic we need here is a way to make class checks resilient to missing parent classes. This means only these functions are concerned: get_class_methods, get_class_vars, get_parent_class, is_a, is_subclass_of, class_exists, class_implements, class_parents, trait_exists, defined, interface_exists, method_exists, property_exists, is_callable. We need to call them and have them return false in the case that currently crashes.
I don't see why, it's ok to make DI crash when trying to crawl through invalid classes
For anyone in the dark regarding how to fix this if you're _NOT_ running an automated, out-of-the-box pipeline, i.e. not on Heroku: https://github.com/docker-library/php/issues/864#issuecomment-511036456
If you're not on docker, kick your PHP version down one notch to 7.2.19
or 7.3.6
.
Would it be possible to mark a provisionally registered class as “in use” as soon as it is referenced somewhere?
@nikic this proposal seems like a nice idea, WDYT? The current situation is pretty scary for 7.4: it's quite common to check for optional dependencies with a class_exists(), with resiliency relying on the possibility to recover from missing parents.
it's ok to make DI crash when trying to crawl through invalid classes
@ostrolucky this logic exists in Config and in Cache components. Fixing the symptoms in DI still leaves the root issue open, which is what we're discussing here.
I have the same issue but with FixturesBundle
PHP Fatal error: During class fetch: Uncaught ReflectionException: Class DoctrineBundle\FixturesBundle\Fixture not found
poc is here:
https://github.com/mmarton/symfony-php7.2.20
might be a php bug related to the fix:
https://bugs.php.net/bug.php?id=76980
I have this issue when upgrading to php 7.2.20
PHP Fatal error: During class fetch: Uncaught ReflectionException: Class SymfonyComponent\Templating\Helper\Helper not found in /var/www/html/project/vendor/symfony/security-bundle/Templating/Helper/LogoutUrlHelper.php:23
class
LogoutUrlHelper extends Helper <<< class Helper does not exists
7.3.8RC1 was tagged today http://git.php.net/?p=php-src.git;a=tag;h=27265a358a4f0d505a192607cbd26d60862ed6cf
7.3.8RC1 was tagged today
And 7.2.21 RC1 as well. Yay. 🎉
I try to find a workaround with composer config platform but it dosn't work for "php" : "7.2.19" with 7.2.20 installed !
Hey @karousn , the problem in the PHP version, not in Composer dependencies. Composer config platform version only affects installed dependencies. Just upgrade to 7.2.21 or downgrade to 7.2.19 your PHP version and do nothing in your project
Hey @karousn , the problem in the PHP version, not in Composer dependencies. Composer config platform version only affects installed dependencies. Just upgrade to 7.2.21 or downgrade to 7.2.19 your PHP version and do nothing in your project
How to downgrade 7.2.20 to 7.2.19 ? i can't find any example :(
i'm not using docker
@arturslogins, you might have to uninstall and reinstall, which could be a pain if you have lots of complexity in dependencies and configurations. Hopefully 7.2.21/7.3.8 are tagged and published soon (I don't know how long it typically takes after RCs are tagged)
Hey @karousn , the problem in the PHP version, not in Composer dependencies. Composer config platform version only affects installed dependencies. Just upgrade to 7.2.21 or downgrade to 7.2.19 your PHP version and do nothing in your project
Thanks for explanation @bocharsky-bw , so after some search to downgrade the php version to 7.2.19 you have two choices :
As example :
apt-cache policy php7.2
# to check if you have php7.2.19 in version table
sudo nano /etc/apt/preferences.d/php72
# create a file to add preferences
Put this in
Package: php7.2*
Pin: version 7.2.19-0ubuntu0.18.04.1
Pin-Priority: 1200
After that you need to update & upgrade
sudo apt update -y
sudo apt upgrade
it well be the same for 7.3 version.
Hey @karousn , the problem in the PHP version, not in Composer dependencies. Composer config platform version only affects installed dependencies. Just upgrade to 7.2.21 or downgrade to 7.2.19 your PHP version and do nothing in your project
Thanks for explanation @bocharsky-bw , so after some search to downgrade the php version to 7.2.19 you have two choices :
- Wait for oerdnj/deb.sury.org#1208 if you used as ppa under debian fork distribution
- Do the Apt pinning by your self, if you realy understand what you do.
As example :
apt-cache policy php7.2
# to check if you have php7.2.19 in version table
sudo nano /etc/apt/preferences.d/php72
# create a file to add preferencesPut this in
Package: php7.2*
Pin: version 7.2.19-0ubuntu0.18.04.1
Pin-Priority: 1200After that you need to update & upgrade
sudo apt update -y
sudo apt upgrade
it well be the same for 7.3 version.
Thanks for replay
@karousn Unfortunately debian repos of sury.org host only the latest version of PHP :(
@nikic wouldn't loading the class B also load the class C to perform variance checks, before you instantiate the B object ?
Hey @karousn , the problem in the PHP version, not in Composer dependencies. Composer config platform version only affects installed dependencies. Just upgrade to 7.2.21 or downgrade to 7.2.19 your PHP version and do nothing in your project
Thanks for explanation @bocharsky-bw , so after some search to downgrade the php version to 7.2.19 you have two choices :
* Wait for [oerdnj/deb.sury.org#1208](https://github.com/oerdnj/deb.sury.org/issues/1208) if you used as ppa under debian fork distribution * Do the [Apt pinning](https://wiki.debian.org/AptPreferences) by your self, if you realy understand what you do.
As example :
apt-cache policy php7.2
# to check if you have php7.2.19 in version table
sudo nano /etc/apt/preferences.d/php72
# create a file to add preferencesPut this in
Package: php7.2*
Pin: version 7.2.19-0ubuntu0.18.04.1
Pin-Priority: 1200After that you need to update & upgrade
sudo apt update -y
sudo apt upgrade
it well be the same for 7.3 version.
@karousn this only works if you haven't already updated to 7.2.20 though, right? In other words, this won't roll your PHP back, but prevent the 7.2.19 PHP from being upgraded to 7.2.20. Or is that wrong?
@arderyp : no it will also roll back php version if you installed 7.2.20 exactly when you execute sudo apt upgrade
it will ask you for downgrade your php version to 7.2.19
@karousn, good to know, thanks for confirming!
For those who can't wait and are under debian 9
using sury.org
depot. I manage to downgrade my php installation with the cached debs into /var/cache/apt/archive
:
sudo dpkg -i /var/cache/apt/archives/php7.2-*_7.2.19*.deb
Then normally you have to sudo apt-mark hold php7.2-*
to avoid upgrade
but maybe we just have to wait few days to be able to upgrade to php 7.2.21
.
This will works only if you need php-fpm
and php-cli
but if you are using apache
then you have to also install the libapache2-mod-php7.2
deb and dependancies
I've ran a quick test with my reproducer on php 7.3.8RC1. Looks like this issue is gone. 🎉
(edit: 7.2.21RC1 is fine as well.)
All right, before this evolves into a StackOverflow thread on how to install specific php versions on various platforms:
For all people using homebrew (on MacOS), you can use this formula.
Copying it to your Formula folder will allow you to install [email protected]
(instead of only the [email protected]
option which installs 7.2.20).
Simply run this:
curl https://gist.githubusercontent.com/bossan/1cad9a8819808d1e684898af38541e52/raw/e05632cf98d9aff4f489422f4413d2a0678abfd0/[email protected] > /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/[email protected] && brew install [email protected]
api-platform users (im on mac using brew 7.3.7) - I had to add this to my composer.json file and all works after an update
"symfony/form": "4.2.*",
I still do not understand. What if we run symfony 4.1 and want to upgrade to php 7.3, installing form package only create more issues like upgrading symfony + 60+ packages which may take days.
Is there any soft salvation?
Error: During class fetch: Uncaught ReflectionException: Class Symfony\Component\Form\FormTypeGuesserInterface not found in /var/www/hint/vendor/symfony/doctrine-bridge/Form/DoctrineOrmTypeGuesser.php:25
Stack trace:
#0 /var/www/hint/vendor/symfony/debug/DebugClassLoader.php(145): require('/var/www/hint/v...')
#1 [internal function]: Symfony\Component\Debug\DebugClassLoader->loadClass('Symfony\\Bridge\\...')
#2 [internal function]: spl_autoload_call('Symfony\\Bridge\\...')
#3 /var/www/hint/vendor/symfony/config/Resource/ClassExistenceResource.php(76): class_exists('Symfony\\Bridge\\...')
#4 /var/www/hint/vendor/symfony/dependency-injection/ContainerBuilder.php(349): Symfony\Component\Config\Resource\ClassExistenceResource->isFresh(0)
#5 /var/www/hint/vendor/symfony/dependency-injection/Compiler/AutowirePass.php(312): Symfony\Component\DependencyInjection\ContainerBuilder->getReflectionClass('Symfony\\Bridge\\...', false)
#6 /var/www/hint/vendor/symfony/dependency-injection/Compiler/AutowirePass.php(298): Symfony\Com
The problem isn't in symfony, it's in the latest php releases.
If you encounter this problem, stay on php 7.2.19 or 7.3.6 untill 7.2.21 and 7.3.8 are released and packaged by your favorite package manager. https://github.com/symfony/symfony/issues/32395#issuecomment-512781215
@mmarton I saw php bug report, is there any solution except installing form component?
@BonBonSlick Just wait for PHP 7.2.21 or 7.3.8
@teohhanhui got to downgrade back to php7.2. < 7.20 :(
@BonBonSlick installing form component isn't even fix it every time. I have this issue with doctrine fixtures bundle.
You could try to separate service loading for any services that depend on any composer dev package, and only include that config if app_env=dev. And event this could fail if the form component is a dependency of a dependency...
@mmarton too tricky, just rolled back already
For Ubuntu and Debian users you can now update your php binaries, oerdnj fix it :
https://github.com/oerdnj/deb.sury.org/issues/1208#issuecomment-515014129
Filed a feature request in PHP:
https://bugs.php.net/bug.php?id=78351
Remove Entity from exclude list in service.yaml fix for me.
`e̶x̶c̶l̶u̶d̶e̶:̶ ̶'̶.̶.̶/̶s̶r̶c̶/̶{̶D̶e̶p̶e̶n̶d̶e̶n̶c̶y̶I̶n̶j̶e̶c̶t̶i̶o̶n̶,̶E̶n̶t̶i̶t̶y̶,̶M̶i̶g̶r̶a̶t̶i̶o̶n̶s̶,̶T̶e̶s̶t̶s̶,̶K̶e̶r̶n̶e̶l̶.̶p̶h̶p̶}̶'̶`
`exclude: '../src/{DependencyInjection,Migrations,Tests,Kernel.php}'`
https://github.com/php/php-src/releases
7.2.21 & 7.3.8 released
released two days ago, and yet still now showing on the official PHP downloads page: https://www.php.net/downloads.php
hopefully the various distros will distribute the updates soon.
released two days ago, and yet still now showing on the official PHP downloads page: https://www.php.net/downloads.php
hopefully the various distros will distribute the updates soon.
It's tagged but not released just yet. Releases tend to happen on Thursdays if I'm not mistaken, so likely tomorrow :)
PHP 7.2.21 doesn't include 78351 as a fixed issue at at https://www.php.net/ChangeLog-7.php#7.2.21, and https://bugs.php.net/bug.php?id=78351 is still listed as open - is there any way of verifying it's been fixed in there?
(Admittedly I could rebuild my system & test but it seems more efficient to understand if there's a canonical way of seeing if it's gone into PHP 7.2 master)
It contains the revert of the fix that caused the issue.
https://github.com/php/php-src/commit/22ed362810c1b3a5ecb54ebd1d50d804c7fc3159
Excellent, thanks @mmarton
looks like the Remi repo is distributing the new releases, just now waiting on Ondrej for the Debian/Ubuntu users.
@arderyp Ondřej provides patched packages since Jul 25 as per https://github.com/oerdnj/deb.sury.org/issues/1208#issuecomment-515014129
@discordier, right, patched, but not latest releases
Closing as this has been fixed in PHP.
For PHP 7.4 support, see #32995
Most helpful comment
That depends on what you're asking for here. I can revert this change from 7.2 and 7.3. Enforcing this is not critical for those branches and if it causes breakage in Symfony I have no problem with reverting it.
For PHP 7.4 though this change is going to stay in some form. I can invest some effort to avoid throwing a fatal error, but if the request here is to restore the old behavior where you say
Foo implements Bar
and you can end up with a classFoo
that does not actually implementBar
by throwing an exception during autoloading, then no, I'm not going to restore that behavior.