Using Events with the EventManager - Vanilla Success
<main> <article class="userContent"> <div class="embedExternal embedImage display-large float-none"> <div class="embedExternal-content"> <a class="embedImage-link" href="https://us.v-cdn.net/6030677/uploads/HC9YV9LT5NXH/microsoftteams-image-288-29.png" rel="nofollow noreferrer noopener ugc" target="_blank"> <img class="embedImage-img" src="https://us.v-cdn.net/6030677/uploads/HC9YV9LT5NXH/microsoftteams-image-288-29.png" alt="MicrosoftTeams-image (8).png" height="108" width="1356" loading="lazy" data-display-size="large" data-float="none"></img></a> </div> </div> <p>Events are a fundamental way for different <a href="https://docs.vanillaforums.com/developer/addons" rel="nofollow noreferrer ugc">addons</a> to communicate with each other. Events are fired with and any addon can hook into / listen to these events and respond to them.</p><p>The <code class="code codeInline" spellcheck="false" tabindex="0">EventManager</code> is responsible for creating and responding to events in Vanilla.</p><h2 data-id="getting-the-event-manager">Getting the Event Manager</h2><p>The proper way to get the event manager is through the <a href="https://docs.vanillaforums.com/developer/framework/dependency-injection" rel="nofollow noreferrer ugc">container</a>. Do not create a new instance yourself.</p><h2 data-id="firing-an-event">Firing an Event</h2><p>There are multiple ways to fire events.</p><h2 data-id="fire()"><code class="code codeInline" spellcheck="false" tabindex="0">fire()</code></h2><p>The simplest method of firing an event is using the <code class="code codeInline" spellcheck="false" tabindex="0">EventManager::fire(string $event, ...$args): array</code>. You pass an event name, and any arguments you want the event handlers to have, and get back an array of responses from every responding event.</p><p><strong>Example 1</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">class HtmlProcessor { // ... private $allowedUrlSchemes = ['http://', 'https://', 'mailto://', 'tel://']; public function __construct(EventManager $eventManager) { // Allow addons to add extra allowed URL schemes /** @var string[] */ $extraUrlSchemes = $eventManager->fire('getExtraAllowedUrlSchemes'); $this->allowedUrlSchemes = array_merge($this->allowedUrlSchemes, $extraUrlSchemes); } // ... } </pre><p>Let’s take a look at what the handler for this event would look like.</p><p><strong>plugins/steam/Addon/SteamEventHandlers.php</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">namespace Vanilla\Steam\Addon; class SteamEventHandlers implements \Garden\EventHandlersInterface { public function getExtraAllowedUrlSchemes() { return "steam://"; } } </pre><h2 data-id="firefilter()"><code class="code codeInline" spellcheck="false" tabindex="0">fireFilter()</code></h2><p>The previous <code class="code codeInline" spellcheck="false" tabindex="0">EventManager::fire()</code> call was simple, but does not work for every case. Imagine a scenario where you would like multiple addons to be able build on top of the results of each other. Such as the the <code class="code codeInline" spellcheck="false" tabindex="0">GET /api/v2//discussions</code> endpoint. Here we want <em>multiple</em> addons to be able to modify the result, and we want them all to be working with the same thing. We also want to pass the context of the request into the event.</p><p><code class="code codeInline" spellcheck="false" tabindex="0">EventManager::fireFilter(string $event, $initialValue, ...$args)</code> is the perfect candidate for this. It will fire an event name of <code class="code codeInline" spellcheck="false" tabindex="0">$event</code>, gather all of the handlers, and pass <code class="code codeInline" spellcheck="false" tabindex="0">$initalValue</code> as the first parameter of the event handler, then pass the return value of the previous event handler into the first parameter of the each other handler. The result of the last handler will be returned.</p><p>The rest of the arguments will be passed along to each handler.</p><p><strong>Example</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">class DiscussionsApiController extends AbstractApiController { public function index(array $query) { // ... // Allow addons to modify the result. $result = $this->getEventManager()->fireFilter( 'discussionsApiController_indexOutput', $result, $this, $in, $query, $rows ); // ... } } </pre><p>And the handler:</p><p><strong>plugins/reactions/Addon/ReactionsEventHandlers.php</strong></p><pre class="code codeBlock" spellcheck="false" tabindex="0">namespace Vanilla\Reactions\Addon; class ReactionsEventHandlers implements \Garden\EventHandlersInterface { public function discussionsApiController_indexOutput( array $previousResult, DiscussionsApiController $sender, Schema $inSchema, array $query, array $rows ): array { $newResult = $previousResult // Modify the result in same way return $newResult; } </pre><p>Note that the return type should be the same as the <code class="code codeInline" spellcheck="false" tabindex="0">$previousResult</code> type, because it will be passed into the next handler.</p><h2 data-id="firedeprecated()"><code class="code codeInline" spellcheck="false" tabindex="0">fireDeprecated()</code></h2><p>The fire deprecated method is very similar to the <code class="code codeInline" spellcheck="false" tabindex="0">fire()</code> method. It functions identically except:</p><ul><li>If any handler is bound to it</li><li>It will trigger a deprecated notice (<code class="code codeInline" spellcheck="false" tabindex="0">E_USER_DEPRECATED</code>).</li></ul> </article> </main>