vendor/shopware/core/Framework/Plugin/KernelPluginLoader/KernelPluginLoader.php line 109

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Plugin\KernelPluginLoader;
  3. use Composer\Autoload\ClassLoader;
  4. use Shopware\Core\Framework\Log\Package;
  5. use Shopware\Core\Framework\Parameter\AdditionalBundleParameters;
  6. use Shopware\Core\Framework\Plugin;
  7. use Shopware\Core\Framework\Plugin\Exception\KernelPluginLoaderException;
  8. use Shopware\Core\Framework\Plugin\KernelPluginCollection;
  9. use Symfony\Component\DependencyInjection\ContainerBuilder;
  10. use Symfony\Component\DependencyInjection\Definition;
  11. use Symfony\Component\DependencyInjection\Reference;
  12. use Symfony\Component\HttpKernel\Bundle\Bundle;
  13. #[Package('core')]
  14. abstract class KernelPluginLoader extends Bundle
  15. {
  16.     /**
  17.      * @var array<int, mixed>
  18.      */
  19.     protected $pluginInfos = [];
  20.     private readonly KernelPluginCollection $pluginInstances;
  21.     private readonly string $pluginDir;
  22.     private bool $initialized false;
  23.     /**
  24.      * @internal
  25.      */
  26.     public function __construct(
  27.         private readonly ClassLoader $classLoader,
  28.         ?string $pluginDir null
  29.     ) {
  30.         $this->pluginDir $pluginDir ?? 'custom/plugins';
  31.         $this->pluginInstances = new KernelPluginCollection();
  32.     }
  33.     final public function getPluginDir(string $projectDir): string
  34.     {
  35.         // absolute path
  36.         if (mb_strpos($this->pluginDir'/') === 0) {
  37.             return $this->pluginDir;
  38.         }
  39.         return $projectDir '/' $this->pluginDir;
  40.     }
  41.     /**
  42.      * @return array<int, mixed>
  43.      * Basic information required for instantiating the plugins
  44.      */
  45.     final public function getPluginInfos(): array
  46.     {
  47.         return $this->pluginInfos;
  48.     }
  49.     /**
  50.      * @final
  51.      * Instances of the plugin bundle classes
  52.      */
  53.     public function getPluginInstances(): KernelPluginCollection
  54.     {
  55.         return $this->pluginInstances;
  56.     }
  57.     /**
  58.      * @param array<string, mixed> $kernelParameters
  59.      * @param array<int, string> $loadedBundles
  60.      *
  61.      * @return \Traversable<Bundle>
  62.      */
  63.     final public function getBundles(array $kernelParameters = [], array $loadedBundles = []): iterable
  64.     {
  65.         if (!$this->initialized) {
  66.             return;
  67.         }
  68.         foreach ($this->pluginInstances->getActives() as $plugin) {
  69.             $copy = new KernelPluginCollection($this->getPluginInstances()->all());
  70.             $additionalBundleParameters = new AdditionalBundleParameters($this->classLoader$copy$kernelParameters);
  71.             $additionalBundles $plugin->getAdditionalBundles($additionalBundleParameters);
  72.             [$preLoaded$postLoaded] = $this->splitBundlesIntoPreAndPost($additionalBundles);
  73.             foreach ([...\array_values($preLoaded), $plugin, ...\array_values($postLoaded)] as $bundle) {
  74.                 if (!\in_array($bundle->getName(), $loadedBundlestrue)) {
  75.                     yield $bundle;
  76.                     $loadedBundles[] = $bundle->getName();
  77.                 }
  78.             }
  79.         }
  80.         if (!\in_array($this->getName(), $loadedBundlestrue)) {
  81.             yield $this;
  82.         }
  83.     }
  84.     /**
  85.      * @throws KernelPluginLoaderException
  86.      */
  87.     final public function initializePlugins(string $projectDir): void
  88.     {
  89.         if ($this->initialized) {
  90.             return;
  91.         }
  92.         $this->loadPluginInfos();
  93.         if (empty($this->pluginInfos)) {
  94.             $this->initialized true;
  95.             return;
  96.         }
  97.         $this->registerPluginNamespaces($projectDir);
  98.         $this->instantiatePlugins($projectDir);
  99.         $this->initialized true;
  100.     }
  101.     final public function build(ContainerBuilder $container): void
  102.     {
  103.         if (!$this->initialized) {
  104.             return;
  105.         }
  106.         parent::build($container);
  107.         /*
  108.          * Register every plugin in the di container, enable autowire and set public
  109.          */
  110.         foreach ($this->pluginInstances->getActives() as $plugin) {
  111.             $class $plugin::class;
  112.             $definition = new Definition();
  113.             if ($container->hasDefinition($class)) {
  114.                 $definition $container->getDefinition($class);
  115.             }
  116.             $definition->setFactory([new Reference(self::class), 'getPluginInstance']);
  117.             $definition->addArgument($class);
  118.             $definition->setAutowired(true);
  119.             $definition->setPublic(true);
  120.             $container->setDefinition($class$definition);
  121.         }
  122.     }
  123.     final public function getPluginInstance(string $class): ?Plugin
  124.     {
  125.         $plugin $this->pluginInstances->get($class);
  126.         if (!$plugin || !$plugin->isActive()) {
  127.             return null;
  128.         }
  129.         return $plugin;
  130.     }
  131.     public function getClassLoader(): ClassLoader
  132.     {
  133.         return $this->classLoader;
  134.     }
  135.     abstract protected function loadPluginInfos(): void;
  136.     /**
  137.      * @throws KernelPluginLoaderException
  138.      */
  139.     private function registerPluginNamespaces(string $projectDir): void
  140.     {
  141.         foreach ($this->pluginInfos as $plugin) {
  142.             \assert(\is_string($plugin['baseClass']));
  143.             $pluginName $plugin['name'] ?? $plugin['baseClass'];
  144.             // plugins managed by composer are already in the classMap
  145.             if ($plugin['managedByComposer']) {
  146.                 continue;
  147.             }
  148.             if (!isset($plugin['autoload'])) {
  149.                 $reason sprintf(
  150.                     'Unable to register plugin "%s" in autoload. Required property `autoload` missing.',
  151.                     $plugin['baseClass']
  152.                 );
  153.                 throw new KernelPluginLoaderException($pluginName$reason);
  154.             }
  155.             $psr4 $plugin['autoload']['psr-4'] ?? [];
  156.             $psr0 $plugin['autoload']['psr-0'] ?? [];
  157.             if (empty($psr4) && empty($psr0)) {
  158.                 $reason sprintf(
  159.                     'Unable to register plugin "%s" in autoload. Required property `psr-4` or `psr-0` missing in property autoload.',
  160.                     $plugin['baseClass']
  161.                 );
  162.                 throw new KernelPluginLoaderException($pluginName$reason);
  163.             }
  164.             foreach ($psr4 as $namespace => $paths) {
  165.                 if (\is_string($paths)) {
  166.                     $paths = [$paths];
  167.                 }
  168.                 $mappedPaths $this->mapPsrPaths($pluginName$paths$projectDir$plugin['path']);
  169.                 $this->classLoader->addPsr4($namespace$mappedPaths);
  170.                 if ($this->classLoader->isClassMapAuthoritative()) {
  171.                     $this->classLoader->setClassMapAuthoritative(false);
  172.                 }
  173.             }
  174.             foreach ($psr0 as $namespace => $paths) {
  175.                 if (\is_string($paths)) {
  176.                     $paths = [$paths];
  177.                 }
  178.                 $mappedPaths $this->mapPsrPaths($pluginName$paths$projectDir$plugin['path']);
  179.                 $this->classLoader->add($namespace$mappedPaths);
  180.                 if ($this->classLoader->isClassMapAuthoritative()) {
  181.                     $this->classLoader->setClassMapAuthoritative(false);
  182.                 }
  183.             }
  184.         }
  185.     }
  186.     /**
  187.      * @param array<string> $psr
  188.      *
  189.      * @throws KernelPluginLoaderException
  190.      *
  191.      * @return array<string>
  192.      */
  193.     private function mapPsrPaths(string $plugin, array $psrstring $projectDirstring $pluginRootPath): array
  194.     {
  195.         $mappedPaths = [];
  196.         $absolutePluginRootPath $this->getAbsolutePluginRootPath($projectDir$pluginRootPath);
  197.         if (mb_strpos($absolutePluginRootPath$projectDir) !== 0) {
  198.             throw new KernelPluginLoaderException(
  199.                 $plugin,
  200.                 sprintf('Plugin dir %s needs to be a sub-directory of the project dir %s'$pluginRootPath$projectDir)
  201.             );
  202.         }
  203.         foreach ($psr as $path) {
  204.             $mappedPaths[] = $absolutePluginRootPath '/' $path;
  205.         }
  206.         return $mappedPaths;
  207.     }
  208.     private function getAbsolutePluginRootPath(string $projectDirstring $pluginRootPath): string
  209.     {
  210.         // is relative path
  211.         if (mb_strpos($pluginRootPath'/') !== 0) {
  212.             $pluginRootPath $projectDir '/' $pluginRootPath;
  213.         }
  214.         return $pluginRootPath;
  215.     }
  216.     /**
  217.      * @throws KernelPluginLoaderException
  218.      */
  219.     private function instantiatePlugins(string $projectDir): void
  220.     {
  221.         foreach ($this->pluginInfos as $pluginData) {
  222.             $className $pluginData['baseClass'];
  223.             $pluginClassFilePath $this->classLoader->findFile($className);
  224.             if (!class_exists($className) || !$pluginClassFilePath || !file_exists($pluginClassFilePath)) {
  225.                 continue;
  226.             }
  227.             /** @var Plugin $plugin */
  228.             $plugin = new $className((bool) $pluginData['active'], $pluginData['path'], $projectDir);
  229.             if (!$plugin instanceof Plugin) {
  230.                 $reason sprintf('Plugin class "%s" must extend "%s"'$plugin::class, Plugin::class);
  231.                 throw new KernelPluginLoaderException($pluginData['name'], $reason);
  232.             }
  233.             $this->pluginInstances->add($plugin);
  234.         }
  235.     }
  236.     /**
  237.      * @param Bundle[] $bundles
  238.      *
  239.      * @return array<Bundle[]>
  240.      */
  241.     private function splitBundlesIntoPreAndPost(array $bundles): array
  242.     {
  243.         $pre = [];
  244.         $post = [];
  245.         foreach ($bundles as $index => $bundle) {
  246.             if (\is_int($index) && $index 0) {
  247.                 $pre[$index] = $bundle;
  248.             } else {
  249.                 $post[$index] = $bundle;
  250.             }
  251.         }
  252.         \ksort($pre);
  253.         \ksort($post);
  254.         return [$pre$post];
  255.     }
  256. }