Page MenuHomePhabricator

Extension onload callback cannot use ConfigFactory for its own configuration in MW >=1.30
Open, Needs TriagePublic

Description

TL;DR:
when myExtension::onExtensionLoad( $info )executes $myConfig = \MediaWiki\MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'myExtension' ); we get a ConfigException. Ideas to fix this can be found further down below. Look for Ideas to solve this.

Problem

in my extension.json I have

"ConfigRegistry": {
	"myExtension": "GlobalVarConfig::newInstance"
},

as well as

"callback": "myExtension\\Setup::onExtensionLoad",

When I am trying to do

public static function onExtensionLoad( $info ) {
	$myConfig = \MediaWiki\MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'myExtension' );
	//...
}

I am getting

PHP Fatal error:  Uncaught exception 'ConfigException' with message 'No registered builder available for myExtension.' in /var/www/mediawiki-1.30.0/includes/config/ConfigFactory.php:131
Stack trace:
#0 /var/www/mediawiki-1.30.0/extensions/MyExtension/src/Setup.php(51): ConfigFactory->makeConfig('myExtension')
#1 [internal function]: MyExtension\Setup::onExtensionLoad(Array)
#2 /var/www/mediawiki-1.30.0/includes/registration/ExtensionRegistry.php(343): call_user_func('BootstrapCompon...', Array)
#3 /var/www/mediawiki-1.30.0/includes/registration/ExtensionRegistry.php(149): ExtensionRegistry->exportExtractedData(Array)
#4 /var/www/mediawiki-1.30.0/includes/Setup.php(40): ExtensionRegistry->loadFromQueue()
#5 /var/www/mediawiki-1.30.0/maintenance/doMaintenance.php(79): require_once('/var/www/medi...')
#6 /var/www/mediawiki-1.30.0/tests/phpunit/phpunit.php(172): require('/var/www/medi...')
#7 {main}
  thrown in /var/www/mediawiki-1.30.0/includes/config/ConfigFactory.php on line 131

I did some digging and found out that

  • /includes/Setup.php calls ExtensionRegistry->loadFromQueue() in line 40
  • loadFromQueue generates a LocalServerObjectCache (in line 135), which through some intermediary calls accesses the MainConfig
  • MainConfig has a Closure in line 102 in ServiceWiring that produces a ConfigFactory with return $services->getConfigFactory()->makeConfig( 'main' );
  • that call finally generates a MediaWiki\Services\ServiceContainer->createService('ConfigFactory') for 'main' in line 361

This Closure is executed:

function ( MediaWikiServices $services ) {
	// Use the bootstrap config to initialize the ConfigFactory.
	$registry = $services->getBootstrapConfig()->get( 'ConfigRegistry' );
	$factory = new ConfigFactory();

	foreach ( $registry as $name => $callback ) {
		$factory->register( $name, $callback );
	}
	return $factory;
}

When the closure is executed, the ConfigFactory object is created with the contents of wgConfigRegistry at the time:

Array
(
    [main] => GlobalVarConfig::newInstance
)

That, as you can see, does not yet include the extension config registry (remember we are at an early stage of ExtensionRegistry->loadFromQueue()). But unfortunately on ConfigFactory creation, the contents of wgConfigRegistry are stored and no updates will be taken.

Looking further into the excecution of the ExtensionRegistry: On line 146 or 149 ExtensionRegistry->exportExtractedData( $data ); is called which (after merging a bunch of arrays) executes the callbacks in line 342. You can find a complete call stack at [0].

Q: Why does the ConfigRegistry work at all for Extensions?
A: /includes/Setup.php (after registering Extensions and doing a bunch of other stuff) 'clears' all Services in line 533 with
MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' );

Ideas to solve this

  • call MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' ); before the execution of the callback hooks in ExtensionRegistry
  • have ExtensionRegistry call ConfigFactory::register for every extension before the callbacks are executed
  • do just that, but in ExtensionProcessor while auto-registering the ConfigRegistry
  • extensions have to register their own config in the onload callback.

Maybe this could be taken care of when addressing T155154? rMWc7cc4ecd81bf is awaiting merge and could be augmented.

[0]:
This dump is taken with a 1.30.0. Current master, however, also throws the described exception.

#0 [internal function]: MediaWiki\Services\ServiceContainer->{closure}(Object(MediaWiki\MediaWikiServices))
#1 /var/www/mediawiki-1.30.0/includes/services/ServiceContainer.php(361): call_user_func_array(Object(Closure), Array)
#2 /var/www/mediawiki-1.30.0/includes/services/ServiceContainer.php(344): MediaWiki\Services\ServiceContainer->createService('ConfigFactory')
#3 /var/www/mediawiki-1.30.0/includes/MediaWikiServices.php(411): MediaWiki\Services\ServiceContainer->getService('ConfigFactory')
#4 /var/www/mediawiki-1.30.0/includes/ServiceWiring.php(104): MediaWiki\MediaWikiServices->getConfigFactory()
#5 [internal function]: MediaWiki\Services\ServiceContainer->{closure}(Object(MediaWiki\MediaWikiServices))
#6 /var/www/mediawiki-1.30.0/includes/services/ServiceContainer.php(361): call_user_func_array(Object(Closure), Array)
#7 /var/www/mediawiki-1.30.0/includes/services/ServiceContainer.php(344): MediaWiki\Services\ServiceContainer->createService('MainConfig')
#8 /var/www/mediawiki-1.30.0/includes/MediaWikiServices.php(422): MediaWiki\Services\ServiceContainer->getService('MainConfig')
#9 /var/www/mediawiki-1.30.0/includes/ServiceWiring.php(386): MediaWiki\MediaWikiServices->getMainConfig()
#10 [internal function]: MediaWiki\Services\ServiceContainer->{closure}(Object(MediaWiki\MediaWikiServices))
#11 /var/www/mediawiki-1.30.0/includes/services/ServiceContainer.php(361): call_user_func_array(Object(Closure), Array)
#12 /var/www/mediawiki-1.30.0/includes/services/ServiceContainer.php(344): MediaWiki\Services\ServiceContainer->createService('LocalServerObje...')
#13 /var/www/mediawiki-1.30.0/includes/MediaWikiServices.php(658): MediaWiki\Services\ServiceContainer->getService('LocalServerObje...')
#14 /var/www/mediawiki-1.30.0/includes/registration/ExtensionRegistry.php(135): MediaWiki\MediaWikiServices->getLocalServerObjectCache()
#15 /var/www/mediawiki-1.30.0/includes/Setup.php(40): ExtensionRegistry->loadFromQueue()
#16 /var/www/mediawiki-1.30.0/includes/WebStart.php(114): require_once('/var/www/medi...')
#17 /var/www/mediawiki-1.30.0/index.php(40): require('/var/www/medi...')
#18 {main}

Event Timeline

Oetterer renamed this task from Extension onload callback cannot use ConfigFactory for its own configuration to Extension onload callback cannot use ConfigFactory for its own configuration in MW >=1.30.Jan 13 2018, 8:33 AM
Oetterer updated the task description. (Show Details)
This comment was removed by Oetterer.

Have you tried using a $wgExtensionFunctions?

Theoretically "callback" is supposed to be comparable to running code during the initial inclusion of the PHP entry point, at which point ConfigFactory wouldn't have been set up either.

Using $wgExtensionFunctions (or the equivalent in extension.json) works. That is because /includes/Setup.php evaluates it in line 840 after the call to MediaWikiServices::resetGlobalInstance( new GlobalVarConfig(), 'quick' ); in line 533.

The callback is insofar not equivalent to the inclusion of the entry point as the ExtensionRegistry initialializes the ConfigFactory before parsing all the extension.json files and executing the callbacks. The inclusion of the entry point would have happend before.

The preferred way for extension loading is to use extension.json. The preferred way to initialize an extension (if necessary) is via the callback. The manual clearly states that using wgExtensionFunctions (or extension.json's ExtensionFunctions) is considered a hack [0] that "suggests that something is going wrong somewhere". Using the ConfigRegistry instead of global is the right way to handle extension configuration and the use of it should be encouraged.

Therefore I still think not being able to use ConfigFactory in the extension load callback is a bug.

[0] https://www.mediawiki.org/wiki/Manual:$wgExtensionFunctions