MediaWiki  REL1_22
Hooks.php
Go to the documentation of this file.
00001 <?php
00002 
00030 class MWHookException extends MWException {}
00031 
00039 class Hooks {
00040 
00045     protected static $handlers = array();
00046 
00055     public static function register( $name, $callback ) {
00056         if ( !isset( self::$handlers[$name] ) ) {
00057             self::$handlers[$name] = array();
00058         }
00059 
00060         self::$handlers[$name][] = $callback;
00061     }
00062 
00072     public static function clear( $name ) {
00073         if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
00074             throw new MWException( 'Cannot reset hooks in operation.' );
00075         }
00076 
00077         unset( self::$handlers[$name] );
00078     }
00079 
00089     public static function isRegistered( $name ) {
00090         global $wgHooks;
00091         return !empty( $wgHooks[$name] ) || !empty( self::$handlers[$name] );
00092     }
00093 
00103     public static function getHandlers( $name ) {
00104         global $wgHooks;
00105 
00106         if ( !self::isRegistered( $name ) ) {
00107             return array();
00108         } elseif ( !isset( self::$handlers[$name] ) ) {
00109             return $wgHooks[$name];
00110         } elseif ( !isset( $wgHooks[$name] ) ) {
00111             return self::$handlers[$name];
00112         } else {
00113             return array_merge( self::$handlers[$name], $wgHooks[$name] );
00114         }
00115     }
00116 
00135     public static function run( $event, array $args = array() ) {
00136         wfProfileIn( 'hook: ' . $event );
00137         foreach ( self::getHandlers( $event ) as $hook ) {
00138             // Turn non-array values into an array. (Can't use casting because of objects.)
00139             if ( !is_array( $hook ) ) {
00140                 $hook = array( $hook );
00141             }
00142 
00143             if ( !array_filter( $hook ) ) {
00144                 // Either array is empty or it's an array filled with null/false/empty.
00145                 continue;
00146             } elseif ( is_array( $hook[0] ) ) {
00147                 // First element is an array, meaning the developer intended
00148                 // the first element to be a callback. Merge it in so that
00149                 // processing can be uniform.
00150                 $hook = array_merge( $hook[0], array_slice( $hook, 1 ) );
00151             }
00152 
00158             if ( $hook[0] instanceof Closure ) {
00159                 $func = "hook-$event-closure";
00160                 $callback = array_shift( $hook );
00161             } elseif ( is_object( $hook[0] ) ) {
00162                 $object = array_shift( $hook );
00163                 $method = array_shift( $hook );
00164 
00165                 // If no method was specified, default to on$event.
00166                 if ( $method === null ) {
00167                     $method = "on$event";
00168                 }
00169 
00170                 $func = get_class( $object ) . '::' . $method;
00171                 $callback = array( $object, $method );
00172             } elseif ( is_string( $hook[0] ) ) {
00173                 $func = $callback = array_shift( $hook );
00174             } else {
00175                 throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
00176             }
00177 
00178             // Run autoloader (workaround for call_user_func_array bug)
00179             // and throw error if not callable.
00180             if ( !is_callable( $callback ) ) {
00181                 throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" );
00182             }
00183 
00184             /*
00185              * Call the hook. The documentation of call_user_func_array says
00186              * false is returned on failure. However, if the function signature
00187              * does not match the call signature, PHP will issue an warning and
00188              * return null instead. The following code catches that warning and
00189              * provides better error message.
00190              */
00191             $retval = null;
00192             $badhookmsg = null;
00193             $hook_args = array_merge( $hook, $args );
00194 
00195             // Profile first in case the Profiler causes errors.
00196             wfProfileIn( $func );
00197             set_error_handler( 'Hooks::hookErrorHandler' );
00198             try {
00199                 $retval = call_user_func_array( $callback, $hook_args );
00200             } catch ( MWHookException $e ) {
00201                 $badhookmsg = $e->getMessage();
00202             }
00203             restore_error_handler();
00204             wfProfileOut( $func );
00205 
00206             // Process the return value.
00207             if ( is_string( $retval ) ) {
00208                 // String returned means error.
00209                 throw new FatalError( $retval );
00210             } elseif ( $badhookmsg !== null ) {
00211                 // Exception was thrown from Hooks::hookErrorHandler.
00212                 throw new MWException(
00213                     'Detected bug in an extension! ' .
00214                     "Hook $func has invalid call signature; " . $badhookmsg
00215                 );
00216             } elseif ( $retval === false ) {
00217                 wfProfileOut( 'hook: ' . $event );
00218                 // False was returned. Stop processing, but no error.
00219                 return false;
00220             }
00221         }
00222 
00223         wfProfileOut( 'hook: ' . $event );
00224         return true;
00225     }
00226 
00240     public static function hookErrorHandler( $errno, $errstr ) {
00241         if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
00242             throw new MWHookException( $errstr, $errno );
00243         }
00244         return false;
00245     }
00246 }