EasyAdminBundle
EasyAdminBundle copied to clipboard
Template twig error in production mode
Describe the bug I have a Symfony project where I use EasyAdmin based on docker compose. The error occurs only in the dashboard, and only in production mode. I tried testing in a clean install with no extra dependencies and configuration to make sure it wasn't my settings that were causing the problem. However, the error still doesn't go anywhere, could you also check and maybe find the problem.
To Reproduce
- Clone the setup repository.
- Run in developer mode:
docker compose build --no-cache
anddocker compose up --pull -d --wait
- In php containers, install the following composer dependencies:
easycorp/easyadmin-bundle
andsymfony/orm-pack
- Add a Dashboard controller, as well as any CRUD controller (if not already there), for example for the User entity
- Restart docker compose in production mode:
docker compose down --remove-orphans
,CADDY_MERCURE_JWT_SECRET=secret docker compose -f compose.yaml -f compose.prod.yaml build --no-cache
, andCADDY_MERCURE_JWT_SECRET=secret docker compose -f compose.yaml -f compose.prod.yaml up --pull -d --wait
- Go to the
/admin
page, and go through the CRUD pages several times, if necessary, perform actions to trigger an error.
(OPTIONAL) Additional context
This is the error in the logs:
{ "message": "Uncaught PHP Exception TypeError: \"Twig\\Environment::getTemplateClass(): Argument #1 ($name) must be of type string, null given, called in /app/vendor/twig/twig/src/Template.php on line 319\" at /app/vendor/twig/twig/src/Environment.php line 262", "context": { "exception": { "class": "TypeError", "message": "Twig\\Environment::getTemplateClass(): Argument #1 ($name) must be of type string, null given, called in /app/vendor/twig/twig/src/Template.php on line 319", "code": 0, "file": "/app/vendor/twig/twig/src/Environment.php:262" } }, "level": 500, "level_name": "CRITICAL", "channel": "request", "datetime": "2023-09-28T13:11:19.762885+00:00", "extra": {} }
I don't use Docker, so I can't help here. Let's see if anybody from the community can help debug this issue.
I am also running through the same issue. Happens only in prod and I am using symfony-docker
i'm facing the same issue too...even in a dev environment php docker image is php:8.1-fpm (v 8.1.23) symfony 6.1.12 easyadmin 4.8.4 (after upgrade from 4.7.7)
will post something more useful, if i can find anything...
maybe it's not docker related... i've found that for me removing
->overrideTemplates([ 'crud/index' => 'admin/crud/custom_entity_index.html.twig', 'crud/detail' => 'admin/crud/custom_entity_detail.html.twig' ])
makes the error go away, at least in a dev environment
also
symfony/twig-bridge v6.1.11
symfony/twig-bundle v6.1.11
twig/cssinliner-extra v3.7.1
twig/extra-bundle v3.7.1
twig/inky-extra v3.7.1
twig/intl-extra v3.7.1
twig/markdown-extra v3.7.1
twig/twig v3.7.1
But if that error might be an issue for everyone, then everyone would have it; if you have to remove it in order to work properly, then its an indication that at least something in the orchastration is not going as expected.
FWIW, I am using Docker in my custom built environment (because mine matches the clients servers technology), I do not have this problem; so maybe it is indeed related to the Symfony Docker env.
Have you checked if you have the same problem in a pure Symfony Apache-PHP environment? If required, I could share mine, too.
tested without docker on an ubuntu server with apache, php8.1-fpm and easyadmin 4.8.4 (prod APP_ENV), without overrideTemplates there's no error, with overrideTemplates it comes out
a more in-depth investigation led to verifying that my problem depended on the structure of the custom templates which did not reflect the new ones from easyadmin (specifically the use of render_detail_fields_with_tabs)
at this point, although the error is similar, it is possible that it is not connected to what was reported by @zpi12lmm ...
I had the same problem in the prod environment only. I found out that it works fine with EasyAdminBundle version 4.6.1. and this decorator for the Twig Environment https://github.com/EasyCorp/EasyAdminBundle/issues/3715#issuecomment-999616605. Saved me a lot of headache.
hello my docker fellow,
I've the same issue, I'm using the symfony dunglas template: https://github.com/dunglas/symfony-docker
I tried the twig environment work around but it didn't fix this issue. Any way to fix this without down versionned easy admin ?
I'll do some investigation about when and why this error is thrown
Hi again,
I've manage to exract the stack trace:
#0 /app/vendor/twig/twig/src/Template.php(319): Twig\\Environment->getTemplateClass()
#1 /app/var/cache/prod/twig/33/33b42a927271b040446d8c8bdb1eed5c.php(39): Twig\\Template->loadTemplate()
#2 /app/vendor/twig/twig/src/Template.php(86): __TwigTemplate_81476204e24c3fd5ef9c449cfa80e5e4->doGetParent()
#3 /app/var/cache/prod/twig/33/33b42a927271b040446d8c8bdb1eed5c.php(48): Twig\\Template->getParent()
#4 /app/vendor/twig/twig/src/Template.php(394): __TwigTemplate_81476204e24c3fd5ef9c449cfa80e5e4->doDisplay()
#5 /app/vendor/twig/twig/src/Template.php(367): Twig\\Template->displayWithErrorHandling()
#6 /app/var/cache/prod/twig/d1/d1138a024533fd5875370119c00e1040.php(42): Twig\\Template->display()
#7 /app/vendor/twig/twig/src/Template.php(394): __TwigTemplate_45572567e494b5e89e637704a58a157e->doDisplay()
#8 /app/vendor/twig/twig/src/Template.php(367): Twig\\Template->displayWithErrorHandling()
#9 /app/vendor/twig/twig/src/Template.php(379): Twig\\Template->display()
#10 /app/vendor/twig/twig/src/TemplateWrapper.php(40): Twig\\Template->render()
#11 /app/vendor/twig/twig/src/Environment.php(280): Twig\\TemplateWrapper->render()
#12 /app/vendor/symfony/framework-bundle/Controller/AbstractController.php(243): Twig\\Environment->render()
#13 /app/vendor/symfony/framework-bundle/Controller/AbstractController.php(254): Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController->renderView()
#14 /app/src/Controller/Admin/DashboardController.php(38): Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController->render()
#15 /app/vendor/symfony/http-kernel/HttpKernel.php(181): App\\Controller\\Admin\\DashboardController->index()
#16 /app/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()
#17 /app/vendor/symfony/http-kernel/Kernel.php(197): Symfony\\Component\\HttpKernel\\HttpKernel->handle()
#18 /app/vendor/runtime/frankenphp-symfony/src/Runner.php(35): Symfony\\Component\\HttpKernel\\Kernel->handle()
#19 [internal function]: Runtime\\FrankenPhpSymfony\\Runner->Runtime\\FrankenPhpSymfony\\{closure}()
#20 /app/vendor/runtime/frankenphp-symfony/src/Runner.php(30): frankenphp_handle_request()
#21 /app/vendor/autoload_runtime.php(29): Runtime\\FrankenPhpSymfony\\Runner->run()
#22 /app/public/index.php(5): require_once('...')
#23 {main}
"} []
And here is the 33b42a927271b040446d8c8bdb1eed5c.php file:
<?php
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Source;
use Twig\Template;
/* @EasyAdmin/page/content.html.twig */
class __TwigTemplate_81476204e24c3fd5ef9c449cfa80e5e4 extends Template
{
private $source;
private $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->blocks = [
'body_class' => [$this, 'block_body_class'],
'page_title' => [$this, 'block_page_title'],
'page_content' => [$this, 'block_page_content'],
'content_title' => [$this, 'block_content_title'],
'main' => [$this, 'block_main'],
];
}
protected function doGetParent(array $context)
{
// line 2
return $this->loadTemplate(twig_get_attribute($this->env, $this->source, ($context["ea"] ?? null), "templatePath", ["layout"], "method", false, false, false, 2), "@EasyAdmin/page/content.html.twig", 2);
}
protected function doDisplay(array $context, array $blocks = [])
{
$macros = $this->macros;
// line 3
$context["__internal_1b5724d1ff989db167bf5fb9925283416edb9743d6264d1a1f777ddfda4e1a6d"] = twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, ($context["ea"] ?? null), "i18n", [], "any", false, false, false, 3), "translationDomain", [], "any", false, false, false, 3);
// line 2
$this->getParent($context)->display($context, array_merge($this->blocks, $blocks));
}
// line 5
public function block_body_class($context, array $blocks = [])
{
$macros = $this->macros;
echo "page-content";
}
// line 8
public function block_page_title($context, array $blocks = [])
{
$macros = $this->macros;
}
// line 12
public function block_page_content($context, array $blocks = [])
{
$macros = $this->macros;
}
// line 15
public function block_content_title($context, array $blocks = [])
{
$macros = $this->macros;
$this->displayBlock("page_title", $context, $blocks);
}
// line 17
public function block_main($context, array $blocks = [])
{
$macros = $this->macros;
$this->displayBlock("page_content", $context, $blocks);
}
public function getTemplateName()
{
return "@EasyAdmin/page/content.html.twig";
}
public function isTraitable()
{
return false;
}
public function getDebugInfo()
{
return array ( 78 => 17, 71 => 15, 65 => 12, 59 => 8, 52 => 5, 48 => 2, 46 => 3, 39 => 2,);
}
public function getSourceContext()
{
return new Source("", "@EasyAdmin/page/content.html.twig", "/app/vendor/easycorp/easyadmin-bundle/src/Resources/views/page/content.html.twig");
}
}
I hope this help found what's wrong
I also encountered the same error using the latest version of https://github.com/dunglas/symfony-docker
Downgrading to 4.6.1 and adding the decorator class like @plantas mentioned worked for me but i wasn't happy with that.
It seems to me that this issue is caused by the FrankenPHP server that is used in the docker configuration. Going back to an older version that used separated containers for php and caddy solved the issue for me. https://github.com/dunglas/symfony-docker/tree/b5710da39cc9939c2eef4787ab50b4ee7d16e44f
@ac-shadow, I confirm that it comes from the frankenphp worker mode. But i liked the one container stuff, so I disabled it on the Dockerfile like this: (don't know if there is a "proper" way)
#...
FROM frankenphp_base AS frankenphp_prod
ENV APP_ENV=prod
# Comment this line
#ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link frankenphp/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/
# Comment this line
#COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
# ...
Thank you @tdumalin. Just tried out your solution and everything works fine now. It also fixed another unrelated issue I had before when I used the FrankenPHP server. Seems like that was also caused by the worker mode.
@ac-shadow, Can you tell me what's the other issue it solved? Juts by curiosity, for me it's solved another issue with session creation. If I mange to list all the related issues maybe it'll be easier to understand what's happening with worker mode ?
@tdumalin, The issue was a memory leak but a workaround for that is also mentioned in the docs which I missed before: https://github.com/dunglas/frankenphp/blob/main/docs/worker.md#restart-the-worker-after-a-certain-number-of-requests
I've created a simple fix that should update globals if they are different.
I can't test it in FrankenPHP at the moment, but I hope this will help. Additionally, it should be beneficial for any app servers where app states are persistent between requests (roadrunner,swoole, etc.).
Moreover, this fix will allow the use of subrequests in EasyAdmin.
Example concept:
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block main %}
<h2> Employees </h2>
{{ render('/?crudAction=index&crudControllerFqcn=App\EmployeeCrudController') }}
<h2> Organisations </h2>
{{ render('/?crudAction=index&crudControllerFqcn=App\OrganisationCrudController') }}
{% endblock %}
It was unable due to issue with AdminContext in twig, ea
variable was the same for sub requests as for master request;
Fix is just a simple listener for KernelView event:
<?php
declare(strict_types=1);
namespace App\EventListener;
use EasyCorp\Bundle\EasyAdminBundle\Twig\EasyAdminTwigExtension;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Twig\Environment;
final class RefreshAdminContext implements EventSubscriberInterface
{
public function __construct(private Environment $twig){
}
public static function getSubscribedEvents():array
{
return [
//Priority is set to 1 because it should be executed before CrudResponseListener (which has no priority, so it is 0 by default).
ViewEvent::class => ['onKernelView',1]
];
}
public function onKernelView(ViewEvent $event):void
{
$extensionGlobals = $this->twig->getExtension(EasyAdminTwigExtension::class)->getGlobals();
$twigGlobals = $this->twig->getGlobals();
foreach ($extensionGlobals as $key=>$value){
if(!isset($twigGlobals[$key]) || $twigGlobals[$key]===$value) {
continue;
}
//Update the global variable if it exists in the Twig environment and its value is different from that in the extension.
$this->twig->addGlobal($key,$value);
}
}
}
@misterx's solution didn't end up working for me. While this caused the bug to be less frequent (however, it still occurred in some marginal cases), it also caused the Admin Context in Twig to not match the context in the rest of the application in some situations, which led to highlighted menu items not matching the route, for example.
I came up with the following fix:
services.yaml
App\Service\Twig\Environment:
parent: twig
decorates: twig
calls:
- setAdminContextProvider: [ '@EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider' ]
App\Service\Twig\Environment.php
<?php
namespace App\Service\Twig;
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
use Symfony\Contracts\Service\Attribute\Required;
/**
* Refreshes EasyAdmin context between requests. Fixes problem with production worker
*
* See:
* - https://github.com/EasyCorp/EasyAdminBundle/issues/5986
* - https://github.com/dunglas/symfony-docker/issues/474
*/
class Environment extends \Twig\Environment
{
private AdminContextProvider $adminContextProvider;
#[Required]
public function setAdminContextProvider(AdminContextProvider $adminContextProvider): void
{
$this->adminContextProvider = $adminContextProvider;
}
public function getGlobals(): array
{
$globals = parent::getGlobals();
$context = $this->adminContextProvider->getContext();
if ($context !== null) {
$globals['ea'] = $context;
}
return $globals;
}
}
This does not change the fact that the solution is rather inelegant and the creators of EsayAdmin should, in my opinion, remove Twig Global and use an Twig Extension instead (e.g. replace ea
-> ea()
).
@mozkomor05 My fix is related to accessing the fresh AdminContext in Twig templates. Regarding the issue with the menu, as I see, menu highlighting is implemented in MenuItemMatcher. Therefore, it's not related to the Twig context and uses AdminContextProvider to retrieve the context from the request. Maybe there is some another service that stores state (similar to Twig\Environment with globals);
UPD: An error might be caused if the Twig\Environment::render
method is called before the kernel View event is fired.
UPD2: After reviewing the EA flow again, I think it's better to update globals after initializing AdminContext instead of waiting until the end of the request. So, I suggest changing the onKernelView event in my fix to onKernelRequest and registering the listener after AdminRouterSubscriber.
Changing the event to kernel.controller
fixed the menu matching issue. However I still come across rare situations where Twig global ea
is not accessible. I'll try to debug later. For now, the twig decorator hack works better for me.
@javiereguiluz any thoughts on moving the admin context from a global to a twig function and then deprecating the global?
Seems like it might fix this and is done pretty easily. Or is there a reason it's a global?
+1
Thanks for your attempt to solve this issue by replacing the Twig global variable by a Twig function. However, I don't like that solution for two reasons:
- I can't think of any way of making it in a BC way that doesn't break all apps that use the
ea
global variable - It doesn't really solve the problem, it just changes code to avoid it
I talked with @dunglas about this. Two quick comments:
- This error is probably caused by EasyAdmin, not Symfony or FrankenPHP ... BUT, our code is pretty standard: we're just injecting an object as a global Twig variable in an event listener. It's 100% Symfony standard code.
- Kévin mentioned that maybe we're missing some "reset" somewhere
Symfony for example has a lot of reset()
calls which were introduced to make it compatible with apps like FrankenPHP in worker mode. See for example:
- https://github.com/symfony/symfony/pull/43333/files
- https://github.com/symfony/symfony/pull/45479/files
But there are also examples where we removed the reset()
and replaced by a different solution:
- https://github.com/symfony/symfony/pull/49104/files
I tried to install FrankenPHP to reproduce the issue. I can't even run my SF + EA apps with FrankenPHP, so I can't reproduce it.
So, can anyone please give a shot to this proposal and see if we're missing some reset somewhere? Thanks!
@javiereguiluz, try Swoole, it works, https://github.com/php-runtime/swoole
@javiereguiluz, the issue will be resolved, if Twig developers will add something like RefreshGlobalsInterface for Extension.
Please check #6273 and report back if it fixes the issue for you.