vendor/shopware/core/Framework/Script/Execution/ScriptExecutor.php line 60

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