MediaWiki  REL1_24
Hooks.php
Go to the documentation of this file.
00001 <?php
00002 
00030 class MWHookException extends MWException {
00031 }
00032 
00040 class Hooks {
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 
00136     public static function run( $event, array $args = array(), $deprecatedVersion = null ) {
00137         wfProfileIn( 'hook: ' . $event );
00138         foreach ( self::getHandlers( $event ) as $hook ) {
00139             // Turn non-array values into an array. (Can't use casting because of objects.)
00140             if ( !is_array( $hook ) ) {
00141                 $hook = array( $hook );
00142             }
00143 
00144             if ( !array_filter( $hook ) ) {
00145                 // Either array is empty or it's an array filled with null/false/empty.
00146                 continue;
00147             } elseif ( is_array( $hook[0] ) ) {
00148                 // First element is an array, meaning the developer intended
00149                 // the first element to be a callback. Merge it in so that
00150                 // processing can be uniform.
00151                 $hook = array_merge( $hook[0], array_slice( $hook, 1 ) );
00152             }
00153 
00159             if ( $hook[0] instanceof Closure ) {
00160                 $func = "hook-$event-closure";
00161                 $callback = array_shift( $hook );
00162             } elseif ( is_object( $hook[0] ) ) {
00163                 $object = array_shift( $hook );
00164                 $method = array_shift( $hook );
00165 
00166                 // If no method was specified, default to on$event.
00167                 if ( $method === null ) {
00168                     $method = "on$event";
00169                 }
00170 
00171                 $func = get_class( $object ) . '::' . $method;
00172                 $callback = array( $object, $method );
00173             } elseif ( is_string( $hook[0] ) ) {
00174                 $func = $callback = array_shift( $hook );
00175             } else {
00176                 throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
00177             }
00178 
00179             // Run autoloader (workaround for call_user_func_array bug)
00180             // and throw error if not callable.
00181             if ( !is_callable( $callback ) ) {
00182                 throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" );
00183             }
00184 
00185             /*
00186              * Call the hook. The documentation of call_user_func_array says
00187              * false is returned on failure. However, if the function signature
00188              * does not match the call signature, PHP will issue an warning and
00189              * return null instead. The following code catches that warning and
00190              * provides better error message.
00191              */
00192             $retval = null;
00193             $badhookmsg = null;
00194             $hook_args = array_merge( $hook, $args );
00195 
00196             // Profile first in case the Profiler causes errors.
00197             wfProfileIn( $func );
00198             set_error_handler( 'Hooks::hookErrorHandler' );
00199 
00200             // mark hook as deprecated, if deprecation version is specified
00201             if ( $deprecatedVersion !== null ) {
00202                 wfDeprecated( "$event hook (used in $func)", $deprecatedVersion );
00203             }
00204 
00205             try {
00206                 $retval = call_user_func_array( $callback, $hook_args );
00207             } catch ( MWHookException $e ) {
00208                 $badhookmsg = $e->getMessage();
00209             } catch ( Exception $e ) {
00210                 restore_error_handler();
00211                 throw $e;
00212             }
00213             restore_error_handler();
00214             wfProfileOut( $func );
00215 
00216             // Process the return value.
00217             if ( is_string( $retval ) ) {
00218                 // String returned means error.
00219                 throw new FatalError( $retval );
00220             } elseif ( $badhookmsg !== null ) {
00221                 // Exception was thrown from Hooks::hookErrorHandler.
00222                 throw new MWException(
00223                     'Detected bug in an extension! ' .
00224                     "Hook $func has invalid call signature; " . $badhookmsg
00225                 );
00226             } elseif ( $retval === false ) {
00227                 wfProfileOut( 'hook: ' . $event );
00228                 // False was returned. Stop processing, but no error.
00229                 return false;
00230             }
00231         }
00232 
00233         wfProfileOut( 'hook: ' . $event );
00234         return true;
00235     }
00236 
00250     public static function hookErrorHandler( $errno, $errstr ) {
00251         if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
00252             throw new MWHookException( $errstr, $errno );
00253         }
00254         return false;
00255     }
00256 }