diff options
author | Nikki Nikkhoui <nnikkhoui@wikimedia.org> | 2020-02-10 11:47:46 -0300 |
---|---|---|
committer | Tim Starling <tstarling@wikimedia.org> | 2020-04-17 15:48:38 +1000 |
commit | 0adc5f342888740e1b611f95f1474be329eae817 (patch) | |
tree | 7d1ccedc5a0016b24592feb69454f67ea3cd2a54 /includes/registration | |
parent | 99a84628f422b28739cf2ff1d42ef43f0f38fe5b (diff) | |
download | mediawikicore-0adc5f342888740e1b611f95f1474be329eae817.tar.gz mediawikicore-0adc5f342888740e1b611f95f1474be329eae817.zip |
Hook Container
New classes and modificatons to existing classes to support the new Hooks system. All changes are documented in RFC https://phabricator.wikimedia.org/T240307.
- HookContainer.php: Class for doing much of what Hooks.php has historically done, but enabling new-style hooks to be processed and registered. Changes include new ways of defining hook handler functions as an object with defined dependencies in extension.json, removing runWithoutAbort() and addit it to an $options parameter to be passed to HookContainer::run(), being able to decipher whether a hook handler is legacy or non-legacy style and run them in the appropriate way, etc.
- DeprecatedHooks.php: For marking hooks deprecated and verifying if one is deprecated
- DeprecatedHooksTest.php: Unit tests for DeprecatedHooks.php
- Hooks.php: register() will now additionally register hooks with handlers in new HooksContainer.php. getHandlers() will be a legacy wrapper for calling the newer HookContainer::getHandlers()
- MediaWikiServices.php: Added getHookContainer() for retrieving HookContainer singleton
- ExtensionProcessor.php: modified extractHooks() to be able to extract new style handler objects being registered in extension.json
- ServiceWiring.php: Added HookContainer to list of services to return
- HookContainerTest.php: Unit tests for HookContainer.php
- ExtensionProcessorTest.php: Moved file out of /unit folder and now extends MediaWikiTestCase instead of MediaWikiUnitTestCase (as the tests are not truly unit tests). Modified existing tests for ExtensionProcessor::extractHooks() to include a test case for new style handler
Bug: T240307
Change-Id: I432861d8995cfd7180e77e115251d8055b7eceec
Diffstat (limited to 'includes/registration')
-rw-r--r-- | includes/registration/ExtensionProcessor.php | 129 |
1 files changed, 118 insertions, 11 deletions
diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php index 7a085ba8c11f..ea8ea216fef2 100644 --- a/includes/registration/ExtensionProcessor.php +++ b/includes/registration/ExtensionProcessor.php @@ -1,5 +1,7 @@ <?php +use MediaWiki\HookRunner\DeprecatedHooks; + class ExtensionProcessor implements Processor { /** @@ -196,7 +198,7 @@ class ExtensionProcessor implements Processor { */ public function extractInfo( $path, array $info, $version ) { $dir = dirname( $path ); - $this->extractHooks( $info ); + $this->extractHooks( $info, $path ); $this->extractExtensionMessagesFiles( $dir, $info ); $this->extractMessagesDirs( $dir, $info ); $this->extractNamespaces( $info ); @@ -258,7 +260,6 @@ class ExtensionProcessor implements Processor { $this->storeToArray( $path, $key, $val, $this->attributes ); } } - } } @@ -274,7 +275,39 @@ class ExtensionProcessor implements Processor { } } + /** + * Will throw wfDeprecated() warning if: + * 1. an extension does not acknowledge deprecation and + * 2. is marked deprecated + */ + private function emitDeprecatedHookWarnings() { + if ( !isset( $this->attributes['Hooks'] ) ) { + return; + } + $extDeprecatedHooks = $this->attributes['DeprecatedHooks'] ?? false; + if ( !$extDeprecatedHooks ) { + return; + } + $deprecatedHooks = new DeprecatedHooks( $extDeprecatedHooks ); + foreach ( $this->attributes['Hooks'] as $name => $handlers ) { + if ( $deprecatedHooks->isHookDeprecated( $name ) ) { + $deprecationInfo = $deprecatedHooks->getDeprecationInfo( $name ); + foreach ( $handlers as $handler ) { + if ( !isset( $handler['deprecated'] ) || !$handler['deprecated'] ) { + wfDeprecated( + "$name hook", + $deprecationInfo['deprecatedVersion'] ?? false, + $deprecationInfo['component'] ?? false + ); + } + } + } + } + } + public function getExtractedInfo() { + $this->emitDeprecatedHookWarnings(); + // Make sure the merge strategies are set foreach ( $this->globals as $key => $val ) { if ( isset( self::MERGE_STRATEGIES[$key] ) ) { @@ -376,25 +409,99 @@ class ExtensionProcessor implements Processor { ); } } - return $merged; } - protected function extractHooks( array $info ) { - if ( isset( $info['Hooks'] ) ) { - foreach ( $info['Hooks'] as $name => $value ) { - if ( is_array( $value ) ) { - foreach ( $value as $callback ) { - $this->globals['wgHooks'][$name][] = $callback; + /** + * When handler value is an array, set $wgHooks or Hooks attribute + * Could be legacy hook e.g. 'GlobalFunctionName' or non-legacy hook + * referencing a handler definition from 'HookHandler' attribute + * + * @param array $callback Handler + * @param array $hookHandlersAttr handler definitions from 'HookHandler' attribute + * @param string $name + * @param string $path extension.json file path + * @throws UnexpectedValueException + */ + private function setArrayHookHandler( + array $callback, + array $hookHandlersAttr, + string $name, + string $path + ) { + if ( isset( $callback['handler'] ) ) { + $handlerName = $callback['handler']; + $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false; + if ( !$handlerDefinition ) { + throw new UnexpectedValueException( + "Missing handler definition for $name in HookHandlers attribute in $path" + ); + } + $callback['handler'] = $handlerDefinition; + $this->attributes['Hooks'][$name][] = $callback; + } else { + foreach ( $callback as $callable ) { + if ( is_array( $callable ) ) { + if ( isset( $callable['handler'] ) ) { // Non-legacy style handler + $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path ); + } else { // Legacy style handler array + $this->globals['wgHooks'][$name][] = $callable; } - } else { - $this->globals['wgHooks'][$name][] = $value; + } elseif ( is_string( $callable ) ) { + $this->setStringHookHandler( $callable, $hookHandlersAttr, $name ); } } } } /** + * When handler value is a string, set $wgHooks or Hooks attribute. + * Could be legacy hook e.g. 'GlobalFunctionName' or non-legacy hook + * referencing a handler definition from 'HookHandler' attribute + * + * @param string $callback Handler + * @param array $hookHandlersAttr handler definitions from 'HookHandler' attribute + * @param string $name + */ + private function setStringHookHandler( + string $callback, + array $hookHandlersAttr, + string $name + ) { + if ( isset( $hookHandlersAttr[$callback] ) ) { + $handler = [ 'handler' => $hookHandlersAttr[$callback] ]; + $this->attributes['Hooks'][$name][] = $handler; + } else { // legacy style handler + $this->globals['wgHooks'][$name][] = $callback; + } + } + + /** + * Extract hook information from Hooks and HookHandler attributes. + * Store hook in $wgHooks if a legacy style handler or the 'Hooks' attribute if + * a non-legacy handler + * + * @param array $info attributes and associated values from extension.json + * @param string $path path to extension.json + */ + protected function extractHooks( array $info, string $path ) { + if ( !isset( $info['Hooks'] ) ) { + return; + } + $hookHandlersAttr = []; + foreach ( $info['HookHandlers'] ?? [] as $name => $def ) { + $hookHandlersAttr[$name] = [ 'name' => "$path-$name" ] + $def; + } + foreach ( $info['Hooks'] as $name => $callback ) { + if ( is_string( $callback ) ) { + $this->setStringHookHandler( $callback, $hookHandlersAttr, $name ); + } elseif ( is_array( $callback ) ) { + $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path ); + } + } + } + + /** * Register namespaces with the appropriate global settings * * @param array $info |