EasyCorp / EasyAdminBundle

EasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
MIT License
4.07k stars 1.02k forks source link

EasyAdmin is not compatible with RoadRunner PHP app server #5591

Closed speller closed 1 year ago

speller commented 1 year ago

Describe the bug

RoadRunner is a powerful replacement to the standard FPM which keeps PHP processes running. This makes PHP apps lightning-fast. The only drawback is that all PHP code must be stateless and not rely on global and static variables, all objects must be destroyed after the request. EasyAdmin seems to be not following these requirements and admin pages fail randomly with the following error:

LogicException:
Unable to add global "ea" as the runtime or the extensions have already been initialized.

  at vendor/twig/twig/src/Environment.php:769
  at Twig\Environment->addGlobal('ea', object(AdminContext))
     (vendor/easycorp/easyadmin-bundle/src/EventListener/AdminRouterSubscriber.php:87)
  at EasyCorp\Bundle\EasyAdminBundle\EventListener\AdminRouterSubscriber->onKernelRequest(object(RequestEvent), 'kernel.request', object(TraceableEventDispatcher))
     (vendor/symfony/event-dispatcher/Debug/WrappedListener.php:115)
  at Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(object(RequestEvent), 'kernel.request', object(TraceableEventDispatcher))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:206)
  at Symfony\Component\EventDispatcher\EventDispatcher->callListeners(array(object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener)), 'kernel.request', object(RequestEvent))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:56)
  at Symfony\Component\EventDispatcher\EventDispatcher->dispatch(object(RequestEvent), 'kernel.request')
     (vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php:127)
  at Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch(object(RequestEvent), 'kernel.request')
     (vendor/symfony/http-kernel/HttpKernel.php:139)
  at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
     (vendor/symfony/http-kernel/HttpKernel.php:74)
  at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
     (vendor/symfony/http-kernel/Kernel.php:184)
  at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
     (vendor/baldinof/roadrunner-bundle/src/Http/KernelHandler.php:44)
  at Baldinof\RoadRunnerBundle\Http\KernelHandler->handle(object(Request))
  at Generator->current()
     (vendor/baldinof/roadrunner-bundle/src/Http/MiddlewareStack.php:97)
  at Baldinof\RoadRunnerBundle\Http\Runner->getResponse(object(Generator), 'Baldinof\\RoadRunnerBundle\\Http\\KernelHandler::handle()')
     (vendor/baldinof/roadrunner-bundle/src/Http/MiddlewareStack.php:75)
  at Baldinof\RoadRunnerBundle\Http\Runner->handle(object(Request))
     (vendor/baldinof/roadrunner-bundle/src/Integration/Doctrine/DoctrineORMMiddleware.php:61)
  at Baldinof\RoadRunnerBundle\Integration\Doctrine\DoctrineORMMiddleware->process(object(Request), object(Runner))
  at Generator->current()
     (vendor/baldinof/roadrunner-bundle/src/Http/MiddlewareStack.php:97)
  at Baldinof\RoadRunnerBundle\Http\Runner->getResponse(object(Generator), 'Baldinof\\RoadRunnerBundle\\Integration\\Doctrine\\DoctrineORMMiddleware::process()')
     (vendor/baldinof/roadrunner-bundle/src/Http/MiddlewareStack.php:83)
  at Baldinof\RoadRunnerBundle\Http\Runner->handle(object(Request))
     (vendor/baldinof/roadrunner-bundle/src/Http/MiddlewareStack.php:37)
  at Baldinof\RoadRunnerBundle\Http\MiddlewareStack->handle(object(Request))
  at Generator->current()
     (vendor/baldinof/roadrunner-bundle/src/Worker/Worker.php:111)
  at Baldinof\RoadRunnerBundle\Worker\Worker->start()
     (vendor/baldinof/roadrunner-bundle/src/Runtime/Runner.php:27)
  at Baldinof\RoadRunnerBundle\Runtime\Runner->run()
     (vendor/autoload_runtime.php:29)
  at require_once('/app/vendor/autoload_runtime.php')
     (public/index.php:5)                

To Reproduce Try using easyadmin under RR.

It would be nice if EA will fix this.

ksn135 commented 1 year ago

:+1:

kiler129 commented 1 year ago

That isn't so much about EAB but Twig, or more so how your application uses it. EAB registers kernel.request event (\EasyCorp\Bundle\EasyAdminBundle\EventListener\AdminRouterSubscriber::onKernelRequest) and simply adds a global Twig variable. This can only happen before extensions and runtime are initialized (\Twig\ExtensionSet::initExtensions). Something is triggering this before onKernelRequest happens in your app, which means that EAB cannot add global variable. What's crucial, Twig will let you change the variable, despite the misleading addGlobal prefix. However, adding new globals is impossible once the environment initializes.

From my experience this will cause problems with a lot of libraries, as adding Twig globals in onKernelRequest is pretty standard. I would start from looking what causes the init in your case. As a dirty workaround you can initialize Twig global variable ea with a null before the extension set is compiled. You can even do that with a custom twig extension or event listener... but this is a hack ;)

javiereguiluz commented 1 year ago

@speller thanks for reporting this, but I'm afraid we can't do anything on our side. We don't use RoadRunner and we don't have resources to try it or debug this.

Luckily, according to @kiler129 message, this could be not an issue with EasyAdmin itself but more related to Twig global variables. I hope you can find a solution, even if it's a bit hackish. Sorry!

AleksSem commented 11 months ago

@javiereguiluz probably EA shouldn't use twig globals to store context data

GromNaN commented 7 months ago

Related issue https://github.com/twigphp/Twig/issues/4007

I think we need to modify the way the global variable is declared. The Twig environment should not be initialized differently depending on the request.

https://github.com/EasyCorp/EasyAdminBundle/blob/f63e526f65219f26a3add5020a6021a0f5e29e26/src/Twig/EasyAdminTwigExtension.php#L68-L74

The ea variable can be replaced with a Twig function that would to the same as AdminContextProvider::getContext(). https://github.com/EasyCorp/EasyAdminBundle/blob/f63e526f65219f26a3add5020a6021a0f5e29e26/src/Provider/AdminContextProvider.php#L27

This function signature would require the current request from the global app instead of dependency injection of RequestStack:

# src/Resources/views/crud/field/array.html.twig
- {% if ea.crud.currentAction == 'detail' %}
+ {% if ea_context(app.request).crud.currentAction == 'detail' %}

@javiereguiluz Can you re-open this issue please.