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
- feature #18728 deprecate get() for uncompiled container builders (xabbuh) This PR was squashed before being merged into the 3.2-dev branch (closes #18728). Discussion ---------- deprecate get() for... — committed to symfony/symfony by fabpot 8 years ago
- feature #18728 deprecate get() for uncompiled container builders (xabbuh) This PR was squashed before being merged into the 3.2-dev branch (closes #18728). Discussion ---------- deprecate get() for... — committed to symfony/web-profiler-bundle by fabpot 8 years ago
- feature #18728 deprecate get() for uncompiled container builders (xabbuh) This PR was squashed before being merged into the 3.2-dev branch (closes #18728). Discussion ---------- deprecate get() for... — committed to symfony/dependency-injection by fabpot 8 years ago
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)