Configuring the Container in an Addon - HL Vanilla Community
<main> <article class="userContent"> <p>Vanilla makes heavy use of a dependency injection container. This document outlines how to configure it through an addon.</p><div class="js-embed embedResponsive" data-embedjson="{"body":"Vanilla uses garden-container for dependency injection. The \\Garden\\Container object is a PSR-11 (Container Interface) compliant container class. This document outlines its usage in Vanilla, rather than how to use it, so it is worth reading the in-repo documentation. Creating Container Rules The container is primarily…","photoUrl":"https:\/\/us.v-cdn.net\/6030677\/uploads\/VWGAXAF5OFCO\/microsoftteams-image.png","url":"https:\/\/success.vanillaforums.com\/kb\/articles\/160-dependency-injection-in-vanilla","embedType":"link","name":"Dependency Injection in Vanilla - Vanilla Success"}"> <a href="https://success.vanillaforums.com/kb/articles/160-dependency-injection-in-vanilla" rel="nofollow noreferrer ugc"> https://success.vanillaforums.com/kb/articles/160-dependency-injection-in-vanilla </a> </div><h2 data-id="creating-a-containerrules-class">Creating a ContainerRules Class</h2><p>This is the preferred way to configure an addon since the 2021.024 release.</p><ol><li>Create a new class in the <code class="code codeInline" spellcheck="false" tabindex="0">Addon</code> namespace of your addon. For example the dashboard container rules is <code class="code codeInline" spellcheck="false" tabindex="0">Vanilla\Dashboard\Addon\DashboardContainerRules</code>.</li><li>This class should extend <code class="code codeInline" spellcheck="false" tabindex="0">Vanilla\AddonContainerRules</code> and implement it's abstract methods.</li></ol><h3 data-id="example">Example</h3><p><strong>/applications/dashboard/Addon/DashboardContainerRules.php</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">namespace Vanilla\Dashboard\Addon; /** * Container rules for the dashboard. */ class DashboardContainerRules extends AddonContainerRules { /** * @param ContainerConfigurationInterface $container */ public function configureContainer(ContainerConfigurationInterface $container): void { PageControllerRoute::configurePageRoutes($container, [ '/settings/layout' => LayoutSettingsPageController::class, ]); $container->rule(SiteTotalService::class) ->addCall('registerProvider', [new Reference(UserSiteTotalProvider::class)]) ; } } </pre><h3 data-id="important-caveats">Important Caveats</h3><p>Notably you are a given a <code class="code codeInline" spellcheck="false" tabindex="0">ContainerConfigurationInterface</code> here. This is because at this point in the request it is forbidden to construct anything from the container. Anything you created would be potentially "incomplete" as other addons may not have configured the container yet.</p><p>If you need to configure an argument for a method, call, or construct rule, use a <code class="code codeInline" spellcheck="false" tabindex="0">Garden\Container\Reference</code>. This delays the construction of that item until it is needed and the container is ready.</p><p><strong>Don't circumvent this with \Gdn::container()</strong></p><p>Don't. It's illegal. Every time you circumvent this rule a child cries and hacker pwns your site. Debugging a addon/container based race condition will turn your brain into putty. Just say no. Friends don't let friends create dependency injection race conditions.</p><h2 data-id="applying-addition-rules-for-tests">Applying Addition Rules for Tests</h2><p>Sometimes you want to configure additional rules just for tests. Generally this is "automatically" configure something for tests, configure a mock, stub, or spy.</p><p>You can do this by overriding the <code class="code codeInline" spellcheck="false" tabindex="0">configureTestContainer()</code> method.</p><pre class="code codeBlock" spellcheck="false" tabindex="0">/** * @inheritdoc */ public function configureTestContainer(Container $container): void { // Pockets tests are a lot simpler if these aren't marked as shared. $container ->rule(PocketsModel::class) ->setShared(false) ->rule(\PocketsApiController::class) ->setShared(false) ; } </pre><h2 data-id="legacy-methods">Legacy Methods</h2><p>There were 2 previous methods for configuring the container through and addon but they both have major pitfalls.</p><ol><li>Add an event handler <code class="code codeInline" spellcheck="false" tabindex="0">container_init(Container $container)</code>. This was prone to container race conditions because the plugin class could have already dependency injected something during its construction. Additionally it ran too late in the process to make rules for the config, request, or addon manager.</li><li>Create a file <code class="code codeInline" spellcheck="false" tabindex="0">bootstrap.php</code> in your addon. This ran earlier than <code class="code codeInline" spellcheck="false" tabindex="0">container_init</code> but didn't run consistently in tests, requiring you to make use of both and repeat container rules in individual tests as well.</li></ol><p><br></p> </article> </main>