enable_lazy_ghost_objects: true prevents detection of proxy creation errors at Symfony compile time #7352

Open
opened 2026-01-22 15:50:29 +01:00 by admin · 9 comments
Owner

Originally created by @chrif on GitHub (Mar 22, 2024).

enable_lazy_ghost_objects: false is deprecated, but when it is true, clearing the Symfony cache is not enough to get the Unable to create a proxy for a readonly class error. It is only when the proxy class is used that we get it.

So in dev we have auto_generate_proxy_classes: true and we didn't get an error when developing. I guess the problematic proxy was not used in the development use case.

In production we have auto_generate_proxy_classes: false as recommended. We also have a functional test to check that the prod Symfony cache can be cleared. It passed the test.

It's only when we released and used the proxy class that we got the Unable to create a proxy for a readonly class.

With enable_lazy_ghost_objects: false everything works fine, but we have a deprecation message.

Originally created by @chrif on GitHub (Mar 22, 2024). `enable_lazy_ghost_objects: false` is deprecated, but when it is `true`, clearing the Symfony cache is not enough to get the `Unable to create a proxy for a readonly class` error. It is only when the proxy class is used that we get it. So in dev we have `auto_generate_proxy_classes: true` and we didn't get an error when developing. I guess the problematic proxy was not used in the development use case. In production we have `auto_generate_proxy_classes: false` as recommended. We also have a functional test to check that the prod Symfony cache can be cleared. It passed the test. It's only when we released and used the proxy class that we got the `Unable to create a proxy for a readonly class`. With `enable_lazy_ghost_objects: false` everything works fine, but we have a deprecation message.
admin added the Bug label 2026-01-22 15:50:29 +01:00
Author
Owner

@stof commented on GitHub (Mar 22, 2024):

Can you share the stack trace of the exception you got to see when it was triggered in each case ?

@stof commented on GitHub (Mar 22, 2024): Can you share the stack trace of the exception you got to see when it was triggered in each case ?
Author
Owner

@chrif commented on GitHub (Mar 22, 2024):

Below you will find my initial reply, but I found the trigger in production while trying to get the full stack trace. It is the opcache preload.php file that triggers the error. If I comment opcache.preload=/app/config/preload.php in php.ini, I don't get the error, and if I keep it but set APP_DEBUG=1, I don't get the error either.

Versions

doctrine/doctrine-bundle              2.11.3
doctrine/orm                          2.19.0
symfony/framework-bundle              v6.4.4

Stack trace for the prod cache clear functional test when it fails with enable_lazy_ghost_objects: false

In InvalidArgumentException.php line 102:
                                                                               
  [Doctrine\Common\Proxy\Exception\InvalidArgumentException]                   
  Unable to create a proxy for a readonly class "App\Entity\MyEntity".              
                                                                               

Exception trace:
  at /app/vendor/doctrine/common/src/Proxy/Exception/InvalidArgumentException.php:102
 Doctrine\Common\Proxy\Exception\InvalidArgumentException::classMustNotBeReadOnly() at /app/vendor/doctrine/common/src/Proxy/ProxyGenerator.php:372
 Doctrine\Common\Proxy\ProxyGenerator->verifyClassCanBeProxied() at /app/vendor/doctrine/common/src/Proxy/ProxyGenerator.php:312
 Doctrine\Common\Proxy\ProxyGenerator->generateProxyClass() at /app/vendor/doctrine/common/src/Proxy/AbstractProxyFactory.php:146
 Doctrine\Common\Proxy\AbstractProxyFactory->generateProxyClasses() at /app/vendor/doctrine/orm/src/Proxy/ProxyFactory.php:228
 Doctrine\ORM\Proxy\ProxyFactory->generateProxyClasses() at /app/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php:60
 Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer->warmUp() at /app/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php:108
 Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate->warmUp() at /app/vendor/symfony/http-kernel/Kernel.php:545
 Symfony\Component\HttpKernel\Kernel->initializeContainer() at /app/vendor/symfony/http-kernel/Kernel.php:763
 Symfony\Component\HttpKernel\Kernel->preBoot() at /app/vendor/symfony/http-kernel/Kernel.php:126
 Symfony\Component\HttpKernel\Kernel->boot() at /app/library/Common/Application/Kernel.php:34
 App\Kernel->boot() at /app/vendor/symfony/http-kernel/Kernel.php:144
 Symfony\Component\HttpKernel\Kernel->reboot() at /app/vendor/symfony/framework-bundle/Command/CacheClearCommand.php:229
 Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand->warmup() at /app/vendor/symfony/framework-bundle/Command/CacheClearCommand.php:141
 Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand->execute() at /app/vendor/symfony/console/Command/Command.php:326
 Symfony\Component\Console\Command\Command->run() at /app/vendor/symfony/console/Application.php:1096
 Symfony\Component\Console\Application->doRunCommand() at /app/vendor/symfony/framework-bundle/Console/Application.php:126
 Symfony\Bundle\FrameworkBundle\Console\Application->doRunCommand() at /app/vendor/symfony/console/Application.php:324
 Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:80
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:175
 Symfony\Component\Console\Application->run() at /app/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php:49
 Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner->run() at /app/vendor/autoload_runtime.php:39
 require_once() at /app/application/console:8

cache:clear [--no-warmup] [--no-optional-warmers] [--maintenance-override MAINTENANCE-OVERRIDE]

In production when it fails with enable_lazy_ghost_objects: true I don't have the full stack trace. If I use APP_DEBUG=true, I don't get the error. If it's not enough, I'll have to figure this out.

<b>Fatal error</b>:  Non-readonly class Proxies\__CG__\App\Entity\MyEntity cannot extend readonly class App\Entity\MyEntity in <b>/app/var/cache/production/doctrine/orm/Proxies/__CG__AppEntityMyEntity.php</b> on line <b>8</b><br />
@chrif commented on GitHub (Mar 22, 2024): Below you will find my initial reply, but I found the trigger in production while trying to get the full stack trace. It is the [opcache `preload.php`](https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading) file that triggers the error. If I comment `opcache.preload=/app/config/preload.php` in php.ini, I don't get the error, and if I keep it but set `APP_DEBUG=1`, I don't get the error either. Versions ``` doctrine/doctrine-bundle 2.11.3 doctrine/orm 2.19.0 symfony/framework-bundle v6.4.4 ``` Stack trace for the prod cache clear functional test when it fails with `enable_lazy_ghost_objects: false` ``` In InvalidArgumentException.php line 102: [Doctrine\Common\Proxy\Exception\InvalidArgumentException] Unable to create a proxy for a readonly class "App\Entity\MyEntity". Exception trace: at /app/vendor/doctrine/common/src/Proxy/Exception/InvalidArgumentException.php:102 Doctrine\Common\Proxy\Exception\InvalidArgumentException::classMustNotBeReadOnly() at /app/vendor/doctrine/common/src/Proxy/ProxyGenerator.php:372 Doctrine\Common\Proxy\ProxyGenerator->verifyClassCanBeProxied() at /app/vendor/doctrine/common/src/Proxy/ProxyGenerator.php:312 Doctrine\Common\Proxy\ProxyGenerator->generateProxyClass() at /app/vendor/doctrine/common/src/Proxy/AbstractProxyFactory.php:146 Doctrine\Common\Proxy\AbstractProxyFactory->generateProxyClasses() at /app/vendor/doctrine/orm/src/Proxy/ProxyFactory.php:228 Doctrine\ORM\Proxy\ProxyFactory->generateProxyClasses() at /app/vendor/symfony/doctrine-bridge/CacheWarmer/ProxyCacheWarmer.php:60 Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer->warmUp() at /app/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php:108 Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate->warmUp() at /app/vendor/symfony/http-kernel/Kernel.php:545 Symfony\Component\HttpKernel\Kernel->initializeContainer() at /app/vendor/symfony/http-kernel/Kernel.php:763 Symfony\Component\HttpKernel\Kernel->preBoot() at /app/vendor/symfony/http-kernel/Kernel.php:126 Symfony\Component\HttpKernel\Kernel->boot() at /app/library/Common/Application/Kernel.php:34 App\Kernel->boot() at /app/vendor/symfony/http-kernel/Kernel.php:144 Symfony\Component\HttpKernel\Kernel->reboot() at /app/vendor/symfony/framework-bundle/Command/CacheClearCommand.php:229 Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand->warmup() at /app/vendor/symfony/framework-bundle/Command/CacheClearCommand.php:141 Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand->execute() at /app/vendor/symfony/console/Command/Command.php:326 Symfony\Component\Console\Command\Command->run() at /app/vendor/symfony/console/Application.php:1096 Symfony\Component\Console\Application->doRunCommand() at /app/vendor/symfony/framework-bundle/Console/Application.php:126 Symfony\Bundle\FrameworkBundle\Console\Application->doRunCommand() at /app/vendor/symfony/console/Application.php:324 Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:80 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:175 Symfony\Component\Console\Application->run() at /app/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php:49 Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner->run() at /app/vendor/autoload_runtime.php:39 require_once() at /app/application/console:8 cache:clear [--no-warmup] [--no-optional-warmers] [--maintenance-override MAINTENANCE-OVERRIDE] ``` In production when it fails with `enable_lazy_ghost_objects: true` I don't have the full stack trace. If I use APP_DEBUG=true, I don't get the error. If it's not enough, I'll have to figure this out. ``` <b>Fatal error</b>: Non-readonly class Proxies\__CG__\App\Entity\MyEntity cannot extend readonly class App\Entity\MyEntity in <b>/app/var/cache/production/doctrine/orm/Proxies/__CG__AppEntityMyEntity.php</b> on line <b>8</b><br /> ```
Author
Owner

@stof commented on GitHub (Mar 22, 2024):

Indeed, the new implementation of ProxyFactory does not seem to validate the compatibility of classes at proxy generation time, but only when the proxy class is used.

/cc @nicolas-grekas

@stof commented on GitHub (Mar 22, 2024): Indeed, the new implementation of ProxyFactory does not seem to validate the compatibility of classes at proxy generation time, but only when the proxy class is used. /cc @nicolas-grekas
Author
Owner

@ostrolucky commented on GitHub (Mar 22, 2024):

I guess this is an issue in var-dumper? Shall we close here?

@ostrolucky commented on GitHub (Mar 22, 2024): I guess this is an issue in var-dumper? Shall we close here?
Author
Owner

@nicolas-grekas commented on GitHub (Mar 22, 2024):

I think this is for the ORM. VarExporter's lazy objects are compatible with readonly classes. But I'm not sure the ORM takes care of them properly (either failing early or generaton readonly proxies.)

@nicolas-grekas commented on GitHub (Mar 22, 2024): I think this is for the ORM. VarExporter's lazy objects are compatible with readonly classes. But I'm not sure the ORM takes care of them properly (either failing early or generaton readonly proxies.)
Author
Owner

@derrabus commented on GitHub (Mar 26, 2024):

Can you create a reproducer for this issue?

@derrabus commented on GitHub (Mar 26, 2024): Can you create a reproducer for this issue?
Author
Owner

@chrif commented on GitHub (Mar 28, 2024):

Can you create a reproducer for this issue?

I'll make time next week for that.

@chrif commented on GitHub (Mar 28, 2024): > Can you create a reproducer for this issue? I'll make time next week for that.
Author
Owner

@beberlei commented on GitHub (Mar 28, 2024):

The error "the Unable to create a proxy for a readonly class" is only triggered when lazy ghost objects is false during ProxyFactory::generateProxyClasses. There is no other way this exception inside the ProxyGenerator can be triggered, because its from the old code.

9d1a4973ae/src/Proxy/ProxyFactory.php (L227-L229)

@beberlei commented on GitHub (Mar 28, 2024): The error "the Unable to create a proxy for a readonly class" is only triggered when lazy ghost objects is false during `ProxyFactory::generateProxyClasses`. There is no other way this exception inside the ProxyGenerator can be triggered, because its from the old code. https://github.com/doctrine/doctrine2/blob/9d1a4973ae2d8ddedd6b66e958e32ebb58cdf3d6/src/Proxy/ProxyFactory.php#L227-L229
Author
Owner

@beberlei commented on GitHub (Mar 28, 2024):

Some hints, A reproducer for the fatal error with lazy proxy should be a test with a readonly class, see @requires PHP 8.1 and other uses in the testsuite to restrict run for php with readonly classes. Call $this->_em->getReference() to create a proxy.

@beberlei commented on GitHub (Mar 28, 2024): Some hints, A reproducer for the fatal error with lazy proxy should be a test with a readonly class, see ` @requires PHP 8.1` and other uses in the testsuite to restrict run for php with readonly classes. Call `$this->_em->getReference()` to create a proxy.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7352