Accessing your plugin’s classes during its installation and activation
So you have created this awesome plugin, that you are so proud of. But for it to be perfect, you need to do some processes, when the plugin is being installed and activated. No problem, right? You simply go to the plugin’s main class, that resides in its src directory and use the standard methods like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php declare(strict_types=1); namespace SomePlugin; use Shopware\Core\Framework\Plugin\Context\ActivateContext; use Shopware\Core\Framework\Plugin\Context\DeactivateContext; use Shopware\Core\Framework\Plugin\Context\InstallContext; use Shopware\Core\Framework\Plugin\Context\UninstallContext; class SomePlugin extends \Shopware\Core\Framework\Plugin { public function install(InstallContext $installContext): void { parent::install($installContext); //do something during installation } public function uninstall(UninstallContext $uninstallContext): void { parent::uninstall($uninstallContext); //do something during uninstallation } public function activate(ActivateContext $activateContext): void { parent::activate($activateContext); //do something during activation } public function deactivate(DeactivateContext $deactivateContext): void { parent::deactivate($deactivateContext); //do something during deactivation } } |
This works nice and well, as long as you don’t need to use methods from some of your plugin’s classes. But what if you do? In that case, the first thing you would probably try, is the standard dependency injection. That is unfortunately not possible here, because the contructor of the main plugin’s class in final, which means, that you can’t change it. You can take a look at it yourself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
namespace Shopware\Core\Framework; use Composer\Autoload\ClassLoader; use Shopware\Core\Framework\Parameter\AdditionalBundleParameters; use Shopware\Core\Framework\Plugin\Context\ActivateContext; use Shopware\Core\Framework\Plugin\Context\DeactivateContext; use Shopware\Core\Framework\Plugin\Context\InstallContext; use Shopware\Core\Framework\Plugin\Context\UninstallContext; use Shopware\Core\Framework\Plugin\Context\UpdateContext; use Shopware\Core\Kernel; use Symfony\Component\Routing\RouteCollectionBuilder; abstract class Plugin extends Bundle { /** * @var bool */ private $active; /** * @var string */ private $basePath; final public function __construct(bool $active, string $basePath, ?string $projectDir = null) { $this->active = $active; $this->basePath = $basePath; if ($projectDir && mb_strpos($this->basePath, '/') !== 0) { $this->basePath = $projectDir . '/' . $this->basePath; } $this->path = $this->computePluginClassPath(); } ... } |
So without your custom constructor, there is no way of injecting the classes you need. Sure, you could instantiate them, using ‘new’. But that would be extremely tedious and impractical, because they probably have some dependencies and those dependencies have other dependencies. So what to do now?
Well, you could take a look at the class Bundle, which is a parent class of the Plugin class. There is a glimpse of hope here, because the $container is accessible from here. Container or more precisely the Service container, as the name suggests, is a magical box, that contains services. You can use it like this to basically simulate dependency injection, that you are used to use in the class constructors:
1 |
$this->someService = $this->container->get('SomePlugin\Components\SomeService'); |
Victory? Unfortunately no. As you will find out, during plugin installation, your service (e.g. the class you want to inject and use its methods) is still not available. The reason for this is, that it is not contained in the Container, when you request it, because your plugin is not activated at that time. And here comes the whole point of this article: the solution.
How to add the plugin’s classes to the Service container
The Service container normally contains only some Shopware 6 core classes and repositories plus the classes of active plugins. So if we want to be able to access the classes of an inactive plugin, we need to add them to the container. For that, I have created a method, that you can put into your plugin’s main class and call it, whenever you need to access your plugin’s classes from there, even if the plugin is deactivated at the moment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/** * Method for adding this plugin's classes to the dependency injection container, so that they are available in install/uninstall process, even if the plugin is not active at that time */ private function putClassesToContainer(): void { /** @var Kernel $kernel */ $kernel = $this->container->get('kernel'); /** @var KernelPluginLoader $pluginLoader */ $pluginLoader = $this->container->get(KernelPluginLoader::class); //get all plugins and mark the current plugin as active, so that its classes get added to the container $plugins = $pluginLoader->getPluginInfos(); foreach ($plugins as $i => $pluginData) { if ($pluginData['baseClass'] === 'SomePlugin\SomePlugin') { $plugins[$i]['active'] = 1; } } //load all plugins $tmpStaticPluginLoader = new StaticKernelPluginLoader( $pluginLoader->getClassLoader(), $kernel->getContainer()->getParameter('kernel.plugin_dir'), $plugins ); //reboot kernel to apply the changes to the container $kernel->reboot(null, $tmpStaticPluginLoader); //handle an exception, when calling getContainer on a not booted kernel /** @var \Symfony\Component\DependencyInjection\ContainerInterface|null $newContainer */ $newContainer = $kernel->getContainer(); if (!$newContainer) { throw new \RuntimeException('Failed to reboot the kernel'); } $this->container = $newContainer; } |
Simply put, it simulates, that your plugin is active and regenerates the container based on that. There is one more thing you need to do, before this can work. Go to the services.xml file, located in your Plugin’s src/Resources/config subdirectory and make your services public like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="SomePlugin\Components\SomeService" public="true"> </service> </services> </container> |
And that’s it! Now you are able to use whatever methods you have in any of your plugin’s classes. Took me a while to figure this out, so I hope it helps you. If you have an alternative solution or you simply want to thank me, please leave a comment below.
Thank you so much for this! Huge help!!