Using Legacy Events with Gdn_Pluggable - Vanilla Success
<main> <article class="userContent"> <h3 data-id="gdn_pluggable">Gdn_Pluggable</h3><p>The <code class="code codeInline" spellcheck="false" tabindex="0">EventManager</code> class represents the modern way of firing and handling events in Vanilla. Previously events were fired through the <code class="code codeInline" spellcheck="false" tabindex="0">PluginManager</code> or through and abstraction <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code>. Lots of Vanilla classes extend from <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code>. Almost every class beginning with <code class="code codeInline" spellcheck="false" tabindex="0">Gdn</code> extends from <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code> including <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Plugin</code> and <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Controller</code>.</p><p>Using this method of firing events is now <strong><em>discouraged</em></strong>. While the implementation now uses the <code class="code codeInline" spellcheck="false" tabindex="0">EventManager</code> internally, firing events this way carries the overhead of multple additional function calls and has a less explicit syntax.</p><p><code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code> exposes a method <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable::fireEvent(string $eventName, array $args)</code>. It would join the name of the class firing and event with the <code class="code codeInline" spellcheck="false" tabindex="0">$eventName</code> and fire that as an event. The class property <code class="code codeInline" spellcheck="false" tabindex="0">->EventArguments</code> would be merged with <code class="code codeInline" spellcheck="false" tabindex="0">$args</code> and passed to any handler. If the class name was not preferable the event prefix could be set by calling the <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable::fireAs(string $prefix)</code> method before calling <code class="code codeInline" spellcheck="false" tabindex="0">fireEvent()</code>.</p><p>The common method of the receiving data back from the events was to use the fact that array values are passed by reference and modify them inside of the handlers.</p><p>Let’s look at an example.</p><pre class="code codeBlock" spellcheck="false" tabindex="0">class Gdn_Form extends Gdn_Pluggable { public function bodyBox($column = 'Body', $attributes = []) { // ... $this->EventArguments['Table'] = val('Table', $attributes); $this->EventArguments['Column'] = $column; $this->EventArguments['Attributes'] = $attributes; $this->EventArguments['BodyBox'] =& $result; $this->fireEvent('BeforeBodyBox'); // ... } } </pre><p>And a handler. Notice</p><ul><li>The calling class being passed in as the first parameter.</li><li>The arguments array being passed as the second array.</li><li>The arguments array is modified by reference.</li></ul><pre class="code codeBlock" spellcheck="false" tabindex="0">class EditorPlugin extends Gdn_Plugin { public function gdn_form_beforeBodyBox_handler(Gdn_Form $sender, array $args) { // ... // Convert the form body to WYSIWYG if ($this->ForceWysiwyg == true && $needsConversion) { $wysiwygBody = Gdn_Format::to($sender->getValue('Body'), $this->Format); $sender->setValue('Body', $wysiwygBody); $this->Format = 'Wysiwyg'; $sender->setValue('Format', $this->Format); } // Append the editor HTML $view = $c->fetchView('editor', '', 'plugins/editor'); $args['BodyBox'] .= $view; } } </pre><p><br></p><h2 data-id="handlers">Handlers</h2><p>Any class extending <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Plugin</code> can handle these events fired by and instance of <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code>. These handlers look like this:</p><pre class="code codeBlock" spellcheck="false" tabindex="0">/** * @param object $sender Sending object instance. * @param array $args Event's arguments. */ public function base_someEvent_handler($sender, $args) { // Do something. } </pre><p>Each handler’s function name is made up of 3 parts. - The name of class implementing <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code> to listen for - The event name - <code class="code codeInline" spellcheck="false" tabindex="0">handler</code></p><p>Using <code class="code codeInline" spellcheck="false" tabindex="0">base</code> instead of a class name will allow your handler to listen to every fired event for your event name. So <code class="code codeInline" spellcheck="false" tabindex="0">base_someEvent_handler</code> would listen for a <code class="code codeInline" spellcheck="false" tabindex="0">fireEvent('SomeEvent')</code> on every instance of <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code>, while <code class="code codeInline" spellcheck="false" tabindex="0">profileController_getConnections_handler</code> would listen only on the <code class="code codeInline" spellcheck="false" tabindex="0">ProfileController</code> for the <code class="code codeInline" spellcheck="false" tabindex="0">fireEvent('GetConnections)</code>.</p><p>The handler is passed a <code class="code codeInline" spellcheck="false" tabindex="0">$sender</code> and <code class="code codeInline" spellcheck="false" tabindex="0">$args</code> so that you method can call methods on it’s sending instance of <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code> and its event arguments.</p><h2 data-id="magic-events">Magic Events</h2><p>Magic events were an elaborate system of hook possibilities that involved the method prefix ‘x’ and PHP’s <code class="code codeInline" spellcheck="false" tabindex="0">__call()</code> method. Currently, there is only one undeprecated magic event in Vanilla: <code class="code codeInline" spellcheck="false" tabindex="0">render_before</code>. It invokes just before the page is rendered. Example use: <code class="code codeInline" spellcheck="false" tabindex="0">base_render_before($sender)</code>. <strong>It is best to avoid when another event is usable.</strong></p><p>For a better alternative hook that reliably fires early on every request, try <code class="code codeInline" spellcheck="false" tabindex="0">gdn_dispatcher_appStartup_handler</code> instead. To universally include a CSS file, use <code class="code codeInline" spellcheck="false" tabindex="0">assetModel_styleCss_handler</code>.</p><h2 data-id="magic-methods">Magic Methods</h2><p>Magic methods allow you to create new methods and add them to existing objects. They are created in much the same way that you plug into events. Imagine you wanted to add a method named <code class="code codeInline" spellcheck="false" tabindex="0">Kaboom</code> to the DiscussionsController:</p><pre class="code codeBlock" spellcheck="false" tabindex="0">class MyPlugin extends Gdn_Plugin { public function discussionsController_kaboom_create($sender) { echo "Kaboom!"; } } </pre><p>With this addon enabled, going to the URL <code class="code codeInline" spellcheck="false" tabindex="0">/discussions/kaboom</code> would now output the text “Kaboom!”. You can references other methods and properties on the extended object using the <code class="code codeInline" spellcheck="false" tabindex="0">$sender</code> variable.</p><p>If you use a magic method to duplicate an existing method name, it will be overridden completely. And call to it will be directed to your plugin instead. The only exception is the <code class="code codeInline" spellcheck="false" tabindex="0">index()</code> method.</p><p>Magic methods only work in classes that extend <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Pluggable</code>. For example, notice the <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Form</code> class does, but the <code class="code codeInline" spellcheck="false" tabindex="0">Gdn_Format</code> class does not. All models and controllers do.</p> </article> </main>