PHP API reference for HookContainer: running, registering, and testing hooks.
HookContainer (namespace MediaWiki\HookContainer, since MediaWiki 1.35) is the service responsible for maintaining hook handler lists and dispatching hook calls. It replaced the legacy $wgHooks global array as the canonical way to invoke and register hooks.
use MediaWiki\HookContainer\HookContainer;use MediaWiki\MediaWikiServices;// In a service constructor (preferred)class MyService { private HookContainer $hookContainer; public function __construct( HookContainer $hookContainer ) { $this->hookContainer = $hookContainer; }}// In static code (fallback only)$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
public function run( string $hook, array $args = [], array $options = [] ): bool
Invokes all handlers registered for $hook, passing $args as arguments. Returns true if no handler aborted the call, or false if a handler returned false and the hook is abortable.Parameters
public function isRegistered( string $hook ): bool
Returns true if at least one handler is registered for $hook. Useful for skipping expensive preparation work when no extension is listening.
if ( $hookContainer->isRegistered( 'BeforePageDisplay' ) ) { // prepare data only if someone will use it $hookContainer->run( 'BeforePageDisplay', [ $output, $skin ] );}
HookContainer::NOOP to register an explicit no-op.
// Register a closure$hookContainer->register( 'BeforePageDisplay', static function ( $out, $skin ) { $out->addModules( 'ext.myExtension' );} );// Register an object with onBeforePageDisplay()$hookContainer->register( 'BeforePageDisplay', new MyHookHandler() );
#[\NoDiscard]public function scopedRegister( string $hook, callable|string|array $handler ): ScopedCallback
Registers a handler and returns a ScopedCallback. The handler is automatically removed when the callback goes out of scope or is explicitly destroyed. Designed for temporary registrations, especially in tests.
$scope = $hookContainer->scopedRegister( 'PageSaveComplete', $myHandler );// ... handler is active here ...unset( $scope ); // handler is removed
Returns a deduplicated list of all hook names that have at least one registered handler, across $wgHooks, extension.json, and runtime-registered handlers.
Rather than calling HookContainer::run() directly, MediaWiki core uses typed hook runner classes that provide a method per hook. This gives IDE support and static analysis for hook arguments.MediaWiki core ships three runners:
MediaWiki\HookContainer\HookRunner — hooks called by general core code.
MediaWiki\Api\ApiHookRunner — hooks called by the Action API.
MediaWiki\ResourceLoader\HookRunner — hooks specific to ResourceLoader.
use MediaWiki\HookContainer\HookRunner;use MediaWiki\MediaWikiServices;// Calling the PageSaveComplete hook via HookRunner$hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer());$hookRunner->onPageSaveComplete( $wikiPage, $user, $summary, $flags, $revisionRecord, $editResult );
Extensions that call their own hooks should define their own runner class:
The handler class must implement the hook interface and provide the corresponding on{HookName} method:
namespace MediaWiki\Extension\FoodProcessor;use MediaWiki\Hook\MashHook;use MediaWiki\ReadOnlyMode;class HookHandler implements MashHook { private ReadOnlyMode $readOnlyMode; public function __construct( ReadOnlyMode $readOnlyMode ) { $this->readOnlyMode = $readOnlyMode; } public function onMash( string $banana ): void { if ( $this->readOnlyMode->isReadOnly() ) { return; } // handle the hook }}
Use a separate HookHandlers entry and a separate handler class for each hook. This ensures that services needed by one hook are not instantiated when an unrelated hook fires.
When a hook is called with [ 'noServices' => true ], any handler that declares services will cause HookContainer to throw an UnexpectedValueException. This option is used for hooks that fire very early in the request lifecycle before all services are available.
When deprecated: true is set and MediaWiki considers the hook deprecated, the handler is not called (filtered out). If MediaWiki does not yet know about the deprecation, the handler is called normally. This allows simultaneous forward and backward compatibility.
use MediaWikiIntegrationTestCase;class MyHookTest extends MediaWikiIntegrationTestCase { public function testHookIsCalled(): void { $called = false; $scope = $this->getServiceContainer() ->getHookContainer() ->scopedRegister( 'PageSaveComplete', static function () use ( &$called ) { $called = true; } ); // ... perform the action that triggers the hook ... $this->assertTrue( $called ); // $scope goes out of scope here, handler is removed automatically }}
clear() only works when MW_PHPUNIT_TEST is defined. In integration tests, the hook container is reset between test cases by the test framework, so clear() is rarely needed unless you want to isolate a specific test method from handlers registered globally.