src/Core/Framework/Script/Execution/ScriptExecutor.php line 51

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Script\Execution;
  3. use Psr\Log\LoggerInterface;
  4. use Shopware\Core\DevOps\Environment\EnvironmentHelper;
  5. use Shopware\Core\Framework\Adapter\Twig\Extension\PhpSyntaxExtension;
  6. use Shopware\Core\Framework\Adapter\Twig\SecurityExtension;
  7. use Shopware\Core\Framework\Adapter\Twig\TwigEnvironment;
  8. use Shopware\Core\Framework\App\Event\Hooks\AppLifecycleHook;
  9. use Shopware\Core\Framework\Log\Package;
  10. use Shopware\Core\Framework\Script\Debugging\Debug;
  11. use Shopware\Core\Framework\Script\Debugging\ScriptTraces;
  12. use Shopware\Core\Framework\Script\Exception\NoHookServiceFactoryException;
  13. use Shopware\Core\Framework\Script\Exception\ScriptExecutionFailedException;
  14. use Shopware\Core\Framework\Script\Execution\Awareness\AppSpecificHook;
  15. use Shopware\Core\Framework\Script\Execution\Awareness\HookServiceFactory;
  16. use Shopware\Core\Framework\Script\Execution\Awareness\StoppableHook;
  17. use Shopware\Core\Framework\Script\ServiceStubs;
  18. use Symfony\Bridge\Twig\Extension\TranslationExtension;
  19. use Symfony\Component\DependencyInjection\ContainerInterface;
  20. use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  21. use Twig\Environment;
  22. use Twig\Extension\DebugExtension;
  23. #[Package('core')]
  24. class ScriptExecutor
  25. {
  26.     public static bool $isInScriptExecutionContext false;
  27.     /**
  28.      * @internal
  29.      */
  30.     public function __construct(private readonly ScriptLoader $loader, private readonly LoggerInterface $logger, private readonly ScriptTraces $traces, private readonly ContainerInterface $container, private readonly TranslationExtension $translationExtension)
  31.     {
  32.     }
  33.     public function execute(Hook $hook): void
  34.     {
  35.         if (EnvironmentHelper::getVariable('DISABLE_EXTENSIONS'false)) {
  36.             return;
  37.         }
  38.         if ($hook instanceof InterfaceHook) {
  39.             throw new \RuntimeException(sprintf(
  40.                 'Tried to execute InterfaceHook "%s", butInterfaceHooks should not be executed, execute the functions of the hook instead',
  41.                 $hook::class
  42.             ));
  43.         }
  44.         $scripts $this->loader->get($hook->getName());
  45.         $this->traces->initHook($hook);
  46.         foreach ($scripts as $script) {
  47.             $scriptAppInfo $script->getScriptAppInformation();
  48.             if ($scriptAppInfo && $hook instanceof AppSpecificHook && $hook->getAppId() !== $scriptAppInfo->getAppId()) {
  49.                 // only execute scripts from the app the hook specifies
  50.                 continue;
  51.             }
  52.             if (!$hook instanceof AppLifecycleHook && !$script->isActive()) {
  53.                 continue;
  54.             }
  55.             try {
  56.                 static::$isInScriptExecutionContext true;
  57.                 $this->render($hook$script);
  58.             } catch (\Throwable $e) {
  59.                 $scriptException = new ScriptExecutionFailedException($hook->getName(), $script->getName(), $e);
  60.                 $this->logger->error($scriptException->getMessage(), ['exception' => $e]);
  61.                 throw $scriptException;
  62.             } finally {
  63.                 static::$isInScriptExecutionContext false;
  64.             }
  65.             if ($hook instanceof StoppableHook && $hook->isPropagationStopped()) {
  66.                 break;
  67.             }
  68.         }
  69.     }
  70.     private function render(Hook $hookScript $script): void
  71.     {
  72.         $twig $this->initEnv($script);
  73.         $services $this->initServices($hook$script);
  74.         $twig->addGlobal('services'$services);
  75.         $this->traces->trace($hook$script, function (Debug $debug) use ($twig$script$hook): void {
  76.             $twig->addGlobal('debug'$debug);
  77.             if ($hook instanceof DeprecatedHook) {
  78.                 ScriptTraces::addDeprecationNotice($hook->getDeprecationNotice());
  79.             }
  80.             $template $twig->load($script->getName());
  81.             if (!$hook instanceof FunctionHook) {
  82.                 $template->render(['hook' => $hook]);
  83.                 return;
  84.             }
  85.             $blockName $hook->getFunctionName();
  86.             if ($template->hasBlock($blockName)) {
  87.                 $template->renderBlock($blockName, ['hook' => $hook]);
  88.                 return;
  89.             }
  90.             if (!$hook instanceof OptionalFunctionHook) {
  91.                 throw new \RuntimeException(sprintf(
  92.                     'Required function "%s" missing in script "%s", please make sure you add the required block in your script.',
  93.                     $hook->getFunctionName(),
  94.                     $script->getName()
  95.                 ));
  96.             }
  97.             $requiredFromVersion $hook->willBeRequiredInVersion();
  98.             if ($requiredFromVersion) {
  99.                 ScriptTraces::addDeprecationNotice(sprintf(
  100.                     'Function "%s" will be required from %s onward, but is not implemented in script "%s", please make sure you add the block in your script.',
  101.                     $hook->getFunctionName(),
  102.                     $requiredFromVersion,
  103.                     $script->getName()
  104.                 ));
  105.             }
  106.         });
  107.         $this->callAfter($services$hook$script);
  108.     }
  109.     private function initEnv(Script $script): Environment
  110.     {
  111.         $twig = new TwigEnvironment(
  112.             new ScriptTwigLoader($script),
  113.             $script->getTwigOptions()
  114.         );
  115.         $twig->addExtension(new PhpSyntaxExtension());
  116.         $twig->addExtension($this->translationExtension);
  117.         $twig->addExtension(new SecurityExtension([]));
  118.         if ($script->getTwigOptions()['debug'] ?? false) {
  119.             $twig->addExtension(new DebugExtension());
  120.         }
  121.         return $twig;
  122.     }
  123.     private function initServices(Hook $hookScript $script): ServiceStubs
  124.     {
  125.         $services = new ServiceStubs($hook->getName());
  126.         $deprecatedServices $hook->getDeprecatedServices();
  127.         foreach ($hook->getServiceIds() as $serviceId) {
  128.             if (!$this->container->has($serviceId)) {
  129.                 throw new ServiceNotFoundException($serviceId'Hook: ' $hook->getName());
  130.             }
  131.             $service $this->container->get($serviceId);
  132.             if (!$service instanceof HookServiceFactory) {
  133.                 throw new NoHookServiceFactoryException($serviceId);
  134.             }
  135.             $services->add($service->getName(), $service->factory($hook$script), $deprecatedServices[$serviceId] ?? null);
  136.         }
  137.         return $services;
  138.     }
  139.     private function callAfter(ServiceStubs $servicesHook $hookScript $script): void
  140.     {
  141.         foreach ($hook->getServiceIds() as $serviceId) {
  142.             if (!$this->container->has($serviceId)) {
  143.                 throw new ServiceNotFoundException($serviceId'Hook: ' $hook->getName());
  144.             }
  145.             $factory $this->container->get($serviceId);
  146.             if (!$factory instanceof HookServiceFactory) {
  147.                 throw new NoHookServiceFactoryException($serviceId);
  148.             }
  149.             $service $services->get($factory->getName());
  150.             $factory->after($service$hook$script);
  151.         }
  152.     }
  153. }