MediaWiki r6405 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r6404‎ | r6405 (on ViewVC)‎ | r6406 >
Date:21:43, 27 November 2004
Author:evanprodromou
Status:old
Tags:
Comment:
Add a system of hooks to allow third-party code to run before, after, or
instead of -- MediaWiki code for particular events (article rollback,
user ban, etc.). Framework is in place; hooks are not yet in place
in the mainline code.
Modified paths:

Diff [purge]

Index: trunk/phase3/docs/hooks.doc
@@ -0,0 +1,228 @@
 2+HOOKS.DOC
 3+
 4+This document describes how event hooks work in MediaWiki; how to add
 5+hooks for an event; and how to run hooks for an event.
 6+
 7+==Glossary==
 8+
 9+event
 10+ Something that happens with the wiki. For example: a user logs
 11+ in. A wiki page is saved. A wiki page is deleted. Often there are
 12+ two events associated with a single action: one before the code
 13+ is run to make the event happen, and one after. Each event has a
 14+ name, preferably in CamelCase. For example, 'UserLogin',
 15+ 'ArticleSave', 'ArticleSaveComplete', 'ArticleDelete'.
 16+
 17+hook
 18+ A clump of code and data that should be run when an event
 19+ happens. This can be either a function and a chunk of data, or an
 20+ object and a method.
 21+
 22+hook function
 23+ The function part of a hook.
 24+
 25+==Rationale==
 26+
 27+Hooks allow us to decouple optionally-run code from code that is run
 28+for everyone. It allows MediaWiki hackers, third-party developers and
 29+local administrators to define code that will be run at certain points
 30+in the mainline code, and to modify the data run by that mainline
 31+code. Hooks can keep mainline code simple, and make it easier to
 32+write extensions. Hooks are a principled alternative to local patches.
 33+
 34+Consider, for example, two options in MediaWiki. One reverses the
 35+order of a title before displaying the article; the other converts the
 36+title to all uppercase letters. Currently, in MediaWiki code, we
 37+handle this as follows:
 38+
 39+ function showAnArticle($article) {
 40+ global $wgReverseTitle, $wgCapitalizeTitle;
 41+
 42+ if ($wgReverseTitle) {
 43+ wfReverseTitle($article);
 44+ }
 45+
 46+ if ($wgCapitalizeTitle) {
 47+ wfCapitalizeTitle($article);
 48+ }
 49+
 50+ # code to actually show the article goes here
 51+ }
 52+
 53+An extension writer, or a local admin, will often add custom code to
 54+the function -- with or without a global variable. For example,
 55+someone wanting email notification when an article is shown may add:
 56+
 57+ function showAnArticle($article) {
 58+ global $wgReverseTitle, $wgCapitalizeTitle;
 59+
 60+ if ($wgReverseTitle) {
 61+ wfReverseTitle($article);
 62+ }
 63+
 64+ if ($wgCapitalizeTitle) {
 65+ wfCapitalizeTitle($article);
 66+ }
 67+
 68+ # code to actually show the article goes here
 69+
 70+ if ($wgNotifyArticle) {
 71+ wfNotifyArticleShow($article));
 72+ }
 73+ }
 74+
 75+Using a hook-running strategy, we can avoid having all this
 76+option-specific stuff in our mainline code. Using hooks, the function
 77+becomes:
 78+
 79+ function showAnArticle($article) {
 80+ if (wfRunHooks('ArticleShow', $article)) {
 81+ # code to actually show the article goes here
 82+ wfRunHooks('ArticleShowComplete', $article);
 83+ }
 84+ }
 85+
 86+We've cleaned up the code here by removing clumps of weird,
 87+infrequently used code and moving them off somewhere else. It's much
 88+easier for someone working with this code to see what's _really_ going
 89+on, and make changes or fix bugs.
 90+
 91+In addition, we can take all the code that deals with the little-used
 92+title-reversing options (say) and put it in one place. Instead of
 93+having a little title-reversing if-block spread all over the codebase
 94+in showAnArticle, deleteAnArticle, exportArticle, etc., we can
 95+concentrate it all in an extension file:
 96+
 97+ function reverseArticleTitle($article) {
 98+ # ...
 99+ }
 100+
 101+ function reverseForExport($article) {
 102+ # ...
 103+ }
 104+
 105+The setup function for the extension just has to add its hook
 106+functions to the appropriate events:
 107+
 108+ setupTitleReversingExtension() {
 109+ global $wgHooks;
 110+
 111+ $wgHooks['ArticleShow'][] = reverseArticleTitle;
 112+ $wgHooks['ArticleDelete'][] = reverseArticleTitle;
 113+ $wgHooks['ArticleExport'][] = reverseForExport;
 114+ }
 115+
 116+Having all this code related to the title-reversion option in one
 117+place means that it's easier to read and understand; you don't have to
 118+do a grep-find to see where the $wgReverseTitle variable is used, say.
 119+
 120+If the code is well enough isolated, it can even be excluded when not
 121+used -- making for some slight savings in memory and time at runtime.
 122+Admins who want to have all the reversed titles can add:
 123+
 124+ require_once('extensions/ReverseTitle.php');
 125+
 126+...to their LocalSettings.php file; those of us who don't want or need
 127+it can just leave it out.
 128+
 129+The extensions don't even have to be shipped with MediaWiki; they
 130+could be provided by a third-party developer or written by the admin
 131+him/herself.
 132+
 133+==Writing hooks==
 134+
 135+A hook is a chunk of code run at some particular event. It consists of:
 136+
 137+ * a function with some optional accompanying data, or
 138+ * an object with a method and some optional accompanying data.
 139+
 140+Hooks are registered by adding them to the global $wgHooks array for a
 141+given event. All the following are valid ways to define hooks:
 142+
 143+ $wgHooks['EventName'][] = someFunction; # function, no data
 144+ $wgHooks['EventName'][] = array(someFunction, $someData);
 145+ $wgHooks['EventName'][] = array(someFunction); # weird, but OK
 146+
 147+ $wgHooks['EventName'][] = $object; # object only
 148+ $wgHooks['EventName'][] = array($object, 'someMethod');
 149+ $wgHooks['EventName'][] = array($object, 'someMethod', $someData);
 150+ $wgHooks['EventName'][] = array($object); # weird but OK
 151+
 152+When an event occurs, the function (or object method) will be called
 153+with the optional data provided as well as event-specific parameters.
 154+The above examples would result in the following code being executed
 155+when 'EventName' happened:
 156+
 157+ # function, no data
 158+ someFunction($param1, $param2)
 159+ # function with data
 160+ someFunction($someData, $param1, $param2)
 161+
 162+ # object only
 163+ $object->onEventName($param1, $param2)
 164+ # object with method
 165+ $object->someMethod($param1, $param2)
 166+ # object with method and data
 167+ $object->someMethod($someData, $param1, $param2)
 168+
 169+Note that when an object is the hook, and there's no specified method,
 170+the default method called is 'onEventName'. For different events this
 171+would be different: 'onArticleSave', 'onUserLogin', etc.
 172+
 173+The extra data is useful if we want to use the same function or object
 174+for different purposes. For example:
 175+
 176+ $wgHooks['ArticleSaveComplete'][] = array(ircNotify, 'TimStarling');
 177+ $wgHooks['ArticleSaveComplete'][] = array(ircNotify, 'brion');
 178+
 179+This code would result in ircNotify being run twice when an article is
 180+saved: once for 'TimStarling', and once for 'brion'.
 181+
 182+Hooks can return three possible values:
 183+ * true: the hook has operated successfully
 184+ * "some string": an error occurred; processing should
 185+ stop and the error should be shown to the user
 186+ * false: the hook has successfully done the work
 187+ necessary and the calling function should skip
 188+
 189+The last result would be for cases where the hook function replaces
 190+the main functionality. For example, if you wanted to authenticate
 191+users to a custom system (LDAP, another PHP program, whatever), you
 192+could do:
 193+
 194+ $wgHooks['UserLogin'][] = array(ldapLogin, $ldapServer);
 195+
 196+ function ldapLogin($username, $password) {
 197+ # log user into LDAP
 198+ return false;
 199+ }
 200+
 201+Returning false makes less sense for events where the action is
 202+complete, and will probably be ignored.
 203+
 204+==Using hooks==
 205+
 206+A calling function or method uses the wfRunHooks() function to run
 207+the hooks related to a particular event, like so:
 208+
 209+ class Article {
 210+ # ...
 211+ function protect() {
 212+ global $wgUser;
 213+ if (wfRunHooks('ArticleProtect', $this, $wgUser)) {
 214+ # protect the article
 215+ wfRunHooks('ArticleProtectComplete', $this, $wgUser);
 216+ }
 217+ }
 218+
 219+wfRunHooks() returns true if the calling function should continue
 220+processing (the hooks ran OK, or there are no hooks to run), or false
 221+if it shouldn't (an error occurred, or one of the hooks handled the
 222+action already). Checking the return value matters more for "before"
 223+hooks than for "complete" hooks.
 224+
 225+==Existing hooks==
 226+
 227+The following list of hooks exist in the code right now:
 228+
 229+TBD
Property changes on: trunk/phase3/docs/hooks.doc
___________________________________________________________________
Added: svn:eol-style
1230 + native
Added: svn:keywords
2231 + Author Date Id Revision
Index: trunk/phase3/includes/Setup.php
@@ -58,6 +58,7 @@
5959 wfProfileIn( $fname.'-includes' );
6060
6161 require_once( 'GlobalFunctions.php' );
 62+require_once( 'Hooks.php' );
6263 require_once( 'Namespace.php' );
6364 require_once( 'RecentChange.php' );
6465 require_once( 'User.php' );
@@ -76,7 +77,7 @@
7778 require_once( 'WebRequest.php' );
7879 require_once( 'LoadBalancer.php' );
7980 require_once( 'HistoryBlob.php' );
80 -
 81+
8182 $wgRequest = new WebRequest();
8283
8384
Index: trunk/phase3/includes/Hooks.php
@@ -0,0 +1,130 @@
 2+<?php
 3+/**
 4+ * Hooks.php -- a tool for running hook functions
 5+ * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
 6+ *
 7+ * This program is free software; you can redistribute it and/or modify
 8+ * it under the terms of the GNU General Public License as published by
 9+ * the Free Software Foundation; either version 2 of the License, or
 10+ * (at your option) any later version.
 11+ *
 12+ * This program is distributed in the hope that it will be useful,
 13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15+ * GNU General Public License for more details.
 16+ *
 17+ * You should have received a copy of the GNU General Public License
 18+ * along with this program; if not, write to the Free Software
 19+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20+ *
 21+ * @author <evan@wikitravel.org>
 22+ * @package MediaWiki
 23+ * @seealso hooks.doc
 24+ */
 25+
 26+if (defined('MEDIAWIKI')) {
 27+
 28+ /*
 29+ * Because programmers assign to $wgHooks, we need to be very
 30+ * careful about its contents. So, there's a lot more error-checking
 31+ * in here than would normally be necessary.
 32+ */
 33+
 34+ function wfRunHooks() {
 35+
 36+ global $wgHooks;
 37+
 38+ if (!is_array($wgHooks)) {
 39+ wfDieDebugBacktrace("Global hooks array is not an array!\n");
 40+ return false;
 41+ }
 42+
 43+ $args = func_get_args();
 44+
 45+ if (count($args) < 1) {
 46+ wfDieDebugBacktrace("No event name given for wfRunHooks().\n");
 47+ return false;
 48+ }
 49+
 50+ $event = array_shift($args);
 51+
 52+ if (!array_key_exists($wgHooks, $event)) {
 53+ return true;
 54+ }
 55+
 56+ if (!is_array($wgHooks[$event])) {
 57+ wfDieDebugBacktrace("Hooks array for event '$event' is not an array!\n");
 58+ return false;
 59+ }
 60+
 61+ foreach ($wgHooks[$event] as $hook) {
 62+
 63+ $object = NULL;
 64+ $method = NULL;
 65+ $func = NULL;
 66+ $data = NULL;
 67+ $have_data = false;
 68+
 69+ /* $hook can be: a function, an object, an array of $function and $data,
 70+ * an array of just a function, an array of object and method, or an
 71+ * array of object, method, and data.
 72+ */
 73+
 74+ if (is_array($hook)) {
 75+ if (count($hook) < 1) {
 76+ wfDieDebugBacktrace("Empty array in hooks for " . $event . "\n");
 77+ } else if (is_object($hook[0])) {
 78+ $object = $hook[0];
 79+ if (count($hook) < 2) {
 80+ $method = "on" . $event;
 81+ } else {
 82+ $method = $hook[1];
 83+ if (count($hook) > 2) {
 84+ $data = $hook[2];
 85+ $have_data = true;
 86+ }
 87+ }
 88+ } else if (is_string($hook[0])) {
 89+ $func = $hook[0];
 90+ if (count($hook) > 1) {
 91+ $data = $hook[1];
 92+ $have_data = true;
 93+ }
 94+ } else {
 95+ wfDieDebugBacktrace("Unknown datatype in hooks for " . $event . "\n");
 96+ }
 97+ } else if (is_string($hook)) { # functions look like strings, too
 98+ $func = $hook;
 99+ } else if (is_object($hook)) {
 100+ $object = $hook;
 101+ $method = "on" . $event;
 102+ } else {
 103+ wfDieDebugBacktrace("Unknown datatype in hooks for " . $event . "\n");
 104+ }
 105+
 106+ if ($have_data) {
 107+ $hook_args = array_merge(array($data), $args);
 108+ } else {
 109+ $hook_args = $args;
 110+ }
 111+
 112+ if ($object) {
 113+ $retval = call_user_func_array(array($object, $method), $hook_args);
 114+ } else {
 115+ $retval = call_user_func_array($func, $hook_args);
 116+ }
 117+
 118+ if (is_string($retval)) {
 119+ global $wgOut;
 120+ $wgOut->fatalError($retval);
 121+ return false;
 122+ } else if (!$retval) {
 123+ return false;
 124+ }
 125+ }
 126+
 127+ return true;
 128+ }
 129+}
 130+
 131+?>
Property changes on: trunk/phase3/includes/Hooks.php
___________________________________________________________________
Added: svn:eol-style
1132 + native
Added: svn:keywords
2133 + Author Date Id Revision
Index: trunk/phase3/includes/DefaultSettings.php
@@ -857,6 +857,18 @@
858858 */
859859 $wgAuth = null;
860860
 861+/**
 862+ * Global list of hooks.
 863+ * Add a hook by doing:
 864+ * $wgHooks['event_name'][] = $function;
 865+ * or:
 866+ * $wgHooks['event_name'][] = array($function, $data);
 867+ * or:
 868+ * $wgHooks['event_name'][] = array($object, 'method');
 869+ */
 870+
 871+$wgHooks = array();
 872+
861873 } else {
862874 die();
863875 }

Status & tagging log

  • 15:00, 12 September 2011 Meno25 (Talk | contribs) changed the status of r6405 [removed: ok added: old]
  • 13:54, 18 June 2009 ^demon (Talk | contribs) changed the status of r6405 [removed: new added: ok]
Personal tools
Namespaces

Variants
Views
Actions
Navigation
Support
Download
Development
Communication
Toolbox