symfony: Doctrine is not fully available (proxies not generated) during Twig TemplateCacheWarmer run

Hi,

we’re experiencing issues with warming up twig caches, which require the csrf token storage, which requires a doctrine proxy.

Real life scenario: implementing a memcached version of the TokenStorageInterface and using an entity to determine if the experimentation for it is enabled or not

Reproduction case: https://github.com/InterNations/cachewarmupbug A patch to the standard 2.8.4 edition: https://github.com/InterNations/cachewarmupbug/commit/eac6d0d5a2663b60e68577d6a934388cb28466d6

php app/console cache:warmup --no-debug -v
// Warming up the cache for the dev environment with debug false
PHP Warning:  require(app/cache/dev/doctrine/orm/Proxies/__CG__AppBundleEntityTest.php): failed to open stream: No such file or directory in vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php on line 209

Warning: require(app/cache/dev/doctrine/orm/Proxies/__CG__AppBundleEntityTest.php): failed to open stream: No such file or directory in vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php on line 209
PHP Fatal error:  require(): Failed opening required 'app/cache/dev/doctrine/orm/Proxies/__CG__AppBundleEntityTest.php' (include_path='.:') in vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php on line 209

Fatal error: require(): Failed opening required 'app/cache/dev/doctrine/orm/Proxies/__CG__AppBundleEntityTest.php' (include_path='.:') in vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php on line 209
# config.yml
doctrine:
    orm:
        auto_generate_proxy_classes: false
# services.xml
<service id="security.csrf.token_storage" class="AppBundle\Security\Csrf\TokenStorage\DatabaseTokenStorage">
     <argument type="service" id="doctrine.orm.entity_manager" />
</service>
# DatabaseTokenStorage
class DatabaseTokenStorage implements TokenStorageInterface
{
    public function __construct(EntityManagerInterface $em)
    {
        $proxy = $em->getReference(Test::class, 123);
    }

The call chain is: TemplateCacheWarmer -> getTwigService -> getCsrfTokenManager -> getDatabaseTokenStorage -> getEntityManager -> getEntityRepository -> Proxy of Test::class (which is not generated yet)

#0 src/AppBundle/Security/Csrf/TokenStorage/DatabaseTokenStorage.php(12): Doctrine\ORM\EntityManager->getReference('AppBundle\\Entit...', 123)
#1 app/cache/dev/appDevProjectContainer.php(1846): AppBundle\Security\Csrf\TokenStorage\DatabaseTokenStorage->__construct(Object(Doctrine\ORM\EntityManager))
#2 vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Container.php(316): appDevProjectContainer->getSecurity_Csrf_TokenStorageService()
#3 app/cache/dev/appDevProjectContainer.php(1833): Symfony\Component\DependencyInjection\Container->get('security.csrf.t...')
#4 vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Container.php(316): appDevProjectContainer->getSecurity_Csrf_TokenManagerService()
#5 app/cache/dev/appDevProjectContainer.php(2868): Symfony\Component\DependencyInjection\Container->get('security.csrf.t...', 2)
#6 vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Container.php(316): appDevProjectContainer->getTwigService()
#7 app/cache/dev/appDevProjectContainer.php(337): Symfony\Component\DependencyInjection\Container->get('twig')
#8 vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Container.php(316): appDevProjectContainer->getCacheWarmerService()
#9 vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php(62): Symfony\Component\DependencyInjection\Container->get('cache_warmer')
#10 vendor/symfony/symfony/src/Symfony/Component/Console/Command/Command.php(259): Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#11 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php(860): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#12 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php(192): Symfony\Component\Console\Application->doRunCommand(Object(Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#13 vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php(92): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#14 vendor/symfony/symfony/src/Symfony/Component/Console/Application.php(123): Symfony\Bundle\FrameworkBundle\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#15 app/console(29): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput))
#16 {main}

The cache warmers in the dumped container:

protected function getCacheWarmerService()
    {
        $a = $this->get('kernel');
        $b = $this->get('templating.filename_parser');

        $c = new \Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinder($a, $b, ($this->targetDirs[2].'/Resources'));

        return $this->services['cache_warmer'] = new \Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate(array(
0 => new \Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplatePathsCacheWarmer($c, $this->get('templating.locator')),
1 => $this->get('kernel.class_cache.cache_warmer'),
2 => new \Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer($this->get('translator')),
3 => new \Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer($this->get('router')), 
4 => new \Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer($this, $c, array()), 
5 => new \Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheWarmer($this->get('twig'), new \Symfony\Bundle\TwigBundle\TemplateIterator($a, $this->targetDirs[2], array())), 
6 => new \Symfony\Bridge\Doctrine\CacheWarmer\ProxyCacheWarmer($this->get('doctrine'))));
    }

There is a way to circumvent the issue by making the DatabaseTokenStorage service lazy, but my expectation is that Doctrine should be fully available to use once the container is built.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 16 (16 by maintainers)

Commits related to this issue

Most upvoted comments

note that auto_generate_proxy_classes: false is not the best value for production usage anymore. Recent versions of Doctrine support generating a proxy when it does not exist yet, but using the existing one otherwise (equivalent of the way Symfony builds its cache in prod, without checking existing cache for freshness). This works much better.

Btw, it wouls also be much better to avoid doing heavy Doctrine usage in the constructor of a class when using DI, as it makes instantiation heavy. $em->getReference is such heavy work as it requires loading all mapping and generating a proxy (on a side note, getReference should generally not be used, as it requires to be sure that the provided id exists in the database. Doctrine won’t validate its existence when using this method, as it is precisely the purpose of the method)