Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Config] ClassExistenceResource::isFresh() throws fatal error on php 7.2.20/7.3.7 if a parent class is missing #32395

Closed
derrabus opened this issue Jul 5, 2019 · 82 comments

Comments

@derrabus
Copy link
Member

derrabus commented Jul 5, 2019

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 ReflectionExceptions 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

@derrabus
Copy link
Member Author

derrabus commented Jul 5, 2019

@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

derrabus added a commit to derrabus/symfony-32395-reproducer that referenced this issue Jul 6, 2019
@derrabus
Copy link
Member Author

derrabus commented Jul 7, 2019

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 (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
}

If a class cannot be loaded because its parent is not found, a ReflectionException is thrown:

$e = new \ReflectionException("Class $class not found");

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.

@derrabus derrabus changed the title [DependencyInjection] AutowirePass throws ReflectionException on php 7.2.20/7.3.7 [Config] ClassExistenceResource::isFresh() throws fatal error on php 7.2.20/7.3.7 if a parent class is missing Jul 7, 2019
@smoench
Copy link
Contributor

smoench commented Jul 8, 2019

FYI: It is caused by https://bugs.php.net/bug.php?id=76980 which was reported because of #28748

@wimg
Copy link
Contributor

wimg commented Jul 8, 2019

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

@derrabus
Copy link
Member Author

derrabus commented Jul 8, 2019

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

@Tobion
Copy link
Member

Tobion commented Jul 8, 2019

The new php bahavior seems fine to me. Can't we fix our code to not rely on such strange behavior?

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Jul 9, 2019

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 :)

@smoench
Copy link
Contributor

smoench commented Jul 9, 2019

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.

@nikic
Copy link
Contributor

nikic commented Jul 9, 2019

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.

php-pulls pushed a commit to php/php-src that referenced this issue Jul 9, 2019
This reverts commit 35353dc.

This changes causes issues for Symfony, see
symfony/symfony#32395. I'm reverting it
from PHP 7.2 and PHP 7.3 and only leaving it in PHP 7.4.
@nikic
Copy link
Contributor

nikic commented Jul 9, 2019

Reverted from 7.2 and 7.3 via php/php-src@22ed362, this change is only in 7.4 now.

@nikic
Copy link
Contributor

nikic commented Jul 9, 2019

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.

@derrabus
Copy link
Member Author

derrabus commented Jul 9, 2019

@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.

@nikic
Copy link
Contributor

nikic commented Jul 9, 2019

@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:

  1. Autoload C. The class C is registered provisionally.
  2. Autoload the parent B. The class B is registered provisionally.
  3. Autoload the parent A. The class A is registered provisionally.
  4. The class A is verified (trivially) and registered fully.
  5. The class 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.
  6. Autoload the interface 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 :(

@teohhanhui
Copy link
Contributor

I think the correct thing to do is to remove both B and C, as they're both invalid.

@nikic
Copy link
Contributor

nikic commented Jul 9, 2019

@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.)

@teohhanhui
Copy link
Contributor

teohhanhui commented Jul 9, 2019

Does it make sense then to only fail if class B is in use (provided there's a way to tell)?

@derrabus
Copy link
Member Author

derrabus commented Jul 9, 2019

Thank you for the detailed explanation, @nikic! Return type contravariance really complicates this. 😕

@elmariachi111
Copy link

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... 😱

@derrabus
Copy link
Member Author

derrabus commented Jul 9, 2019

@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:

  • Have a look at the error message you're getting. It should complain about a missing class or interface.
  • Find out, which package it belongs to (symfony/form, symfony/validator and psr/simple-cache are hot candidates).
  • Add that package to your dependencies.
  • If Flex makes any changes to your app because of the added packages, revert them.

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.

@BonBonSlick
Copy link

@mmarton too tricky, just rolled back already

@Prometee
Copy link

For Ubuntu and Debian users you can now update your php binaries, oerdnj fix it :
oerdnj/deb.sury.org#1208 (comment)

@terjebraten-certua
Copy link
Contributor

Filed a feature request in PHP:
https://bugs.php.net/bug.php?id=78351

@anvodev
Copy link
Contributor

anvodev commented Jul 30, 2019

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}'`

@mmarton
Copy link

mmarton commented Jul 31, 2019

https://github.com/php/php-src/releases
7.2.21 & 7.3.8 released

@arderyp
Copy link
Contributor

arderyp commented Jul 31, 2019

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.

@jyggen
Copy link

jyggen commented Jul 31, 2019

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 :)

@parsingphase
Copy link

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)

@mmarton
Copy link

mmarton commented Aug 1, 2019

It contains the revert of the fix that caused the issue.
php/php-src@22ed362

@parsingphase
Copy link

Excellent, thanks @mmarton

nicolas-grekas added a commit that referenced this issue Aug 1, 2019
…g parent classes (nicolas-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

Skip tests that fatal-error on PHP 7.4 because of missing parent classes

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #32844
| License       | MIT
| Doc PR        | -

See #32395 and  https://bugs.php.net/78351 for more background.

This skips the affected tests with a warning, which means tests won't pass so we won't forget about them.

Commits
-------

c2c7ba8 Skip tests that fatal-error on PHP 7.4 because of missing parent classes
@arderyp
Copy link
Contributor

arderyp commented Aug 2, 2019

looks like the Remi repo is distributing the new releases, just now waiting on Ondrej for the Debian/Ubuntu users.

@discordier
Copy link

@arderyp Ondřej provides patched packages since Jul 25 as per oerdnj/deb.sury.org#1208 (comment)

@arderyp
Copy link
Contributor

arderyp commented Aug 2, 2019

@discordier, right, patched, but not latest releases

@nicolas-grekas
Copy link
Member

Closing as this has been fixed in PHP.
For PHP 7.4 support, see #32995

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests