jenkins-bot has submitted this change and it was merged.
Change subject: Introduce top level service locator.
......................................................................
Introduce top level service locator.
The service locator, MediaWikiServices, is intended to facilitate
"manual" dependency injection in static entry points.
See also the Dependency Injection RFC T384 and Service Locator
RFC T124792 for details.
The following key points were implemented according the
discussion surrounding these RFCs:
* a configurable DI container that allows extensions to add and replace
services.
* no auto-wiring, since it's prone to add confusion in large and complex
applications.
* no 3rd party framework, since they typically do too much.
The following services in MediaWiki core are made accessible via the service
locator
mechanism to showcase the bootstrapping mechanism:
* ConfigFactory and MainConfig
* SiteLookup and SiteStore
However, the implementation of these services was not yet converted to using
proper DI
throughout the code.
Bug: T124792
Change-Id: I3c25c0ac17300d3dd13e1cf5100558a605eee15f
---
M autoload.php
M docs/hooks.txt
A docs/injection.txt
M includes/DefaultSettings.php
A includes/MediaWikiServices.php
A includes/ServiceWiring.php
A includes/Services/ServiceContainer.php
M includes/WikiMap.php
M includes/config/ConfigFactory.php
M includes/site/DBSiteStore.php
M includes/site/SiteSQLStore.php
M maintenance/exportSites.php
M maintenance/importSites.php
M maintenance/rebuildSitesCache.php
A tests/phpunit/includes/MediaWikiServicesTest.php
A tests/phpunit/includes/Services/ServiceContainerTest.php
A tests/phpunit/includes/Services/TestWiring1.php
A tests/phpunit/includes/Services/TestWiring2.php
M tests/phpunit/includes/config/ConfigFactoryTest.php
M tests/phpunit/includes/site/DBSiteStoreTest.php
D tests/phpunit/includes/site/SiteSQLStoreTest.php
M tests/phpunit/includes/site/TestSites.php
22 files changed, 1,106 insertions(+), 127 deletions(-)
Approvals:
BryanDavis: Looks good to me, approved
Smalyshev: Looks good to me, but someone else must approve
Addshore: Looks good to me, but someone else must approve
jenkins-bot: Verified
diff --git a/autoload.php b/autoload.php
index a4c09e0..43d9186 100644
--- a/autoload.php
+++ b/autoload.php
@@ -780,6 +780,7 @@
'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php',
'MediaWikiTitleCodec' => __DIR__ .
'/includes/title/MediaWikiTitleCodec.php',
'MediaWikiVersionFetcher' => __DIR__ .
'/includes/MediaWikiVersionFetcher.php',
+ 'MediaWiki\\MediaWikiServices' => __DIR__ .
'/includes/MediaWikiServices.php',
'MediaWiki\\Languages\\Data\\Names' => __DIR__ .
'/languages/data/Names.php',
'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ .
'/languages/data/ZhConversion.php',
'MediaWiki\\Logger\\LegacyLogger' => __DIR__ .
'/includes/debug/logger/LegacyLogger.php',
@@ -796,6 +797,7 @@
'MediaWiki\\Logger\\Monolog\\WikiProcessor' => __DIR__ .
'/includes/debug/logger/monolog/WikiProcessor.php',
'MediaWiki\\Logger\\NullSpi' => __DIR__ .
'/includes/debug/logger/NullSpi.php',
'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
+ 'MediaWiki\\Services\\ServiceContainer' => __DIR__ .
'/includes/Services/ServiceContainer.php',
'MediaWiki\\Session\\BotPasswordSessionProvider' => __DIR__ .
'/includes/session/BotPasswordSessionProvider.php',
'MediaWiki\\Session\\CookieSessionProvider' => __DIR__ .
'/includes/session/CookieSessionProvider.php',
'MediaWiki\\Session\\ImmutableSessionProviderWithCookie' => __DIR__ .
'/includes/session/ImmutableSessionProviderWithCookie.php',
diff --git a/docs/hooks.txt b/docs/hooks.txt
index 9478f48..0e8730d 100644
--- a/docs/hooks.txt
+++ b/docs/hooks.txt
@@ -1997,6 +1997,11 @@
$request: $wgRequest
$mediaWiki: The $mediawiki object
+'MediaWikiServices': Override services in the default MediaWikiServices
instance.
+Extensions may use this to define, replace, or wrap existing services.
+However, the preferred way to define a new service is the
$wgServiceWiringFiles array.
+$services: MediaWikiServices
+
'MessageCache::get': When fetching a message. Can be used to override the key
for customisations. Given and returned message key must be in special format:
1) first letter must be in lower case according to the content language.
diff --git a/docs/injection.txt b/docs/injection.txt
new file mode 100644
index 0000000..e0466c4
--- /dev/null
+++ b/docs/injection.txt
@@ -0,0 +1,248 @@
+injection.txt
+
+This is an overview of how MediaWiki makes use of dependency injection.
+The design described here grew from the discussion of RFC T384.
+
+
+The term "dependency injection" (DI) refers to a pattern on object oriented
+programming that tries to improve modularity by reducing strong coupling
+between classes. In practical terms, this means that anything an object needs
+to operate should be injected from the outside, the object itself should only
+know narrow interfaces, no concrete implementation of the logic it relies on.
+
+The requirement to inject everything typically results in an architecture that
+based on two main types of objects: simple value objects with no business logic
+(and often immutable), and essentially stateless service objects that use
+other service objects to operate on the value objects.
+
+As of the beginning of 2016 (MW version 1.27), MediaWiki is only starting to
+use the DI approach. Much of the code still relies on global state or direct
+instantiation, resulting in a highly cyclical dependency graph.
+
+
+== Overview ==
+The heart of the DI in MediaWiki is the central service locator,
+MediaWikiServices, which acts as the top level factory for services in
+MediaWiki. MediaWikiServices::getInstance() returns the default service
+locator instance, which can be used to gain access to default instances of
+various services. MediaWikiServices however also allows new services to be
+defined and default services to be redefined. Services are defined or
+redefined by providing a callback function, the "instantiator" function,
+that will return a new instance of the service.
+
+When MediaWikiServices::getInstance() is first called, it will create an
+instance of MediaWikiServices and populate it with the services defined
+in the files listed by $wgServiceWiringFiles, thereby "bootstrapping" the
+DI framework. Per default, $wgServiceWiringFiles lists
+includes/ServiceWiring.php, which defines all default service
+implementations, and specifies how they depend on each other ("wiring").
+
+When a new service is added to MediaWiki core, an instantiator function
+that will create the appropriate default instance for that service must
+be added to ServiceWiring.php. This makes the service available through
+the generic getService() method on the service locator returned by
+MediaWikiServices::getInstance().
+
+Extensions can add their own wiring files to $wgServiceWiringFiles, in order
+to define their own service. Extensions may also use the 'MediaWikiServices'
+hook to define or redefined services by calling methods on the default
+MediaWikiServices instance.
+
+
+It should be noted that the term "service locator" is often used to refer to a
+top level factory that is accessed directly, throughout the code, to avoid
+explicit dependency injection. In contrast, the term "DI container" is often
+used to describe a top level factory that is only accessed when services
+are created. We use the term "service locator" for the top level factory
+because it is more descriptive than "DI container", even though application
+logic is strongly discouraged from accessing MediaWikiServices directly.
+MediaWikiServices::getInstance() should ideally be accessed only in "static
+entry points" such as hook handler functions. See "Migration" below.
+
+
+== Configuration ==
+
+When the default MediaWikiServices instance is created, a Config object is
+provided to the constructor. This Config object represents the "bootstrap"
+configuration which will become available as the 'BootstrapConfig' service.
+As of MW 1.27, the bootstrap config is a GlobalVarConfig object providing
+access to the $wgXxx configuration variables.
+
+The bootstrap config is then used to construct a 'ConfigFactory' service,
+which in turn is used to construct the 'MainConfig' service. Application
+logic should use the 'MainConfig' service (or a more specific configuration
+object). 'BootstrapConfig' should only be used for bootstrapping basic
+services that are needed to load the 'MainConfig'.
+
+
+Note: Several well known services in MediaWiki core act as factories
+themselves, e.g. ApiModuleManager, ObjectCache, SpecialPageFactory, etc.
+The registries these factories are based on are currently managed as part of
+the configuration. This may however change in the future.
+
+
+== Migration ==
+
+This section provides some recipes for improving code modularity by reducing
+strong coupling. The dependency injection mechanism described above is an
+essential tool in this effort.
+
+Migrate access to global service instances and config variables:
+Assume Foo is a class that uses the $wgScriptPath global and calls
+wfGetDB() to get a database connection, in non-static methods.
+* Add $scriptPath as a constructor parameter and use $this->scriptPath
+ instead of $wgScriptPath.
+* Add LoadBalancer $dbLoadBalancer as a constructor parameter. Use
+ $this->dbLoadBalancer->getConnection() instead of wfGetDB().
+* Any code that calls Foo's constructor would now need to provide the
+ $scriptPath and $dbLoadBalancer. To avoid this, avoid direct instantiation
+ of services all together - see below.
+
+Migrate class-level singleton getters:
+Assume class Foo has mostly non-static methods, and provides a static
+getInstance() method that returns a singleton (or default instance).
+* Add an instantiator function for Foo into ServiceWiring.php. The instantiator
+ would do exactly what Foo::getInstance() did. However, it should
+ replace any access to global state with calls to $services->getXxx() to get a
+ service, or $services->getMainConfig()->get() to get a configuration setting.
+* Add a getFoo() method to MediaWikiServices. Don't forget to add the
+ appropriate test cases in MediaWikiServicesTest.
+* Turn Foo::getInstance() into a deprecated alias for
+ MediaWikiServices::getInstance()->getFoo(). Change all calls to
+ Foo::getInstance() to use injection (see above).
+
+Migrate direct service instantiation:
+Assume class Bar calls new Foo().
+* Add an instantiator function for Foo into ServiceWiring.php and add a
getFoo()
+ method to MediaWikiServices. Don't forget to add the appropriate test cases
+ in MediaWikiServicesTest.
+* In the instantiator, replace any access to global state with calls
+ to $services->getXxx() to get a service, or $services->getMainConfig()->get()
+ to get a configuration setting.
+* The code in Bar that calls Foo's constructor should be changed to have a Foo
+ instance injected; Eventually, the only code that instantiates Foo is the
+ instantiator in ServiceWiring.php.
+* As an intermediate step, Bar's constructor could initialize the $foo member
+ variable by calling MediaWikiServices::getInstance()->getFoo(). This is
+ acceptable as a stepping stone, but should be replaced by proper injection
+ via a constructor argument. Do not however inject the MediaWikiServices
+ object!
+
+Migrate parameterized helper instantiation:
+Assume class Bar creates some helper object by calling new Foo( $x ),
+and Foo uses a global singleton of the Xyzzy service.
+* Define a FooFactory class (or a FooFactory interface along with a
MyFooFactory
+ implementation). FooFactory defines the method newFoo( $x ) or getFoo( $x ),
+ depending on the desired semantics (newFoo would guarantee a fresh instance).
+ When Foo gets refactored to have Xyzzy injected, FooFactory will need a
+ Xyzzy instance, so newFoo() can pass it to new Foo().
+* Add an instantiator function for FooFactory into ServiceWiring.php and add a
+ getFooFactory() method to MediaWikiServices. Don't forget to add the
+ appropriate test cases in MediaWikiServicesTest.
+* The code in Bar that calls Foo's constructor should be changed to have a
+ FooFactory instance injected; Eventually, the only code that instantiates
+ Foo are implementations of FooFactory, and the only code that instantiates
+ FooFactory is the instantiator in ServiceWiring.php.
+* As an intermediate step, Bar's constructor could initialize the $fooFactory
+ member variable by calling MediaWikiServices::getInstance()->getFooFactory().
+ This is acceptable as a stepping stone, but should be replaced by proper
+ injection via a constructor argument. Do not however inject the
+ MediaWikiServices object!
+
+Migrate a handler registry:
+Assume class Bar calls FooRegistry::getFoo( $x ) to get a specialized Foo
+instance for handling $x.
+* Turn getFoo into a non-static method.
+* Add an instantiator function for FooRegistry into ServiceWiring.php and add
+ a getFooRegistry() method to MediaWikiServices. Don't forget to add the
+ appropriate test cases in MediaWikiServicesTest.
+* Change all code that calls FooRegistry::getFoo() statically to call this
+ method on a FooRegistry instance. That is, Bar would have a $fooRegistry
+ member, initialized from a constructor parameter.
+* As an intermediate step, Bar's constructor could initialize the $fooRegistry
+ member variable by calling MediaWikiServices::getInstance()->
+ getFooRegistry(). This is acceptable as a stepping stone, but should be
+ replaced by proper injection via a constructor argument. Do not however
+ inject the MediaWikiServices object!
+
+Migrate deferred service instantiation:
+Assume class Bar calls new Foo(), but only when needed, to avoid the cost of
+instantiating Foo().
+* Define a FooFactory interface and a MyFooFactory implementation of that
+ interface. FooFactory defines the method getFoo() with no parameters.
+* Precede as for the "parameterized helper instantiation" case described above.
+
+Migrate a class with only static methods:
+Assume Foo is a class with only static methods, such as frob(), which
+interacts with global state or system resources.
+* Introduce a FooService interface and a DefaultFoo implementation of that
+ interface. FooService contains the public methods defined by Foo.
+* Add an instantiator function for FooService into ServiceWiring.php and
+ add a getFooService() method to MediaWikiServices. Don't forget to
+ add the appropriate test cases in MediaWikiServicesTest.
+* Add a private static getFooService() method to Foo. That method just
+ calls MediaWikiServices::getInstance()->getFooService().
+* Make all methods in Foo delegate to the FooService returned by
+ getFooService(). That is, Foo::frob() would do self::getFooService()->frob().
+* Deprecate Foo. Inject a FooService into all code that calls methods
+ on Foo, and change any calls to static methods in foo to the methods
+ provided by the FooService interface.
+
+Migrate static hook handler functions (to allow unit testing):
+Assume MyExtHooks::onFoo is a static hook handler function that is called with
+the parameter $x; Further assume MyExt::onFoo needs service Bar, which is
+already known to MediaWikiServices (if not, see above).
+* Create a non-static doFoo( $x ) method in MyExtHooks that has the same
+ signature as onFoo( $x ). Move the code from onFoo() into doFoo(), replacing
+ any access to global or static variables with access to instance member
+ variables.
+* Add a constructor to MyExtHooks that takes a Bar service as a parameter.
+* Add a static method called newFromGlobalState() with no parameters. It should
+ just return new MyExtHooks( MediaWikiServices::getBar() ).
+* The original static handler method onFoo( $x ) is then implemented as
+ self::newFromGlobalState()->doFoo( $x ).
+
+Migrate a "smart record":
+Assume Thingy is a "smart record" that "knows" how to load and store itself.
+For this purpose, Thingy uses wfGetDB().
+* Create a "dumb" value class ThingyRecord that contains all the information
+ that Thingy represents (e.g. the information from a database row). The value
+ object should not know about any service.
+* Create a DAO-style service for loading and storing ThingyRecords, called
+ ThingyStore. It may be useful to split the interfaces for reading and
+ writing, with a single class implementing both interfaces, so we in the
+ end have the ThingyLookup and ThingyStore interfaces, and a SqlThingyStore
+ implementation.
+* Add instantiator functions for ThingyLookup and ThingyStore in
+ ServiceWiring.php. Since we want to use the same instance for both service
+ interfaces, the instantiator for ThingyLookup would return
+ $services->getThingyStore().
+* Add getThingyLookup() and getThingyStore methods to MediaWikiServices.
+ Don't forget to add the appropriate test cases in MediaWikiServicesTest.
+* In the old Thingy class, replace all member variables that represent the
+ record's data with a single ThingyRecord object.
+* In the old Thingy class, replace all calls to static methods or functions,
+ such as wfGetDB(), with calls to the appropriate services, such as
+ LoadBalancer::getConnection().
+* In Thingy's constructor, pull in any services needed, such as the
+ LoadBalancer, by using MediaWikiServices::getInstance(). These services
+ cannot be injected without changing the constructor signature, which
+ is often impractical for "smart records" that get instantiated directly
+ in many places in the code base.
+* Deprecate the old Thingy class. Replace all usages of it with one of the
+ three new classes: loading needs a ThingyLookup, storing needs a ThingyStore,
+ and reading data needs a ThingyRecord.
+
+Migrate lazy loading:
+Assume Thingy is a "smart record" as described above, but requires lazy loading
+of some or all the data it represents.
+* Instead of a plain object, define ThingyRecord to be an interface. Provide a
+ "simple" and "lazy" implementations, called SimpleThingyRecord and
+ LazyThingyRecord. LazyThingyRecord knows about some lower level storage
+ interface, like a LoadBalancer, and uses it to load information on demand.
+* Any direct instantiation of a ThingyRecord would use the SimpleThingyRecord
+ implementation.
+* SqlThingyStore however creates instances of LazyThingyRecord, and injects
+ whatever storage layer service LazyThingyRecord needs to perform lazy
loading.
+
+
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 4950359..017795c 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -6885,6 +6885,21 @@
$wgHooks = [];
/**
+ * List of service wiring files to be loaded by the default instance of
MediaWikiServices.
+ * Each file listed here is expected to return an associative array mapping
service names
+ * to instantiator functions. Extensions may add wiring files to define their
own services.
+ * However, this cannot be used to replace existing services - use the
MediaWikiServices
+ * hook for that.
+ *
+ * @see MediaWikiServices
+ * @see ServiceContainer::loadWiringFiles() for details on loading service
instantiator functions.
+ * @see docs/injection.txt for an overview of dependency injection in
MediaWiki.
+ */
+$wgServiceWiringFiles = [
+ __DIR__ . '/ServiceWiring.php'
+];
+
+/**
* Maps jobs to their handling classes; extensions
* can add to this to provide custom jobs
*/
diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php
new file mode 100644
index 0000000..7b1def9
--- /dev/null
+++ b/includes/MediaWikiServices.php
@@ -0,0 +1,153 @@
+<?php
+namespace MediaWiki;
+
+use ConfigFactory;
+use GlobalVarConfig;
+use Config;
+use Hooks;
+use LBFactory;
+use LoadBalancer;
+use MediaWiki\Services\ServiceContainer;
+use SiteLookup;
+use SiteStore;
+
+/**
+ * Service locator for MediaWiki core services.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * MediaWikiServices is the service locator for the application scope of
MediaWiki.
+ * Its implemented as a simple configurable DI container.
+ * MediaWikiServices acts as a top level factory/registry for top level
services, and builds
+ * the network of service objects that defines MediaWiki's application logic.
+ * It acts as an entry point to MediaWiki's dependency injection mechanism.
+ *
+ * Services are defined in the "wiring" array passed to the constructor,
+ * or by calling defineService().
+ *
+ * @see docs/injection.txt for an overview of using dependency injection in the
+ * MediaWiki code base.
+ */
+class MediaWikiServices extends ServiceContainer {
+
+ /**
+ * Returns the global default instance of the top level service locator.
+ *
+ * The default instance is initialized using the service instantiator
functions
+ * defined in ServiceWiring.php.
+ *
+ * @note This should only be called by static functions! The instance
returned here
+ * should not be passed around! Objects that need access to a service
should have
+ * that service injected into the constructor, never a service locator!
+ *
+ * @return MediaWikiServices
+ */
+ public static function getInstance() {
+ static $instance = null;
+
+ if ( $instance === null ) {
+ // NOTE: constructing GlobalVarConfig here is not
particularly pretty,
+ // but some information from the global scope has to be
injected here,
+ // even if it's just a file name or database
credentials to load
+ // configuration from.
+ $config = new GlobalVarConfig();
+ $instance = new self( $config );
+
+ // Load the default wiring from the specified files.
+ $wiringFiles = $config->get( 'ServiceWiringFiles' );
+ $instance->loadWiringFiles( $wiringFiles );
+
+ // Provide a traditional hook point to allow extensions
to configure services.
+ Hooks::run( 'MediaWikiServices', [ $instance ] );
+ }
+
+ return $instance;
+ }
+
+ /**
+ * @param Config $config The Config object to be registered as the
'BootstrapConfig' service.
+ * This has to contain at least the information needed to set up
the 'ConfigFactory'
+ * service.
+ */
+ public function __construct( Config $config ) {
+ parent::__construct();
+
+ // register the given Config object as the bootstrap config
service.
+ $this->defineService( 'BootstrapConfig', function() use (
$config ) {
+ return $config;
+ } );
+ }
+
+ /**
+ * Returns the Config object containing the bootstrap configuration.
+ * Bootstrap configuration would typically include database credentials
+ * and other information that may be needed before the ConfigFactory
+ * service can be instantiated.
+ *
+ * @note This should only be used during bootstrapping, in particular
+ * when creating the MainConfig service. Application logic should
+ * use getMainConfig() to get a Config instances.
+ *
+ * @return Config
+ */
+ public function getBootstrapConfig() {
+ return $this->getService( 'BootstrapConfig' );
+ }
+
+ /**
+ * @return ConfigFactory
+ */
+ public function getConfigFactory() {
+ return $this->getService( 'ConfigFactory' );
+ }
+
+ /**
+ * Returns the Config object that provides configuration for MediaWiki
core.
+ * This may or may not be the same object that is returned by
getBootstrapConfig().
+ *
+ * @return Config
+ */
+ public function getMainConfig() {
+ return $this->getService( 'MainConfig' );
+ }
+
+ /**
+ * @return SiteLookup
+ */
+ public function getSiteLookup() {
+ return $this->getService( 'SiteLookup' );
+ }
+
+ /**
+ * @return SiteStore
+ */
+ public function getSiteStore() {
+ return $this->getService( 'SiteStore' );
+ }
+
+
///////////////////////////////////////////////////////////////////////////
+ // NOTE: When adding a service getter here, don't forget to add a test
+ // case for it in MediaWikiServicesTest::provideGetters() and in
+ // MediaWikiServicesTest::provideGetService()!
+
///////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php
new file mode 100644
index 0000000..d8709b9
--- /dev/null
+++ b/includes/ServiceWiring.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Default wiring for MediaWiki services.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * This file is loaded by MediaWiki\MediaWikiServices::getInstance() during the
+ * bootstrapping of the dependency injection framework.
+ *
+ * This file returns an array that associates service name with instantiator
functions
+ * that create the default instances for the services used by MediaWiki core.
+ * For every service that MediaWiki core requires, an instantiator must be
defined in
+ * this file.
+ *
+ * @note As of version 1.27, MediaWiki is only beginning to use dependency
injection.
+ * The services defined here do not yet fully represent all services used by
core,
+ * much of the code still relies on global state for this accessing services.
+ *
+ * @since 1.27
+ *
+ * @see docs/injection.txt for an overview of using dependency injection in the
+ * MediaWiki code base.
+ */
+
+use MediaWiki\MediaWikiServices;
+
+return [
+ 'SiteStore' => function( MediaWikiServices $services ) {
+ $loadBalancer = wfGetLB(); // TODO: use LB from
MediaWikiServices
+ $rawSiteStore = new DBSiteStore( $loadBalancer );
+
+ // TODO: replace wfGetCache with a CacheFactory service.
+ // TODO: replace wfIsHHVM with a capabilities service.
+ $cache = wfGetCache( wfIsHHVM() ? CACHE_ACCEL : CACHE_ANYTHING
);
+
+ return new CachingSiteStore( $rawSiteStore, $cache );
+ },
+
+ 'SiteLookup' => function( MediaWikiServices $services ) {
+ // Use the default SiteStore as the SiteLookup implementation
for now
+ return $services->getSiteStore();
+ },
+
+ 'ConfigFactory' => 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;
+ },
+
+ 'MainConfig' => function( MediaWikiServices $services ) {
+ // Use the 'main' config from the ConfigFactory service.
+ return $services->getConfigFactory()->makeConfig( 'main' );
+ },
+
+
///////////////////////////////////////////////////////////////////////////
+ // NOTE: When adding a service here, don't forget to add a getter
function
+ // in the MediaWikiServices class. The convenience getter should just
call
+ // $this->getService( 'FooBarService' ).
+
///////////////////////////////////////////////////////////////////////////
+
+];
diff --git a/includes/Services/ServiceContainer.php
b/includes/Services/ServiceContainer.php
new file mode 100644
index 0000000..e3cda2e
--- /dev/null
+++ b/includes/Services/ServiceContainer.php
@@ -0,0 +1,222 @@
+<?php
+namespace MediaWiki\Services;
+
+use InvalidArgumentException;
+use RuntimeException;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Generic service container.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.27
+ */
+
+/**
+ * ServiceContainer provides a generic service to manage named services using
+ * lazy instantiation based on instantiator callback functions.
+ *
+ * Services managed by an instance of ServiceContainer may or may not implement
+ * a common interface.
+ *
+ * @note When using ServiceContainer to manage a set of services, consider
+ * creating a wrapper or a subclass that provides access to the services via
+ * getter methods with more meaningful names and more specific return type
+ * declarations.
+ *
+ * @see docs/injection.txt for an overview of using dependency injection in the
+ * MediaWiki code base.
+ */
+class ServiceContainer {
+
+ /**
+ * @var object[]
+ */
+ private $services = [];
+
+ /**
+ * @var callable[]
+ */
+ private $serviceInstantiators = [];
+
+ /**
+ * @var array
+ */
+ private $extraInstantiationParams;
+
+ /**
+ * @param array $extraInstantiationParams Any additional parameters to
be passed to the
+ * instantiator function when creating a service. This is typically
used to provide
+ * access to additional ServiceContainers or Config objects.
+ */
+ public function __construct( array $extraInstantiationParams = [] ) {
+ $this->extraInstantiationParams = $extraInstantiationParams;
+ }
+
+ /**
+ * @param array $wiringFiles A list of PHP files to load wiring
information from.
+ * Each file is loaded using PHP's include mechanism. Each file is
expected to
+ * return an associative array that maps service names to instantiator
functions.
+ */
+ public function loadWiringFiles( array $wiringFiles ) {
+ foreach ( $wiringFiles as $file ) {
+ // the wiring file is required to return an array of
instantiators.
+ $wiring = require $file;
+
+ Assert::postcondition(
+ is_array( $wiring ),
+ "Wiring file $file is expected to return an
array!"
+ );
+
+ $this->applyWiring( $wiring );
+ }
+ }
+
+ /**
+ * Registers multiple services (aka a "wiring").
+ *
+ * @param array $serviceInstantiators An associative array mapping
service names to
+ * instantiator functions.
+ */
+ public function applyWiring( array $serviceInstantiators ) {
+ Assert::parameterElementType( 'callable',
$serviceInstantiators, '$serviceInstantiators' );
+
+ foreach ( $serviceInstantiators as $name => $instantiator ) {
+ $this->defineService( $name, $instantiator );
+ }
+ }
+
+ /**
+ * Returns true if a service is defined for $name, that is, if a call
to getService( $name )
+ * would return a service instance.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasService( $name ) {
+ return isset( $this->serviceInstantiators[$name] );
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getServiceNames() {
+ return array_keys( $this->serviceInstantiators );
+ }
+
+ /**
+ * Define a new service. The service must not be known already.
+ *
+ * @see getService().
+ * @see replaceService().
+ *
+ * @param string $name The name of the service to register, for use
with getService().
+ * @param callable $instantiator Callback that returns a service
instance.
+ * Will be called with this MediaWikiServices instance as the
only parameter.
+ * Any extra instantiation parameters provided to the
constructor will be
+ * passed as subsequent parameters when invoking the
instantiator.
+ *
+ * @throws RuntimeException if there is already a service registered as
$name.
+ */
+ public function defineService( $name, callable $instantiator ) {
+ Assert::parameterType( 'string', $name, '$name' );
+
+ if ( $this->hasService( $name ) ) {
+ throw new RuntimeException( 'Service already defined: '
. $name );
+ }
+
+ $this->serviceInstantiators[$name] = $instantiator;
+ }
+
+ /**
+ * Replace an already defined service.
+ *
+ * @see defineService().
+ *
+ * @note This causes any previously instantiated instance of the
service to be discarded.
+ *
+ * @param string $name The name of the service to register.
+ * @param callable $instantiator Callback function that returns a
service instance.
+ * Will be called with this MediaWikiServices instance as the
only parameter.
+ * The instantiator must return a service compatible with the
originally defined service.
+ * Any extra instantiation parameters provided to the
constructor will be
+ * passed as subsequent parameters when invoking the
instantiator.
+ *
+ * @throws RuntimeException if $name is not a known service.
+ */
+ public function redefineService( $name, callable $instantiator ) {
+ Assert::parameterType( 'string', $name, '$name' );
+
+ if ( !$this->hasService( $name ) ) {
+ throw new RuntimeException( 'Service not defined: ' .
$name );
+ }
+
+ if ( isset( $this->services[$name] ) ) {
+ throw new RuntimeException( 'Cannot redefine a service
that is already in use: ' . $name );
+ }
+
+ $this->serviceInstantiators[$name] = $instantiator;
+ }
+
+ /**
+ * Returns a service object of the kind associated with $name.
+ * Services instances are instantiated lazily, on demand.
+ * This method may or may not return the same service instance
+ * when called multiple times with the same $name.
+ *
+ * @note Rather than calling this method directly, it is recommended to
provide
+ * getters with more meaningful names and more specific return types,
using
+ * a subclass or wrapper.
+ *
+ * @see redefineService().
+ *
+ * @param string $name The service name
+ *
+ * @throws InvalidArgumentException if $name is not a known service.
+ * @return object The service instance
+ */
+ public function getService( $name ) {
+ if ( !isset( $this->services[$name] ) ) {
+ $this->services[$name] = $this->createService( $name );
+ }
+
+ return $this->services[$name];
+ }
+
+ /**
+ * @param string $name
+ *
+ * @throws InvalidArgumentException if $name is not a known service.
+ * @return object
+ */
+ private function createService( $name ) {
+ if ( isset( $this->serviceInstantiators[$name] ) ) {
+ $service = call_user_func_array(
+ $this->serviceInstantiators[$name],
+ array_merge( [ $this ],
$this->extraInstantiationParams )
+ );
+ } else {
+ throw new InvalidArgumentException( 'Unknown service: '
. $name );
+ }
+
+ return $service;
+ }
+
+}
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index 4534414..cf97984 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -73,13 +73,8 @@
* @return WikiReference|null WikiReference object or null if the wiki
was not found
*/
private static function getWikiWikiReferenceFromSites( $wikiID ) {
- static $siteStore = null;
- if ( !$siteStore ) {
- // Replace once T114471 got fixed and don't do the
caching here.
- $siteStore = SiteSQLStore::newInstance();
- }
-
- $site = $siteStore->getSite( $wikiID );
+ $siteLookup =
\MediaWiki\MediaWikiServices::getInstance()->getSiteLookup();
+ $site = $siteLookup->getSite( $wikiID );
if ( !$site instanceof MediaWikiSite ) {
// Abort if not a MediaWikiSite, as this is about Wikis
diff --git a/includes/config/ConfigFactory.php
b/includes/config/ConfigFactory.php
index d52103c..4b803d8 100644
--- a/includes/config/ConfigFactory.php
+++ b/includes/config/ConfigFactory.php
@@ -42,36 +42,12 @@
protected $configs = [];
/**
- * @var ConfigFactory
- */
- private static $self;
-
- /**
+ * @deprecated since 1.27, use MediaWikiServices::getConfigFactory()
instead.
+ *
* @return ConfigFactory
*/
public static function getDefaultInstance() {
- if ( !self::$self ) {
- self::$self = new self;
- global $wgConfigRegistry;
- foreach ( $wgConfigRegistry as $name => $callback ) {
- self::$self->register( $name, $callback );
- }
- }
- return self::$self;
- }
-
- /**
- * Destroy the default instance
- * Should only be called inside unit tests
- * @throws MWException
- * @codeCoverageIgnore
- */
- public static function destroyDefaultInstance() {
- if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
- throw new MWException( __METHOD__ . ' was called
outside of unit tests' );
- }
-
- self::$self = null;
+ return
\MediaWiki\MediaWikiServices::getInstance()->getConfigFactory();
}
/**
diff --git a/includes/site/DBSiteStore.php b/includes/site/DBSiteStore.php
index 756bb51..974789f 100644
--- a/includes/site/DBSiteStore.php
+++ b/includes/site/DBSiteStore.php
@@ -26,6 +26,7 @@
*
* @license GNU GPL v2+
* @author Jeroen De Dauw < [email protected] >
+ * @author Daniel Kinzler
*/
class DBSiteStore implements SiteStore {
@@ -35,15 +36,20 @@
protected $sites = null;
/**
- * @since 1.25
- * @param null $sitesTable Unused since 1.27
+ * @var LoadBalancer
*/
- public function __construct( $sitesTable = null ) {
- if ( $sitesTable !== null ) {
- throw new InvalidArgumentException(
- __METHOD__ . ': $sitesTable parameter must be
null'
- );
- }
+ private $dbLoadBalancer;
+
+ /**
+ * @since 1.27
+ *
+ * @todo: inject some kind of connection manager that is aware of the
target wiki,
+ * instead of injecting a LoadBalancer.
+ *
+ * @param LoadBalancer $dbLoadBalancer
+ */
+ public function __construct( LoadBalancer $dbLoadBalancer ) {
+ $this->dbLoadBalancer = $dbLoadBalancer;
}
/**
@@ -67,7 +73,7 @@
protected function loadSites() {
$this->sites = new SiteList();
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = $this->dbLoadBalancer->getConnection( DB_SLAVE );
$res = $dbr->select(
'sites',
@@ -124,6 +130,8 @@
$this->sites->setSite( $site );
}
}
+
+ $this->dbLoadBalancer->reuseConnection( $dbr );
}
/**
@@ -170,7 +178,7 @@
return true;
}
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->dbLoadBalancer->getConnection( DB_MASTER );
$dbw->startAtomic( __METHOD__ );
@@ -241,6 +249,8 @@
$dbw->endAtomic( __METHOD__ );
+ $this->dbLoadBalancer->reuseConnection( $dbw );
+
$this->reset();
return $success;
@@ -263,13 +273,15 @@
* @return bool Success
*/
public function clear() {
- $dbw = wfGetDB( DB_MASTER );
+ $dbw = $this->dbLoadBalancer->getConnection( DB_MASTER );
$dbw->startAtomic( __METHOD__ );
$ok = $dbw->delete( 'sites', '*', __METHOD__ );
$ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) &&
$ok;
$dbw->endAtomic( __METHOD__ );
+ $this->dbLoadBalancer->reuseConnection( $dbw );
+
$this->reset();
return $ok;
diff --git a/includes/site/SiteSQLStore.php b/includes/site/SiteSQLStore.php
index e61179b..a4116ae 100644
--- a/includes/site/SiteSQLStore.php
+++ b/includes/site/SiteSQLStore.php
@@ -1,9 +1,7 @@
<?php
/**
- * Represents the site configuration of a wiki.
- * Holds a list of sites (ie SiteList) and takes care
- * of retrieving and caching site information when appropriate.
+ * Dummy class for accessing the global SiteStore instance.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,16 +24,22 @@
* @ingroup Site
*
* @license GNU GPL v2+
- * @author Jeroen De Dauw < [email protected] >
+ * @author Daniel Kinzler
*/
-class SiteSQLStore extends CachingSiteStore {
+class SiteSQLStore {
/**
- * @since 1.21
- * @deprecated 1.25 Construct a SiteStore instance directly instead.
+ * Returns the global SiteStore instance. This is a relict of the first
implementation
+ * of SiteStore, and is kept around for compatibility.
*
- * @param null $sitesTable Unused
- * @param BagOStuff|null $cache
+ * @note This does not return an instance of SiteSQLStore!
+ *
+ * @since 1.21
+ * @deprecated 1.27 use MediaWikiServices::getSiteStore() or
MediaWikiServices::getSiteLookup()
+ * instead.
+ *
+ * @param null $sitesTable IGNORED
+ * @param null $cache IGNORED
*
* @return SiteStore
*/
@@ -46,13 +50,11 @@
);
}
- if ( $cache === null ) {
- $cache = wfGetCache( wfIsHHVM() ? CACHE_ACCEL :
CACHE_ANYTHING );
- }
+ // NOTE: we silently ignore $cache for now, since some existing
callers
+ // specify it. If we break compatibility with them, we could
just as
+ // well just remove this class.
- $siteStore = new DBSiteStore();
-
- return new static( $siteStore, $cache );
+ return
\MediaWiki\MediaWikiServices::getInstance()->getSiteStore();
}
}
diff --git a/maintenance/exportSites.php b/maintenance/exportSites.php
index 157a323..b1e4fa9 100644
--- a/maintenance/exportSites.php
+++ b/maintenance/exportSites.php
@@ -42,8 +42,8 @@
$exporter = new SiteExporter( $handle );
- $sites = SiteSQLStore::newInstance()->getSites( 'recache' );
- $exporter->exportSites( $sites );
+ $siteLookup =
\MediaWiki\MediaWikiServices::getInstance()->getSiteLookup();
+ $exporter->exportSites( $siteLookup->getSites() );
fclose( $handle );
diff --git a/maintenance/importSites.php b/maintenance/importSites.php
index daec7b6..5722344 100644
--- a/maintenance/importSites.php
+++ b/maintenance/importSites.php
@@ -30,7 +30,8 @@
public function execute() {
$file = $this->getArg( 0 );
- $importer = new SiteImporter( SiteSQLStore::newInstance() );
+ $siteStore =
\MediaWiki\MediaWikiServices::getInstance()->getSiteStore();
+ $importer = new SiteImporter( $siteStore );
$importer->setExceptionCallback( [ $this, 'reportException' ] );
$importer->importFromFile( $file );
diff --git a/maintenance/rebuildSitesCache.php
b/maintenance/rebuildSitesCache.php
index 044bafd..230e86d 100644
--- a/maintenance/rebuildSitesCache.php
+++ b/maintenance/rebuildSitesCache.php
@@ -38,7 +38,7 @@
public function execute() {
$sitesCacheFileBuilder = new SitesCacheFileBuilder(
- new DBSiteStore(),
+
\MediaWiki\MediaWikiServices::getInstance()->getSiteLookup(),
$this->getCacheFile()
);
diff --git a/tests/phpunit/includes/MediaWikiServicesTest.php
b/tests/phpunit/includes/MediaWikiServicesTest.php
new file mode 100644
index 0000000..127f869
--- /dev/null
+++ b/tests/phpunit/includes/MediaWikiServicesTest.php
@@ -0,0 +1,77 @@
+<?php
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @covers MediaWiki\MediaWikiServices
+ *
+ * @group MediaWiki
+ */
+class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
+
+ public function testGetInstance() {
+ $services = MediaWikiServices::getInstance();
+ $this->assertInstanceOf( 'MediaWiki\\MediaWikiServices',
$services );
+ }
+
+ public function provideGetters() {
+ // NOTE: This should list all service getters defined in
MediaWikiServices.
+ // NOTE: For every test case defined here there should be a
corresponding
+ // test case defined in provideGetService().
+ return [
+ 'BootstrapConfig' => [ 'getBootstrapConfig',
Config::class ],
+ 'ConfigFactory' => [ 'getConfigFactory',
ConfigFactory::class ],
+ 'MainConfig' => [ 'getMainConfig', Config::class ],
+ 'SiteStore' => [ 'getSiteStore', SiteStore::class ],
+ 'SiteLookup' => [ 'getSiteLookup', SiteLookup::class ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetters
+ */
+ public function testGetters( $getter, $type ) {
+ // Test against the default instance, since the dummy will not
know the default services.
+ $services = MediaWikiServices::getInstance();
+ $service = $services->$getter();
+ $this->assertInstanceOf( $type, $service );
+ }
+
+ public function provideGetService() {
+ // NOTE: This should list all service getters defined in
ServiceWiring.php.
+ // NOTE: For every test case defined here there should be a
corresponding
+ // test case defined in provideGetters().
+ return [
+ 'BootstrapConfig' => [ 'BootstrapConfig', Config::class
],
+ 'ConfigFactory' => [ 'ConfigFactory',
ConfigFactory::class ],
+ 'MainConfig' => [ 'MainConfig', Config::class ],
+ 'SiteStore' => [ 'SiteStore', SiteStore::class ],
+ 'SiteLookup' => [ 'SiteLookup', SiteLookup::class ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetService
+ */
+ public function testGetService( $name, $type ) {
+ // Test against the default instance, since the dummy will not
know the default services.
+ $services = MediaWikiServices::getInstance();
+
+ $service = $services->getService( $name );
+ $this->assertInstanceOf( $type, $service );
+ }
+
+ public function testDefaultServiceInstantiation() {
+ // Check all services in the default instance, not a dummy
instance!
+ // Note that we instantiate all services here, including any
that
+ // were registered by extensions.
+ $services = MediaWikiServices::getInstance();
+ $names = $services->getServiceNames();
+
+ foreach ( $names as $name ) {
+ $this->assertTrue( $services->hasService( $name ) );
+ $service = $services->getService( $name );
+ $this->assertInternalType( 'object', $service );
+ }
+ }
+
+}
diff --git a/tests/phpunit/includes/Services/ServiceContainerTest.php
b/tests/phpunit/includes/Services/ServiceContainerTest.php
new file mode 100644
index 0000000..942c45e
--- /dev/null
+++ b/tests/phpunit/includes/Services/ServiceContainerTest.php
@@ -0,0 +1,214 @@
+<?php
+use MediaWiki\Services\ServiceContainer;
+
+/**
+ * @covers MediaWiki\Services\ServiceContainer
+ *
+ * @group MediaWiki
+ */
+class ServiceContainerTest extends PHPUnit_Framework_TestCase {
+
+ private function newServiceContainer( $extraArgs = [] ) {
+ return new ServiceContainer( $extraArgs );
+ }
+
+ public function testGetServiceNames() {
+ $services = $this->newServiceContainer();
+ $names = $services->getServiceNames();
+
+ $this->assertInternalType( 'array', $names );
+ $this->assertEmpty( $names );
+
+ $name = 'TestService92834576';
+ $services->defineService( $name, function() {
+ return null;
+ } );
+
+ $names = $services->getServiceNames();
+ $this->assertContains( $name, $names );
+ }
+
+ public function testHasService() {
+ $services = $this->newServiceContainer();
+
+ $name = 'TestService92834576';
+ $this->assertFalse( $services->hasService( $name ) );
+
+ $services->defineService( $name, function() {
+ return null;
+ } );
+
+ $this->assertTrue( $services->hasService( $name ) );
+ }
+
+ public function testGetService() {
+ $services = $this->newServiceContainer( [ 'Foo' ] );
+
+ $theService = new stdClass();
+ $name = 'TestService92834576';
+ $count = 0;
+
+ $services->defineService(
+ $name,
+ function( $actualLocator, $extra ) use ( $services,
$theService, &$count ) {
+ $count++;
+ PHPUnit_Framework_Assert::assertSame(
$services, $actualLocator );
+ PHPUnit_Framework_Assert::assertSame( $extra,
'Foo' );
+ return $theService;
+ }
+ );
+
+ $this->assertSame( $theService, $services->getService( $name )
);
+
+ $services->getService( $name );
+ $this->assertSame( 1, $count, 'instantiator should be called
exactly once!' );
+ }
+
+ public function testGetService_fail_unknown() {
+ $services = $this->newServiceContainer();
+
+ $name = 'TestService92834576';
+
+ $this->setExpectedException( 'InvalidArgumentException' );
+
+ $services->getService( $name );
+ }
+
+ public function testDefineService() {
+ $services = $this->newServiceContainer();
+
+ $theService = new stdClass();
+ $name = 'TestService92834576';
+
+ $services->defineService( $name, function( $actualLocator ) use
( $services, $theService ) {
+ PHPUnit_Framework_Assert::assertSame( $services,
$actualLocator );
+ return $theService;
+ } );
+
+ $this->assertTrue( $services->hasService( $name ) );
+ $this->assertSame( $theService, $services->getService( $name )
);
+ }
+
+ public function testDefineService_fail_duplicate() {
+ $services = $this->newServiceContainer();
+
+ $theService = new stdClass();
+ $name = 'TestService92834576';
+
+ $services->defineService( $name, function() use ( $theService )
{
+ return $theService;
+ } );
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ $services->defineService( $name, function() use ( $theService )
{
+ return $theService;
+ } );
+ }
+
+ public function testApplyWiring() {
+ $services = $this->newServiceContainer();
+
+ $wiring = [
+ 'Foo' => function() {
+ return 'Foo!';
+ },
+ 'Bar' => function() {
+ return 'Bar!';
+ },
+ ];
+
+ $services->applyWiring( $wiring );
+
+ $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
+ $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
+ }
+
+ public function testLoadWiringFiles() {
+ $services = $this->newServiceContainer();
+
+ $wiringFiles = [
+ __DIR__ . '/TestWiring1.php',
+ __DIR__ . '/TestWiring2.php',
+ ];
+
+ $services->loadWiringFiles( $wiringFiles );
+
+ $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
+ $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
+ }
+
+ public function testLoadWiringFiles_fail_duplicate() {
+ $services = $this->newServiceContainer();
+
+ $wiringFiles = [
+ __DIR__ . '/TestWiring1.php',
+ __DIR__ . '/./TestWiring1.php',
+ ];
+
+ // loading the same file twice should fail, because
+ $this->setExpectedException( 'RuntimeException' );
+
+ $services->loadWiringFiles( $wiringFiles );
+ }
+
+ public function testRedefineService() {
+ $services = $this->newServiceContainer( [ 'Foo' ] );
+
+ $theService1 = new stdClass();
+ $name = 'TestService92834576';
+
+ $services->defineService( $name, function() {
+ PHPUnit_Framework_Assert::fail(
+ 'The original instantiator function should not
get called'
+ );
+ } );
+
+ // redefine before instantiation
+ $services->redefineService(
+ $name,
+ function( $actualLocator, $extra ) use ( $services,
$theService1 ) {
+ PHPUnit_Framework_Assert::assertSame(
$services, $actualLocator );
+ PHPUnit_Framework_Assert::assertSame( 'Foo',
$extra );
+ return $theService1;
+ }
+ );
+
+ // force instantiation, check result
+ $this->assertSame( $theService1, $services->getService( $name )
);
+ }
+
+ public function testRedefineService_fail_undefined() {
+ $services = $this->newServiceContainer();
+
+ $theService = new stdClass();
+ $name = 'TestService92834576';
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ $services->redefineService( $name, function() use ( $theService
) {
+ return $theService;
+ } );
+ }
+
+ public function testRedefineService_fail_in_use() {
+ $services = $this->newServiceContainer( [ 'Foo' ] );
+
+ $theService = new stdClass();
+ $name = 'TestService92834576';
+
+ $services->defineService( $name, function() {
+ return 'Foo';
+ } );
+
+ // create the service, so it can no longer be redefined
+ $services->getService( $name );
+
+ $this->setExpectedException( 'RuntimeException' );
+
+ $services->redefineService( $name, function() use ( $theService
) {
+ return $theService;
+ } );
+ }
+
+}
diff --git a/tests/phpunit/includes/Services/TestWiring1.php
b/tests/phpunit/includes/Services/TestWiring1.php
new file mode 100644
index 0000000..186021a
--- /dev/null
+++ b/tests/phpunit/includes/Services/TestWiring1.php
@@ -0,0 +1,10 @@
+<?php
+/**
+ * Test file for testing ServiceContainer::loadWiringFiles
+ */
+
+return [
+ 'Foo' => function() {
+ return 'Foo!';
+ },
+];
diff --git a/tests/phpunit/includes/Services/TestWiring2.php
b/tests/phpunit/includes/Services/TestWiring2.php
new file mode 100644
index 0000000..3b4fff0
--- /dev/null
+++ b/tests/phpunit/includes/Services/TestWiring2.php
@@ -0,0 +1,10 @@
+<?php
+/**
+ * Test file for testing ServiceContainer::loadWiringFiles
+ */
+
+return [
+ 'Bar' => function() {
+ return 'Bar!';
+ },
+];
diff --git a/tests/phpunit/includes/config/ConfigFactoryTest.php
b/tests/phpunit/includes/config/ConfigFactoryTest.php
index c4829d2..2288507 100644
--- a/tests/phpunit/includes/config/ConfigFactoryTest.php
+++ b/tests/phpunit/includes/config/ConfigFactoryTest.php
@@ -2,12 +2,6 @@
class ConfigFactoryTest extends MediaWikiTestCase {
- public function tearDown() {
- // Reset this since we mess with it a bit
- ConfigFactory::destroyDefaultInstance();
- parent::tearDown();
- }
-
/**
* @covers ConfigFactory::register
*/
@@ -54,17 +48,10 @@
* @covers ConfigFactory::getDefaultInstance
*/
public function testGetDefaultInstance() {
- // Set $wgConfigRegistry, and check the default
- // instance read from it
- $this->setMwGlobals( 'wgConfigRegistry', [
- 'conf1' => 'GlobalVarConfig::newInstance',
- 'conf2' => 'GlobalVarConfig::newInstance',
- ] );
- ConfigFactory::destroyDefaultInstance();
$factory = ConfigFactory::getDefaultInstance();
- $this->assertInstanceOf( 'Config', $factory->makeConfig(
'conf1' ) );
- $this->assertInstanceOf( 'Config', $factory->makeConfig(
'conf2' ) );
+ $this->assertInstanceOf( 'Config', $factory->makeConfig( 'main'
) );
+
$this->setExpectedException( 'ConfigException' );
- $factory->makeConfig( 'conf3' );
+ $factory->makeConfig( 'xyzzy' );
}
}
diff --git a/tests/phpunit/includes/site/DBSiteStoreTest.php
b/tests/phpunit/includes/site/DBSiteStoreTest.php
index 4f4275d..32dd7f2 100644
--- a/tests/phpunit/includes/site/DBSiteStoreTest.php
+++ b/tests/phpunit/includes/site/DBSiteStoreTest.php
@@ -32,13 +32,22 @@
class DBSiteStoreTest extends MediaWikiTestCase {
/**
+ * @return DBSiteStore
+ */
+ private function newDBSiteStore() {
+ // NOTE: Use the real DB load balancer for now. Eventually, the
test framework should
+ // provide a LoadBalancer that is safe to use in unit tests.
+ return new DBSiteStore( wfGetLB() );
+ }
+
+ /**
* @covers DBSiteStore::getSites
*/
public function testGetSites() {
$expectedSites = TestSites::getSites();
TestSites::insertIntoDb();
- $store = new DBSiteStore();
+ $store = $this->newDBSiteStore();
$sites = $store->getSites();
@@ -62,7 +71,7 @@
* @covers DBSiteStore::saveSites
*/
public function testSaveSites() {
- $store = new DBSiteStore();
+ $store = $this->newDBSiteStore();
$sites = [];
@@ -95,8 +104,8 @@
* @covers DBSiteStore::reset
*/
public function testReset() {
- $store1 = new DBSiteStore();
- $store2 = new DBSiteStore();
+ $store1 = $this->newDBSiteStore();
+ $store2 = $this->newDBSiteStore();
// initialize internal cache
$this->assertGreaterThan( 0, $store1->getSites()->count() );
@@ -121,7 +130,7 @@
* @covers DBSiteStore::clear
*/
public function testClear() {
- $store = new DBSiteStore();
+ $store = $this->newDBSiteStore();
$this->assertTrue( $store->clear() );
$site = $store->getSite( 'enwiki' );
@@ -135,7 +144,7 @@
* @covers DBSiteStore::getSites
*/
public function testGetSitesDefaultOrder() {
- $store = new DBSiteStore();
+ $store = $this->newDBSiteStore();
$siteB = new Site();
$siteB->setGlobalId( 'B' );
$siteA = new Site();
diff --git a/tests/phpunit/includes/site/SiteSQLStoreTest.php
b/tests/phpunit/includes/site/SiteSQLStoreTest.php
deleted file mode 100644
index 6908800..0000000
--- a/tests/phpunit/includes/site/SiteSQLStoreTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @since 1.25
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- * @group Database
- *
- * @author Katie Filbert < [email protected] >
- */
-class SiteSQLStoreTest extends MediaWikiTestCase {
-
- /**
- * @covers SiteSQLStore::newInstance
- */
- public function testNewInstance() {
- $siteStore = SiteSQLStore::newInstance();
- $this->assertInstanceOf( 'SiteSQLStore', $siteStore );
- }
-
-}
diff --git a/tests/phpunit/includes/site/TestSites.php
b/tests/phpunit/includes/site/TestSites.php
index d7865d4..6597906 100644
--- a/tests/phpunit/includes/site/TestSites.php
+++ b/tests/phpunit/includes/site/TestSites.php
@@ -107,7 +107,7 @@
* @since 0.1
*/
public static function insertIntoDb() {
- $sitesTable = new DBSiteStore();
+ $sitesTable =
\MediaWiki\MediaWikiServices::getInstance()->getSiteStore();
$sitesTable->clear();
$sitesTable->saveSites( TestSites::getSites() );
}
--
To view, visit https://gerrit.wikimedia.org/r/264403
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I3c25c0ac17300d3dd13e1cf5100558a605eee15f
Gerrit-PatchSet: 36
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Adrian Heine <[email protected]>
Gerrit-Reviewer: Alex Monk <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Aude <[email protected]>
Gerrit-Reviewer: Bene <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: BryanDavis <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Chad <[email protected]>
Gerrit-Reviewer: DCausse <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: GWicke <[email protected]>
Gerrit-Reviewer: Gergő Tisza <[email protected]>
Gerrit-Reviewer: JanZerebecki <[email protected]>
Gerrit-Reviewer: Jeroen De Dauw <[email protected]>
Gerrit-Reviewer: MaxSem <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: Parent5446 <[email protected]>
Gerrit-Reviewer: Smalyshev <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits