Merge "AllMessagesTablePager: Simplify language handling"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 16 Jan 2019 20:19:01 +0000 (20:19 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 16 Jan 2019 20:19:01 +0000 (20:19 +0000)
102 files changed:
RELEASE-NOTES-1.33
autoload.php
includes/AutoLoader.php
includes/Block.php
includes/HistoryBlob.php
includes/MediaWikiServices.php
includes/Setup.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryPageProps.php
includes/api/ApiQueryUserContribs.php
includes/clientpool/SquidPurgeClientPool.php
includes/config/ConfigFactory.php
includes/config/ConfigRepository.php
includes/content/AbstractContent.php
includes/content/Content.php
includes/content/ContentHandler.php
includes/content/CssContent.php
includes/content/JavaScriptContent.php
includes/content/JsonContent.php
includes/content/MessageContent.php
includes/content/TextContent.php
includes/content/TextContentHandler.php
includes/content/WikitextContent.php
includes/content/WikitextContentHandler.php
includes/db/DatabaseOracle.php
includes/editpage/TextboxBuilder.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/ArchivedFile.php
includes/installer/Installer.php
includes/jobqueue/JobQueue.php
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobQueueRedis.php
includes/libs/filebackend/FileBackend.php
includes/libs/filebackend/filejournal/FileJournal.php
includes/libs/lockmanager/FSLockManager.php
includes/libs/lockmanager/PostgreSqlLockManager.php
includes/libs/lockmanager/QuorumLockManager.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/services/CannotReplaceActiveServiceException.php [new file with mode: 0644]
includes/libs/services/ContainerDisabledException.php [new file with mode: 0644]
includes/libs/services/DestructibleService.php [new file with mode: 0644]
includes/libs/services/NoSuchServiceException.php [new file with mode: 0644]
includes/libs/services/SalvageableService.php [new file with mode: 0644]
includes/libs/services/ServiceAlreadyDefinedException.php [new file with mode: 0644]
includes/libs/services/ServiceContainer.php [new file with mode: 0644]
includes/libs/services/ServiceDisabledException.php [new file with mode: 0644]
includes/mail/EmailNotification.php
includes/parser/Preprocessor_DOM.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderImage.php
includes/search/SearchOracle.php
includes/search/SearchSqlite.php
includes/services/CannotReplaceActiveServiceException.php [deleted file]
includes/services/ContainerDisabledException.php [deleted file]
includes/services/DestructibleService.php [deleted file]
includes/services/NoSuchServiceException.php [deleted file]
includes/services/SalvageableService.php [deleted file]
includes/services/ServiceAlreadyDefinedException.php [deleted file]
includes/services/ServiceContainer.php [deleted file]
includes/services/ServiceDisabledException.php [deleted file]
includes/skins/Skin.php
includes/specials/SpecialEditWatchlist.php
includes/specials/SpecialListgrants.php
includes/specials/SpecialPasswordPolicies.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialSearch.php
includes/specials/SpecialSpecialpages.php
includes/specials/SpecialTrackingCategories.php
includes/specials/pagers/BlockListPager.php
includes/user/User.php
languages/data/ZhConversion.php
languages/messages/MessagesGom_deva.php
maintenance/language/zhtable/toCN.manual
maintenance/language/zhtable/toHK.manual
maintenance/language/zhtable/toSimp.manual
maintenance/language/zhtable/toTW.manual
maintenance/language/zhtable/toTrad.manual
maintenance/language/zhtable/trad2simp.manual
maintenance/language/zhtable/tradphrases.manual
resources/src/mediawiki.special.block.js
tests/phpunit/data/resourceloader/def.svg
tests/phpunit/data/resourceloader/def_variantize.svg
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/content/JavaScriptContentTest.php
tests/phpunit/includes/content/MessageContentTest.php [new file with mode: 0644]
tests/phpunit/includes/content/TextContentTest.php
tests/phpunit/includes/content/WikitextContentHandlerTest.php
tests/phpunit/includes/content/WikitextContentTest.php
tests/phpunit/includes/libs/services/ServiceContainerTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/services/TestWiring1.php [new file with mode: 0644]
tests/phpunit/includes/libs/services/TestWiring2.php [new file with mode: 0644]
tests/phpunit/includes/services/ServiceContainerTest.php [deleted file]
tests/phpunit/includes/services/TestWiring1.php [deleted file]
tests/phpunit/includes/services/TestWiring2.php [deleted file]

index c0dd84f..c9bd093 100644 (file)
@@ -39,6 +39,8 @@ production.
 * Add PasswordPolicy to check the password isn't in the large blacklist.
 * The AuthManagerLoginAuthenticateAudit hook has a new parameter for
   additional information about the authentication event.
+* TextContent::getText() was introduced as a replacement for
+  Content::getNativeData() for text-based content models.
 * …
 
 === External library changes in 1.33 ===
@@ -101,6 +103,8 @@ Below only new and removed languages are listed, as well as changes to languages
 because of Phabricator reports.
 
 * (T203908) Added language support for Eastern Pwo (kjp).
+* (T213717) Fixed a translation error on Goan Konkani (gom-deva) translations
+  for NS_TEMPLATE.
 
 === Breaking changes in 1.33 ===
 * The parameteter $lang in DifferenceEngine::setTextLanguage must be of type
@@ -201,6 +205,10 @@ because of Phabricator reports.
   Title::getUserPermissionsErrors() and Title::userCan(). Previously, the method
   was only called in Action::checkCanExecute(). Actions should ensure that their
   requiresUnblock() returns the proper result (the default is `true`).
+* (T211608) The MediaWiki\Services namespace has been renamed to
+  Wikimedia\Services. The old name is still supported, but deprecated.
+* (T155582) Content::getNativeData has been deprecated. Please use model-
+  specific getters, such as TextContent::getText().
 * …
 
 === Other changes in 1.33 ===
index 91be2e7..afc187f 100644 (file)
@@ -903,6 +903,14 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\OutputHandler' => __DIR__ . '/includes/OutputHandler.php',
        'MediaWiki\\ProcOpenError' => __DIR__ . '/includes/exception/ProcOpenError.php',
        'MediaWiki\\Search\\ParserOutputSearchDataExtractor' => __DIR__ . '/includes/search/ParserOutputSearchDataExtractor.php',
+       'MediaWiki\\Services\\CannotReplaceActiveServiceException' => __DIR__ . '/includes/libs/services/CannotReplaceActiveServiceException.php',
+       'MediaWiki\\Services\\ContainerDisabledException' => __DIR__ . '/includes/libs/services/ContainerDisabledException.php',
+       'MediaWiki\\Services\\DestructibleService' => __DIR__ . '/includes/libs/services/DestructibleService.php',
+       'MediaWiki\\Services\\NoSuchServiceException' => __DIR__ . '/includes/libs/services/NoSuchServiceException.php',
+       'MediaWiki\\Services\\SalvageableService' => __DIR__ . '/includes/libs/services/SalvageableService.php',
+       'MediaWiki\\Services\\ServiceAlreadyDefinedException' => __DIR__ . '/includes/libs/services/ServiceAlreadyDefinedException.php',
+       'MediaWiki\\Services\\ServiceContainer' => __DIR__ . '/includes/libs/services/ServiceContainer.php',
+       'MediaWiki\\Services\\ServiceDisabledException' => __DIR__ . '/includes/libs/services/ServiceDisabledException.php',
        'MediaWiki\\ShellDisabledError' => __DIR__ . '/includes/exception/ShellDisabledError.php',
        'MediaWiki\\Site\\MediaWikiPageNameNormalizer' => __DIR__ . '/includes/site/MediaWikiPageNameNormalizer.php',
        'MediaWiki\\Special\\SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php',
index 677fd01..9dbc9eb 100644 (file)
@@ -136,12 +136,12 @@ class AutoLoader {
                        'MediaWiki\\Linker\\' => __DIR__ . '/linker/',
                        'MediaWiki\\Preferences\\' => __DIR__ . '/preferences/',
                        'MediaWiki\\Revision\\' => __DIR__ . '/Revision/',
-                       'MediaWiki\\Services\\' => __DIR__ . '/services/',
                        'MediaWiki\\Session\\' => __DIR__ . '/session/',
                        'MediaWiki\\Shell\\' => __DIR__ . '/shell/',
                        'MediaWiki\\Sparql\\' => __DIR__ . '/sparql/',
                        'MediaWiki\\Storage\\' => __DIR__ . '/Storage/',
                        'MediaWiki\\Tidy\\' => __DIR__ . '/tidy/',
+                       'Wikimedia\\Services\\' => __DIR__ . '/libs/services/',
                ];
        }
 }
index fb3caf6..7138301 100644 (file)
@@ -1307,7 +1307,7 @@ class Block {
         * @since 1.22
         */
        public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
-               if ( !count( $ipChain ) ) {
+               if ( $ipChain === [] ) {
                        return [];
                }
 
@@ -1332,7 +1332,7 @@ class Block {
                        $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) );
                }
 
-               if ( !count( $conds ) ) {
+               if ( $conds === [] ) {
                        return [];
                }
 
@@ -1388,7 +1388,7 @@ class Block {
         * @return Block|null The "best" block from the list
         */
        public static function chooseBlock( array $blocks, array $ipChain ) {
-               if ( !count( $blocks ) ) {
+               if ( $blocks === [] ) {
                        return null;
                } elseif ( count( $blocks ) == 1 ) {
                        return $blocks[0];
index 1d4f6e4..bca6c7e 100644 (file)
@@ -445,8 +445,7 @@ class DiffHistoryBlob implements HistoryBlob {
                        // Already compressed
                        return;
                }
-               if ( !count( $this->mItems ) ) {
-                       // Empty
+               if ( $this->mItems === [] ) {
                        return;
                }
 
@@ -492,7 +491,7 @@ class DiffHistoryBlob implements HistoryBlob {
                $this->mDiffs = [];
                $this->mDiffMap = [];
                foreach ( $sequences as $seq ) {
-                       if ( !count( $seq['diffs'] ) ) {
+                       if ( $seq['diffs'] === [] ) {
                                continue;
                        }
                        if ( $tail === '' ) {
@@ -627,8 +626,7 @@ class DiffHistoryBlob implements HistoryBlob {
         */
        function __sleep() {
                $this->compress();
-               if ( !count( $this->mItems ) ) {
-                       // Empty object
+               if ( $this->mItems === [] ) {
                        $info = false;
                } else {
                        // Take forward differences to improve the compression ratio for sequences
index 0e36b22..4abd729 100644 (file)
@@ -36,9 +36,6 @@ use MediaHandlerFactory;
 use MediaWiki\Config\ConfigRepository;
 use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkRendererFactory;
-use MediaWiki\Services\SalvageableService;
-use MediaWiki\Services\ServiceContainer;
-use MediaWiki\Services\NoSuchServiceException;
 use MWException;
 use MimeAnalyzer;
 use ObjectCache;
@@ -58,6 +55,9 @@ use SkinFactory;
 use TitleFormatter;
 use TitleParser;
 use VirtualRESTServiceClient;
+use Wikimedia\Services\SalvageableService;
+use Wikimedia\Services\ServiceContainer;
+use Wikimedia\Services\NoSuchServiceException;
 use MediaWiki\Interwiki\InterwikiLookup;
 use MagicWordFactory;
 
index aba050d..4ebe426 100644 (file)
@@ -367,10 +367,9 @@ if ( $wgRCFilterByAge ) {
        // Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
        sort( $wgRCLinkDays );
 
-       // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall
-       for ( $i = 0; $i < count( $wgRCLinkDays ); $i++ ) {
-               if ( $wgRCLinkDays[$i] >= $rcMaxAgeDays ) {
-                       $wgRCLinkDays = array_slice( $wgRCLinkDays, 0, $i + 1, false );
+       foreach ( $wgRCLinkDays as $i => $days ) {
+               if ( $days >= $rcMaxAgeDays ) {
+                       array_splice( $wgRCLinkDays, $i + 1 );
                        break;
                }
        }
@@ -873,11 +872,19 @@ if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
 
        $session->renew();
        if ( MediaWiki\Session\PHPSessionHandler::isEnabled() &&
-               ( $session->isPersistent() || $session->shouldRememberUser() )
+               ( $session->isPersistent() || $session->shouldRememberUser() ) &&
+               session_id() !== $session->getId()
        ) {
                // Start the PHP-session for backwards compatibility
+               if ( session_id() !== '' ) {
+                       wfDebugLog( 'session', 'PHP session {old_id} was already started, changing to {new_id}', 'all', [
+                               'old_id' => session_id(),
+                               'new_id' => $session->getId(),
+                       ] );
+                       session_write_close();
+               }
                session_id( $session->getId() );
-               Wikimedia\quietCall( 'session_start' );
+               session_start();
        }
 
        unset( $session );
index 7d5f6e2..7b5df50 100644 (file)
@@ -94,7 +94,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
                        }
 
                        // no group with the given right(s) exists, no need for a query
-                       if ( !count( $groups ) ) {
+                       if ( $groups === [] ) {
                                $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], '' );
 
                                return;
index d9fe50b..c92f037 100644 (file)
@@ -277,7 +277,7 @@ abstract class ApiQueryBase extends ApiBase {
                if ( count( $ids ) ) {
                        $ids = $this->filterIDs( [ [ $table, $field ] ], $ids );
 
-                       if ( !count( $ids ) ) {
+                       if ( $ids === [] ) {
                                // Return nothing, no IDs are valid
                                $this->where[] = '0 = 1';
                        } else {
index 642c9ac..a8f970e 100644 (file)
@@ -64,7 +64,7 @@ class ApiQueryContributors extends ApiQueryBase {
                                return $v >= $cont_page;
                        } );
                }
-               if ( !count( $pages ) ) {
+               if ( $pages === [] ) {
                        // Nothing to do
                        return;
                }
index 2ab3c56..8a54c0b 100644 (file)
@@ -721,7 +721,7 @@ class ApiQueryInfo extends ApiQueryBase {
                                $getTitles[] = $t->getTalkPage();
                        }
                }
-               if ( !count( $getTitles ) ) {
+               if ( $getTitles === [] ) {
                        return;
                }
 
@@ -751,7 +751,7 @@ class ApiQueryInfo extends ApiQueryBase {
 
                $pageIds = array_keys( $this->titles );
 
-               if ( !count( $pageIds ) ) {
+               if ( $pageIds === [] ) {
                        return;
                }
 
@@ -768,7 +768,7 @@ class ApiQueryInfo extends ApiQueryBase {
        }
 
        private function getVariantTitles() {
-               if ( !count( $this->titles ) ) {
+               if ( $this->titles === [] ) {
                        return;
                }
                $this->variantTitles = [];
index 2bee698..3258004 100644 (file)
@@ -50,7 +50,7 @@ class ApiQueryPageProps extends ApiQueryBase {
                        $pages = $filteredPages;
                }
 
-               if ( !count( $pages ) ) {
+               if ( $pages === [] ) {
                        # Nothing to do
                        return;
                }
index ed83130..6082617 100644 (file)
@@ -136,7 +136,7 @@ class ApiQueryUserContribs extends ApiQueryBase {
                        // prepareQuery might try to sort by actor and confuse everything.
                        $batchSize = 1;
                } elseif ( isset( $this->params['userids'] ) ) {
-                       if ( !count( $this->params['userids'] ) ) {
+                       if ( $this->params['userids'] === [] ) {
                                $encParamName = $this->encodeParamName( 'userids' );
                                $this->dieWithError( [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName" );
                        }
index 5acac63..3a2a971 100644 (file)
@@ -59,7 +59,7 @@ class SquidPurgeClientPool {
                                        $writeSockets["$clientIndex/$i"] = $socket;
                                }
                        }
-                       if ( !count( $readSockets ) && !count( $writeSockets ) ) {
+                       if ( $readSockets === [] && $writeSockets === [] ) {
                                break;
                        }
 
index 2c7afda..769364f 100644 (file)
@@ -20,8 +20,8 @@
  *
  * @file
  */
-use MediaWiki\Services\SalvageableService;
 use Wikimedia\Assert\Assert;
+use Wikimedia\Services\SalvageableService;
 
 /**
  * Factory class to create Config objects
index c87a344..96dc51c 100644 (file)
@@ -22,8 +22,8 @@
 
 namespace MediaWiki\Config;
 
-use MediaWiki\Services\SalvageableService;
 use Wikimedia\Assert\Assert;
+use Wikimedia\Services\SalvageableService;
 
 /**
  * Object which holds currently registered configuration options.
index 733d85a..c82b473 100644 (file)
@@ -180,6 +180,17 @@ abstract class AbstractContent implements Content {
        }
 
        /**
+        * Decides whether two Content objects are equal.
+        * Two Content objects MUST not be considered equal if they do not share the same content model.
+        * Two Content objects that are equal SHOULD have the same serialization.
+        *
+        * This default implementation relies on equalsInternal() to determin whether the
+        * Content objects are logically equivalent. Subclasses that need to implement a custom
+        * equality check should consider overriding equalsInternal(). Subclasses that override
+        * equals() itself MUST make sure that the implementation returns false for $that === null,
+        * and true for $that === this. It MUST also return false if $that does not have the same
+        * content model.
+        *
         * @since 1.21
         *
         * @param Content|null $that
@@ -201,7 +212,34 @@ abstract class AbstractContent implements Content {
                        return false;
                }
 
-               return $this->getNativeData() === $that->getNativeData();
+               // For type safety. Needed for odd cases like MessageContent using CONTENT_MODEL_WIKITEXT
+               if ( get_class( $that ) !== get_class( $this ) ) {
+                       return false;
+               }
+
+               return $this->equalsInternal( $that );
+       }
+
+       /**
+        * Checks whether $that is logically equal to this Content object.
+        *
+        * This method can be overwritten by subclasses that need to implement custom
+        * equality checks.
+        *
+        * This default implementation checks whether the serializations
+        * of $this and $that are the same: $this->serialize() === $that->serialize()
+        *
+        * Implementors can assume that $that is an instance of the same class
+        * as the present Content object, as long as equalsInternal() is only called
+        * by the standard implementation of equals().
+        *
+        * @note Do not call this method directly, call equals() instead.
+        *
+        * @param Content $that
+        * @return bool
+        */
+       protected function equalsInternal( Content $that ) {
+               return $this->serialize() === $that->serialize();
        }
 
        /**
index 1bb43f8..2637aa6 100644 (file)
@@ -77,6 +77,9 @@ interface Content {
         *
         * @since 1.21
         *
+        * @deprecated since 1.33 use getText() for TextContent instances.
+        *             For other content models, use specialized getters.
+        *
         * @return mixed The native representation of the content. Could be a
         *    string, a nested array structure, an object, a binary blob...
         *    anything, really.
@@ -199,9 +202,11 @@ interface Content {
         *
         * - Will return false if $that is null.
         * - Will return true if $that === $this.
-        * - Will return false if $that->getModel() != $this->getModel().
-        * - Will return false if $that->getNativeData() is not equal to $this->getNativeData(),
-        *   where the meaning of "equal" depends on the actual data model.
+        * - Will return false if $that->getModel() !== $this->getModel().
+        * - Will return false if get_class( $that ) !== get_class( $this )
+        * - Should return false if $that->getModel() == $this->getModel() and
+        *     $that is not semantically equivalent to $this, according to
+        *     the data model defined by $this->getModel().
         *
         * Implementations should be careful to make equals() transitive and reflexive:
         *
index 5c18a33..ae47b86 100644 (file)
@@ -88,7 +88,7 @@ abstract class ContentHandler {
                }
 
                if ( $content instanceof TextContent ) {
-                       return $content->getNativeData();
+                       return $content->getText();
                }
 
                wfDebugLog( 'ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!' );
index a09cd15..d32fa88 100644 (file)
@@ -61,7 +61,7 @@ class CssContent extends TextContent {
                global $wgParser;
                // @todo Make pre-save transformation optional for script pages
 
-               $text = $this->getNativeData();
+               $text = $this->getText();
                $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
 
                return new static( $pst );
@@ -73,7 +73,7 @@ class CssContent extends TextContent {
        protected function getHtml() {
                $html = "";
                $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
-               $html .= htmlspecialchars( $this->getNativeData() );
+               $html .= htmlspecialchars( $this->getText() );
                $html .= "\n</pre>\n";
 
                return $html;
@@ -99,7 +99,7 @@ class CssContent extends TextContent {
                        return $this->redirectTarget;
                }
                $this->redirectTarget = null;
-               $text = $this->getNativeData();
+               $text = $this->getText();
                if ( strpos( $text, '/* #REDIRECT */' ) === 0 ) {
                        // Extract the title from the url
                        preg_match( '/title=(.*?)&action=raw/', $text, $matches );
index 69ed8d4..e637798 100644 (file)
@@ -60,7 +60,7 @@ class JavaScriptContent extends TextContent {
                // @todo Make pre-save transformation optional for script pages
                // See T34858
 
-               $text = $this->getNativeData();
+               $text = $this->getText();
                $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
 
                return new static( $pst );
@@ -72,7 +72,7 @@ class JavaScriptContent extends TextContent {
        protected function getHtml() {
                $html = "";
                $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
-               $html .= htmlspecialchars( $this->getNativeData() );
+               $html .= htmlspecialchars( $this->getText() );
                $html .= "\n</pre>\n";
 
                return $html;
@@ -101,7 +101,7 @@ class JavaScriptContent extends TextContent {
                        return $this->redirectTarget;
                }
                $this->redirectTarget = null;
-               $text = $this->getNativeData();
+               $text = $this->getText();
                if ( strpos( $text, '/* #REDIRECT */' ) === 0 ) {
                        // Extract the title from the url
                        preg_match( '/title=(.*?)\\\\u0026action=raw/', $text, $matches );
index 7d8f67c..0f8a9a9 100644 (file)
@@ -36,7 +36,7 @@ class JsonContent extends TextContent {
         */
        public function getJsonData() {
                wfDeprecated( __METHOD__, '1.25' );
-               return FormatJson::decode( $this->getNativeData(), true );
+               return FormatJson::decode( $this->getText(), true );
        }
 
        /**
@@ -49,7 +49,7 @@ class JsonContent extends TextContent {
         */
        public function getData() {
                if ( $this->jsonParse === null ) {
-                       $this->jsonParse = FormatJson::parse( $this->getNativeData() );
+                       $this->jsonParse = FormatJson::parse( $this->getText() );
                }
                return $this->jsonParse;
        }
index b21c6f4..5626b54 100644 (file)
@@ -80,9 +80,22 @@ class MessageContent extends AbstractContent {
        /**
         * Returns the message object, with any parameters already substituted.
         *
+        * @deprecated since 1.33 use getMessage() instead.
+        *
         * @return Message The message object.
         */
        public function getNativeData() {
+               return $this->getMessage();
+       }
+
+       /**
+        * Returns the message object, with any parameters already substituted.
+        *
+        * @since 1.33
+        *
+        * @return Message The message object.
+        */
+       public function getMessage() {
                // NOTE: Message objects are mutable. Cloning here makes MessageContent immutable.
                return clone $this->mMessage;
        }
@@ -131,7 +144,8 @@ class MessageContent extends AbstractContent {
         * @see Content::copy
         */
        public function copy() {
-               // MessageContent is immutable (because getNativeData() returns a clone of the Message object)
+               // MessageContent is immutable (because getNativeData() and getMessage()
+               //   returns a clone of the Message object)
                return $this;
        }
 
index 0198a0d..750b958 100644 (file)
@@ -73,7 +73,7 @@ class TextContent extends AbstractContent {
        }
 
        public function getTextForSummary( $maxlength = 250 ) {
-               $text = $this->getNativeData();
+               $text = $this->getText();
 
                $truncatedtext = MediaWikiServices::getInstance()->getContentLanguage()->
                        truncateForDatabase( preg_replace( "/[\n\r]/", ' ', $text ), max( 0, $maxlength ) );
@@ -87,7 +87,7 @@ class TextContent extends AbstractContent {
         * @return int
         */
        public function getSize() {
-               $text = $this->getNativeData();
+               $text = $this->getText();
 
                return strlen( $text );
        }
@@ -118,9 +118,22 @@ class TextContent extends AbstractContent {
        /**
         * Returns the text represented by this Content object, as a string.
         *
-        * @return string The raw text.
+        * @deprecated since 1.33 use getText() instead.
+        *
+        * @return string The raw text. Subclasses may guarantee a specific syntax here.
         */
        public function getNativeData() {
+               return $this->getText();
+       }
+
+       /**
+        * Returns the text represented by this Content object, as a string.
+        *
+        * @since 1.33
+        *
+        * @return string The raw text.
+        */
+       public function getText() {
                return $this->mText;
        }
 
@@ -130,7 +143,7 @@ class TextContent extends AbstractContent {
         * @return string The raw text.
         */
        public function getTextForSearchIndex() {
-               return $this->getNativeData();
+               return $this->getText();
        }
 
        /**
@@ -145,7 +158,7 @@ class TextContent extends AbstractContent {
                $wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' );
 
                if ( $wikitext ) {
-                       return $wikitext->getNativeData();
+                       return $wikitext->getText();
                } else {
                        return false;
                }
@@ -181,7 +194,7 @@ class TextContent extends AbstractContent {
         * @return Content
         */
        public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
-               $text = $this->getNativeData();
+               $text = $this->getText();
                $pst = self::normalizeLineEndings( $text );
 
                return ( $text === $pst ) ? $this : new static( $pst, $this->getModel() );
@@ -208,8 +221,8 @@ class TextContent extends AbstractContent {
                        $lang = MediaWikiServices::getInstance()->getContentLanguage();
                }
 
-               $otext = $this->getNativeData();
-               $ntext = $that->getNativeData();
+               $otext = $this->getText();
+               $ntext = $that->getText();
 
                # Note: Use native PHP diff, external engines don't give us abstract output
                $ota = explode( "\n", $lang->segmentForDiff( $otext ) );
@@ -244,7 +257,7 @@ class TextContent extends AbstractContent {
 
                if ( in_array( $this->getModel(), $wgTextModelsToParse ) ) {
                        // parse just to get links etc into the database, HTML is replaced below.
-                       $output = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
+                       $output = $wgParser->parse( $this->getText(), $title, $options, true, true, $revId );
                }
 
                if ( $generateHtml ) {
@@ -291,7 +304,7 @@ class TextContent extends AbstractContent {
         * @return string An HTML representation of the content
         */
        protected function getHighlightHtml() {
-               return htmlspecialchars( $this->getNativeData() );
+               return htmlspecialchars( $this->getText() );
        }
 
        /**
@@ -318,7 +331,7 @@ class TextContent extends AbstractContent {
 
                if ( $toHandler instanceof TextContentHandler ) {
                        // NOTE: ignore content serialization format - it's just text anyway.
-                       $text = $this->getNativeData();
+                       $text = $this->getText();
                        $converted = $toHandler->unserializeContent( $text );
                }
 
index 0978ffc..e3dc187 100644 (file)
@@ -45,7 +45,7 @@ class TextContentHandler extends ContentHandler {
        public function serializeContent( Content $content, $format = null ) {
                $this->checkFormat( $format );
 
-               return $content->getNativeData();
+               return $content->getText();
        }
 
        /**
index 517d807..3e2313c 100644 (file)
@@ -61,7 +61,7 @@ class WikitextContent extends TextContent {
        public function getSection( $sectionId ) {
                global $wgParser;
 
-               $text = $this->getNativeData();
+               $text = $this->getText();
                $sect = $wgParser->getSection( $text, $sectionId, false );
 
                if ( $sect === false ) {
@@ -91,8 +91,8 @@ class WikitextContent extends TextContent {
                                "section uses $sectionModelId." );
                }
 
-               $oldtext = $this->getNativeData();
-               $text = $with->getNativeData();
+               $oldtext = $this->getText();
+               $text = $with->getText();
 
                if ( strval( $sectionId ) === '' ) {
                        return $with; # XXX: copy first?
@@ -131,7 +131,7 @@ class WikitextContent extends TextContent {
                $text = wfMessage( 'newsectionheaderdefaultlevel' )
                        ->rawParams( $header )->inContentLanguage()->text();
                $text .= "\n\n";
-               $text .= $this->getNativeData();
+               $text .= $this->getText();
 
                return new static( $text );
        }
@@ -149,7 +149,7 @@ class WikitextContent extends TextContent {
        public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
                global $wgParser;
 
-               $text = $this->getNativeData();
+               $text = $this->getText();
                $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
 
                if ( $text === $pst ) {
@@ -178,7 +178,7 @@ class WikitextContent extends TextContent {
        public function preloadTransform( Title $title, ParserOptions $popts, $params = [] ) {
                global $wgParser;
 
-               $text = $this->getNativeData();
+               $text = $this->getText();
                $plt = $wgParser->getPreloadText( $text, $title, $popts, $params );
 
                return new static( $plt );
@@ -202,12 +202,12 @@ class WikitextContent extends TextContent {
 
                if ( $wgMaxRedirects < 1 ) {
                        // redirects are disabled, so quit early
-                       $this->redirectTargetAndText = [ null, $this->getNativeData() ];
+                       $this->redirectTargetAndText = [ null, $this->getText() ];
                        return $this->redirectTargetAndText;
                }
 
                $redir = MediaWikiServices::getInstance()->getMagicWordFactory()->get( 'redirect' );
-               $text = ltrim( $this->getNativeData() );
+               $text = ltrim( $this->getText() );
                if ( $redir->matchStartAndRemove( $text ) ) {
                        // Extract the first link and see if it's usable
                        // Ensure that it really does come directly after #REDIRECT
@@ -223,7 +223,7 @@ class WikitextContent extends TextContent {
                                $title = Title::newFromText( $m[1] );
                                // If the title is a redirect to bad special pages or is invalid, return null
                                if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
-                                       $this->redirectTargetAndText = [ null, $this->getNativeData() ];
+                                       $this->redirectTargetAndText = [ null, $this->getText() ];
                                        return $this->redirectTargetAndText;
                                }
 
@@ -232,7 +232,7 @@ class WikitextContent extends TextContent {
                        }
                }
 
-               $this->redirectTargetAndText = [ null, $this->getNativeData() ];
+               $this->redirectTargetAndText = [ null, $this->getText() ];
                return $this->redirectTargetAndText;
        }
 
@@ -271,7 +271,7 @@ class WikitextContent extends TextContent {
                # so the regex has to be fairly general
                $newText = preg_replace( '/ \[ \[  [^\]]*  \] \] /x',
                        '[[' . $target->getFullText() . ']]',
-                       $this->getNativeData(), 1 );
+                       $this->getText(), 1 );
 
                return new static( $newText );
        }
@@ -408,7 +408,7 @@ class WikitextContent extends TextContent {
         * @see Content::matchMagicWord()
         */
        public function matchMagicWord( MagicWord $word ) {
-               return $word->match( $this->getNativeData() );
+               return $word->match( $this->getText() );
        }
 
 }
index ab157f5..191c718 100644 (file)
@@ -162,4 +162,24 @@ class WikitextContentHandler extends TextContentHandler {
                return $fields;
        }
 
+       /**
+        * Returns the content's text as-is.
+        *
+        * @param Content $content
+        * @param string|null $format The serialization format to check
+        *
+        * @return mixed
+        */
+       public function serializeContent( Content $content, $format = null ) {
+               $this->checkFormat( $format );
+
+               // NOTE: MessageContent also uses CONTENT_MODEL_WIKITEXT, but it's not a TextContent!
+               // Perhaps MessageContent should use a separate ContentHandler instead.
+               if ( $content instanceof MessageContent ) {
+                       return $content->getMessage()->plain();
+               }
+
+               return parent::serializeContent( $content, $format );
+       }
+
 }
index 628b47b..6af6de5 100644 (file)
@@ -589,7 +589,7 @@ class DatabaseOracle extends Database {
        public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
                $fname = __METHOD__
        ) {
-               if ( !count( $rows ) ) {
+               if ( $rows === [] ) {
                        return true; // nothing to do
                }
 
index 81dc78d..354cc61 100644 (file)
@@ -58,7 +58,7 @@ class TextboxBuilder {
         * @return mixed[]
         */
        public function mergeClassesIntoAttributes( array $classes, array $attribs ) {
-               if ( !count( $classes ) ) {
+               if ( $classes === [] ) {
                        return $attribs;
                }
 
index b3eae90..bb65b0a 100644 (file)
@@ -405,7 +405,7 @@ class LocalRepo extends FileRepo {
         * @return array[] An Array of arrays or iterators of file objects and the hash as key
         */
        function findBySha1s( array $hashes ) {
-               if ( !count( $hashes ) ) {
+               if ( $hashes === [] ) {
                        return []; // empty parameter
                }
 
index 4a84cff..6a3e819 100644 (file)
@@ -169,7 +169,7 @@ class ArchivedFile {
                        $conds['fa_sha1'] = $this->sha1;
                }
 
-               if ( !count( $conds ) ) {
+               if ( $conds === [] ) {
                        throw new MWException( "No specific information for retrieving archived file" );
                }
 
index 029f67d..5a3d77a 100644 (file)
@@ -694,7 +694,7 @@ abstract class Installer {
                                'enableSectionEditLinks' => false,
                                'unwrap' => true,
                        ] );
-               } catch ( MediaWiki\Services\ServiceDisabledException $e ) {
+               } catch ( Wikimedia\Services\ServiceDisabledException $e ) {
                        $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
                }
 
index 3689ba4..4f4728d 100644 (file)
@@ -323,7 +323,7 @@ abstract class JobQueue {
        final public function batchPush( array $jobs, $flags = 0 ) {
                $this->assertNotReadOnly();
 
-               if ( !count( $jobs ) ) {
+               if ( $jobs === [] ) {
                        return; // nothing to do
                }
 
index 9931d83..fa17284 100644 (file)
@@ -214,7 +214,7 @@ class JobQueueDB extends JobQueue {
         * @return void
         */
        public function doBatchPushInternal( IDatabase $dbw, array $jobs, $flags, $method ) {
-               if ( !count( $jobs ) ) {
+               if ( $jobs === [] ) {
                        return;
                }
 
index b103b8e..4853c4a 100644 (file)
@@ -143,7 +143,7 @@ class JobQueueGroup {
                }
 
                $jobs = is_array( $jobs ) ? $jobs : [ $jobs ];
-               if ( !count( $jobs ) ) {
+               if ( $jobs === [] ) {
                        return;
                }
 
index b868128..a1ef28b 100644 (file)
@@ -203,7 +203,7 @@ class JobQueueRedis extends JobQueue {
                        }
                }
 
-               if ( !count( $items ) ) {
+               if ( $items === [] ) {
                        return; // nothing to do
                }
 
index 27e6924..06a155a 100644 (file)
@@ -417,7 +417,7 @@ abstract class FileBackend implements LoggerAwareInterface {
                if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
                        return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
                }
-               if ( !count( $ops ) ) {
+               if ( $ops === [] ) {
                        return $this->newStatus(); // nothing to do
                }
 
@@ -436,6 +436,7 @@ abstract class FileBackend implements LoggerAwareInterface {
         * @see FileBackend::doOperations()
         * @param array $ops
         * @param array $opts
+        * @return StatusValue
         */
        abstract protected function doOperationsInternal( array $ops, array $opts );
 
@@ -655,7 +656,7 @@ abstract class FileBackend implements LoggerAwareInterface {
                if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
                        return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
                }
-               if ( !count( $ops ) ) {
+               if ( $ops === [] ) {
                        return $this->newStatus(); // nothing to do
                }
 
@@ -673,6 +674,7 @@ abstract class FileBackend implements LoggerAwareInterface {
        /**
         * @see FileBackend::doQuickOperations()
         * @param array $ops
+        * @return StatusValue
         * @since 1.20
         */
        abstract protected function doQuickOperationsInternal( array $ops );
@@ -819,6 +821,7 @@ abstract class FileBackend implements LoggerAwareInterface {
        /**
         * @see FileBackend::prepare()
         * @param array $params
+        * @return StatusValue
         */
        abstract protected function doPrepare( array $params );
 
@@ -850,6 +853,7 @@ abstract class FileBackend implements LoggerAwareInterface {
        /**
         * @see FileBackend::secure()
         * @param array $params
+        * @return StatusValue
         */
        abstract protected function doSecure( array $params );
 
@@ -883,6 +887,7 @@ abstract class FileBackend implements LoggerAwareInterface {
        /**
         * @see FileBackend::publish()
         * @param array $params
+        * @return StatusValue
         */
        abstract protected function doPublish( array $params );
 
@@ -909,6 +914,7 @@ abstract class FileBackend implements LoggerAwareInterface {
        /**
         * @see FileBackend::clean()
         * @param array $params
+        * @return StatusValue
         */
        abstract protected function doClean( array $params );
 
index 47be4eb..999594b 100644 (file)
@@ -97,7 +97,7 @@ abstract class FileJournal {
         * @return StatusValue
         */
        final public function logChangeBatch( array $entries, $batchId ) {
-               if ( !count( $entries ) ) {
+               if ( $entries === [] ) {
                        return StatusValue::newGood();
                }
 
index f2624e7..019029c 100644 (file)
@@ -169,7 +169,7 @@ class FSLockManager extends LockManager {
                        if ( $this->locksHeld[$path][$type] <= 0 ) {
                                unset( $this->locksHeld[$path][$type] );
                        }
-                       if ( !count( $this->locksHeld[$path] ) ) {
+                       if ( $this->locksHeld[$path] === [] ) {
                                unset( $this->locksHeld[$path] ); // no locks on this path
                                if ( isset( $this->handles[$path] ) ) {
                                        $handlesToClose[] = $this->handles[$path];
index 65c6993..fd3ffa5 100644 (file)
@@ -18,7 +18,7 @@ class PostgreSqlLockManager extends DBLockManager {
 
        protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
                $status = StatusValue::newGood();
-               if ( !count( $paths ) ) {
+               if ( $paths === [] ) {
                        return $status; // nothing to lock
                }
 
index 1d2e21a..1ef4642 100644 (file)
@@ -98,7 +98,7 @@ abstract class QuorumLockManager extends LockManager {
                                                $bucket = $this->getBucketFromPath( $path );
                                                $pathsToUnlock[$bucket][$type][] = $path;
                                        }
-                                       if ( !count( $this->locksHeld[$path] ) ) {
+                                       if ( $this->locksHeld[$path] === [] ) {
                                                unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
                                        }
                                }
@@ -110,7 +110,7 @@ abstract class QuorumLockManager extends LockManager {
                foreach ( $pathsToUnlock as $bucket => $pathsToUnlockByType ) {
                        $status->merge( $this->doUnlockingRequestBucket( $bucket, $pathsToUnlockByType ) );
                }
-               if ( !count( $this->locksHeld ) ) {
+               if ( $this->locksHeld === [] ) {
                        $status->merge( $this->releaseAllLocks() );
                        $this->degradedBuckets = []; // safe to retry the normal quorum
                }
index 938e534..3e71e36 100644 (file)
@@ -202,7 +202,7 @@ class ChronologyProtector implements LoggerAwareInterface {
                        );
                }
 
-               if ( !count( $this->shutdownPositions ) ) {
+               if ( $this->shutdownPositions === [] ) {
                        return []; // nothing to save
                }
 
index 9a9e36a..7d971af 100644 (file)
@@ -2855,7 +2855,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
                $fname = __METHOD__
        ) {
-               if ( !count( $rows ) ) {
+               if ( $rows === [] ) {
                        return true; // nothing to do
                }
 
index 3fcbcf9..186c89f 100644 (file)
@@ -1337,7 +1337,7 @@ abstract class DatabaseMysqlBase extends Database {
        public function upsert( $table, array $rows, array $uniqueIndexes,
                array $set, $fname = __METHOD__
        ) {
-               if ( !count( $rows ) ) {
+               if ( $rows === [] ) {
                        return true; // nothing to do
                }
 
index ca18122..ab5c3cd 100644 (file)
@@ -408,7 +408,7 @@ class LoadBalancer implements ILoadBalancer {
         * @return array (reader index, lagged replica mode) or false on failure
         */
        private function pickReaderIndex( array $loads, $domain = false ) {
-               if ( !count( $loads ) ) {
+               if ( $loads === [] ) {
                        throw new InvalidArgumentException( "Empty server array given to LoadBalancer" );
                }
 
@@ -476,7 +476,7 @@ class LoadBalancer implements ILoadBalancer {
                }
 
                // If all servers were down, quit now
-               if ( !count( $currentLoads ) ) {
+               if ( $currentLoads === [] ) {
                        $this->connLogger->error( __METHOD__ . ": all servers down" );
                }
 
diff --git a/includes/libs/services/CannotReplaceActiveServiceException.php b/includes/libs/services/CannotReplaceActiveServiceException.php
new file mode 100644 (file)
index 0000000..ee0d7d0
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace Wikimedia\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ *
+ * 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
+ */
+
+/**
+ * Exception thrown when trying to replace an already active service.
+ */
+class CannotReplaceActiveServiceException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
+       }
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( CannotReplaceActiveServiceException::class,
+       'MediaWiki\Services\CannotReplaceActiveServiceException' );
diff --git a/includes/libs/services/ContainerDisabledException.php b/includes/libs/services/ContainerDisabledException.php
new file mode 100644 (file)
index 0000000..c909879
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+namespace Wikimedia\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ *
+ * 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
+ */
+
+/**
+ * Exception thrown when trying to access a service on a disabled container or factory.
+ */
+class ContainerDisabledException extends RuntimeException {
+
+       /**
+        * @param Exception|null $previous
+        */
+       public function __construct( Exception $previous = null ) {
+               parent::__construct( 'Container disabled!', 0, $previous );
+       }
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( ContainerDisabledException::class, 'MediaWiki\Services\ContainerDisabledException' );
diff --git a/includes/libs/services/DestructibleService.php b/includes/libs/services/DestructibleService.php
new file mode 100644 (file)
index 0000000..46e2f82
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+namespace Wikimedia\Services;
+
+/**
+ * Interface for destructible 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
+ */
+
+/**
+ * DestructibleService defines a standard interface for shutting down a service instance.
+ * The intended use is for a service container to be able to shut down services that should
+ * no longer be used, and allow such services to release any system resources.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface DestructibleService {
+
+       /**
+        * Notifies the service object that it should expect to no longer be used, and should release
+        * any system resources it may own. The behavior of all service methods becomes undefined after
+        * destroy() has been called. It is recommended that implementing classes should throw an
+        * exception when service methods are accessed after destroy() has been called.
+        */
+       public function destroy();
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( DestructibleService::class, 'MediaWiki\Services\DestructibleService' );
diff --git a/includes/libs/services/NoSuchServiceException.php b/includes/libs/services/NoSuchServiceException.php
new file mode 100644 (file)
index 0000000..3a0cdf4
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+namespace Wikimedia\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when the requested service is not known.
+ *
+ * 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
+ */
+
+/**
+ * Exception thrown when the requested service is not known.
+ */
+class NoSuchServiceException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "No such service: $serviceName", 0, $previous );
+       }
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( NoSuchServiceException::class, 'MediaWiki\Services\NoSuchServiceException' );
diff --git a/includes/libs/services/SalvageableService.php b/includes/libs/services/SalvageableService.php
new file mode 100644 (file)
index 0000000..4f94515
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+namespace Wikimedia\Services;
+
+/**
+ * Interface for salvageable 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.28
+ */
+
+/**
+ * SalvageableService defines an interface for services that are able to salvage state from a
+ * previous instance of the same class. The intent is to allow new service instances to re-use
+ * resources that would be expensive to re-create, such as cached data or network connections.
+ *
+ * @note There is no expectation that services will be destroyed when the process (or web request)
+ * terminates.
+ */
+interface SalvageableService {
+
+       /**
+        * Re-uses state from $other. $other must not be used after being passed to salvage(),
+        * and should be considered to be destroyed.
+        *
+        * @note Implementations are responsible for determining what parts of $other can be re-used
+        * safely. In particular, implementations should check that the relevant configuration of
+        * $other is the same as in $this before re-using resources from $other.
+        *
+        * @note Implementations must take care to detach any re-used resources from the original
+        * service instance. If $other is destroyed later, resources that are now used by the
+        * new service instance must not be affected.
+        *
+        * @note If $other is a DestructibleService, implementations should make sure that $other
+        * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
+        * after carefully detaching all relevant resources.
+        *
+        * @param SalvageableService $other The object to salvage state from. $other must have the
+        * exact same type as $this.
+        */
+       public function salvage( SalvageableService $other );
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( SalvageableService::class, 'MediaWiki\Services\SalvageableService' );
diff --git a/includes/libs/services/ServiceAlreadyDefinedException.php b/includes/libs/services/ServiceAlreadyDefinedException.php
new file mode 100644 (file)
index 0000000..39c8384
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace Wikimedia\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ *
+ * 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
+ */
+
+/**
+ * Exception thrown when a service was already defined, but the
+ * caller expected it to not exist.
+ */
+class ServiceAlreadyDefinedException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Service already defined: $serviceName", 0, $previous );
+       }
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( ServiceAlreadyDefinedException::class,
+       'MediaWiki\Services\ServiceAlreadyDefinedException' );
diff --git a/includes/libs/services/ServiceContainer.php b/includes/libs/services/ServiceContainer.php
new file mode 100644 (file)
index 0000000..59e5c4b
--- /dev/null
@@ -0,0 +1,471 @@
+<?php
+namespace Wikimedia\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 implements DestructibleService {
+
+       /**
+        * @var object[]
+        */
+       private $services = [];
+
+       /**
+        * @var callable[]
+        */
+       private $serviceInstantiators = [];
+
+       /**
+        * @var callable[][]
+        */
+       private $serviceManipulators = [];
+
+       /**
+        * @var bool[] disabled status, per service name
+        */
+       private $disabled = [];
+
+       /**
+        * @var array
+        */
+       private $extraInstantiationParams;
+
+       /**
+        * @var bool
+        */
+       private $destroyed = false;
+
+       /**
+        * @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;
+       }
+
+       /**
+        * Destroys all contained service instances that implement the DestructibleService
+        * interface. This will render all services obtained from this ServiceContainer
+        * instance unusable. In particular, this will disable access to the storage backend
+        * via any of these services. Any future call to getService() will throw an exception.
+        *
+        * @see resetGlobalInstance()
+        */
+       public function destroy() {
+               foreach ( $this->getServiceNames() as $name ) {
+                       $service = $this->peekService( $name );
+                       if ( $service !== null && $service instanceof DestructibleService ) {
+                               $service->destroy();
+                       }
+               }
+
+               // Break circular references due to the $this reference in closures, by
+               // erasing the instantiator array. This allows the ServiceContainer to
+               // be deleted when it goes out of scope.
+               $this->serviceInstantiators = [];
+               // Also remove the services themselves, to avoid confusion.
+               $this->services = [];
+               $this->destroyed = true;
+       }
+
+       /**
+        * @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 );
+               }
+       }
+
+       /**
+        * Imports all wiring defined in $container. Wiring defined in $container
+        * will override any wiring already defined locally. However, already
+        * existing service instances will be preserved.
+        *
+        * @since 1.28
+        *
+        * @param ServiceContainer $container
+        * @param string[] $skip A list of service names to skip during import
+        */
+       public function importWiring( ServiceContainer $container, $skip = [] ) {
+               $newInstantiators = array_diff_key(
+                       $container->serviceInstantiators,
+                       array_flip( $skip )
+               );
+
+               $this->serviceInstantiators = array_merge(
+                       $this->serviceInstantiators,
+                       $newInstantiators
+               );
+
+               $newManipulators = array_diff(
+                       array_keys( $container->serviceManipulators ),
+                       $skip
+               );
+
+               foreach ( $newManipulators as $name ) {
+                       if ( isset( $this->serviceManipulators[$name] ) ) {
+                               $this->serviceManipulators[$name] = array_merge(
+                                       $this->serviceManipulators[$name],
+                                       $container->serviceManipulators[$name]
+                               );
+                       } else {
+                               $this->serviceManipulators[$name] = $container->serviceManipulators[$name];
+                       }
+               }
+       }
+
+       /**
+        * 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] );
+       }
+
+       /**
+        * Returns the service instance for $name only if that service has already been instantiated.
+        * This is intended for situations where services get destroyed/cleaned up, so we can
+        * avoid creating a service just to destroy it again.
+        *
+        * @note This is intended for internal use and for test fixtures.
+        * Application logic should use getService() instead.
+        *
+        * @see getService().
+        *
+        * @param string $name
+        *
+        * @return object|null The service instance, or null if the service has not yet been instantiated.
+        * @throws RuntimeException if $name does not refer to a known service.
+        */
+       public function peekService( $name ) {
+               if ( !$this->hasService( $name ) ) {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               return $this->services[$name] ?? null;
+       }
+
+       /**
+        * @return string[]
+        */
+       public function getServiceNames() {
+               return array_keys( $this->serviceInstantiators );
+       }
+
+       /**
+        * Define a new service. The service must not be known already.
+        *
+        * @see getService().
+        * @see redefineService().
+        *
+        * @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 ServiceContainer 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 ServiceAlreadyDefinedException( $name );
+               }
+
+               $this->serviceInstantiators[$name] = $instantiator;
+       }
+
+       /**
+        * Replace an already defined service.
+        *
+        * @see defineService().
+        *
+        * @note This will fail if the service was already instantiated. If the service was previously
+        * disabled, it will be re-enabled by this call. Any manipulators registered for the service
+        * will remain in place.
+        *
+        * @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 ServiceContainer 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 NoSuchServiceException if $name is not a known service.
+        * @throws CannotReplaceActiveServiceException if the service was already instantiated.
+        */
+       public function redefineService( $name, callable $instantiator ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               if ( !$this->hasService( $name ) ) {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               if ( isset( $this->services[$name] ) ) {
+                       throw new CannotReplaceActiveServiceException( $name );
+               }
+
+               $this->serviceInstantiators[$name] = $instantiator;
+               unset( $this->disabled[$name] );
+       }
+
+       /**
+        * Add a service manipulator callback for the given service.
+        * This method may be used by extensions that need to wrap, replace, or re-configure a
+        * service. It would typically be called from a MediaWikiServices hook handler.
+        *
+        * The manipulator callback is called just after the service is instantiated.
+        * It can call methods on the service to change configuration, or wrap or otherwise
+        * replace it.
+        *
+        * @see defineService().
+        * @see redefineService().
+        *
+        * @note This will fail if the service was already instantiated.
+        *
+        * @since 1.32
+        *
+        * @param string $name The name of the service to manipulate.
+        * @param callable $manipulator Callback function that manipulates, wraps or replaces a
+        * service instance. The callback receives the new service instance and this
+        * ServiceContainer as parameters, as well as any extra instantiation parameters specified
+        * when constructing this ServiceContainer. If the callback returns a value, that
+        * value replaces the original service instance.
+        *
+        * @throws NoSuchServiceException if $name is not a known service.
+        * @throws CannotReplaceActiveServiceException if the service was already instantiated.
+        */
+       public function addServiceManipulator( $name, callable $manipulator ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               if ( !$this->hasService( $name ) ) {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               if ( isset( $this->services[$name] ) ) {
+                       throw new CannotReplaceActiveServiceException( $name );
+               }
+
+               $this->serviceManipulators[$name][] = $manipulator;
+       }
+
+       /**
+        * Disables a service.
+        *
+        * @note Attempts to call getService() for a disabled service will result
+        * in a DisabledServiceException. Calling peekService for a disabled service will
+        * return null. Disabled services are listed by getServiceNames(). A disabled service
+        * can be enabled again using redefineService().
+        *
+        * @note If the service was already active (that is, instantiated) when getting disabled,
+        * and the service instance implements DestructibleService, destroy() is called on the
+        * service instance.
+        *
+        * @see redefineService()
+        * @see resetService()
+        *
+        * @param string $name The name of the service to disable.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       public function disableService( $name ) {
+               $this->resetService( $name );
+
+               $this->disabled[$name] = true;
+       }
+
+       /**
+        * Resets a service by dropping the service instance.
+        * If the service instances implements DestructibleService, destroy()
+        * is called on the service instance.
+        *
+        * @warning This is generally unsafe! Other services may still retain references
+        * to the stale service instance, leading to failures and inconsistencies. Subclasses
+        * may use this method to reset specific services under specific instances, but
+        * it should not be exposed to application logic.
+        *
+        * @note This is declared final so subclasses can not interfere with the expectations
+        * disableService() has when calling resetService().
+        *
+        * @see redefineService()
+        * @see disableService().
+        *
+        * @param string $name The name of the service to reset.
+        * @param bool $destroy Whether the service instance should be destroyed if it exists.
+        *        When set to false, any existing service instance will effectively be detached
+        *        from the container.
+        *
+        * @throws RuntimeException if $name is not a known service.
+        */
+       final protected function resetService( $name, $destroy = true ) {
+               Assert::parameterType( 'string', $name, '$name' );
+
+               $instance = $this->peekService( $name );
+
+               if ( $destroy && $instance instanceof DestructibleService ) {
+                       $instance->destroy();
+               }
+
+               unset( $this->services[$name] );
+               unset( $this->disabled[$name] );
+       }
+
+       /**
+        * 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 NoSuchServiceException if $name is not a known service.
+        * @throws ContainerDisabledException if this container has already been destroyed.
+        * @throws ServiceDisabledException if the requested service has been disabled.
+        *
+        * @return object The service instance
+        */
+       public function getService( $name ) {
+               if ( $this->destroyed ) {
+                       throw new ContainerDisabledException();
+               }
+
+               if ( isset( $this->disabled[$name] ) ) {
+                       throw new ServiceDisabledException( $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 = ( $this->serviceInstantiators[$name] )(
+                               $this,
+                               ...$this->extraInstantiationParams
+                       );
+
+                       if ( isset( $this->serviceManipulators[$name] ) ) {
+                               foreach ( $this->serviceManipulators[$name] as $callback ) {
+                                       $ret = call_user_func_array(
+                                               $callback,
+                                               array_merge( [ $service, $this ], $this->extraInstantiationParams )
+                                       );
+
+                                       // If the manipulator callback returns an object, that object replaces
+                                       // the original service instance. This allows the manipulator to wrap
+                                       // or fully replace the service.
+                                       if ( $ret !== null ) {
+                                               $service = $ret;
+                                       }
+                               }
+                       }
+
+                       // NOTE: when adding more wiring logic here, make sure importWiring() is kept in sync!
+               } else {
+                       throw new NoSuchServiceException( $name );
+               }
+
+               return $service;
+       }
+
+       /**
+        * @param string $name
+        * @return bool Whether the service is disabled
+        * @since 1.28
+        */
+       public function isServiceDisabled( $name ) {
+               return isset( $this->disabled[$name] );
+       }
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( ServiceContainer::class, 'MediaWiki\Services\ServiceContainer' );
diff --git a/includes/libs/services/ServiceDisabledException.php b/includes/libs/services/ServiceDisabledException.php
new file mode 100644 (file)
index 0000000..86b927b
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+namespace Wikimedia\Services;
+
+use Exception;
+use RuntimeException;
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ *
+ * 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
+ */
+
+/**
+ * Exception thrown when trying to access a disabled service.
+ */
+class ServiceDisabledException extends RuntimeException {
+
+       /**
+        * @param string $serviceName
+        * @param Exception|null $previous
+        */
+       public function __construct( $serviceName, Exception $previous = null ) {
+               parent::__construct( "Service disabled: $serviceName", 0, $previous );
+       }
+
+}
+
+/**
+ * Retain the old class name for backwards compatibility.
+ * @deprecated since 1.33
+ */
+class_alias( ServiceDisabledException::class, 'MediaWiki\Services\ServiceDisabledException' );
index 8a089f6..76a7760 100644 (file)
@@ -154,7 +154,7 @@ class EmailNotification {
                // If nobody is watching the page, and there are no users notified on all changes
                // don't bother creating a job/trying to send emails, unless it's a
                // talk page with an applicable notification.
-               if ( !count( $watchers ) && !count( $wgUsersNotifiedOnAllChanges ) ) {
+               if ( $watchers === [] && !count( $wgUsersNotifiedOnAllChanges ) ) {
                        $sendEmail = false;
                        // Only send notification for non minor edits, unless $wgEnotifMinorEdits
                        if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
index f4e4efa..3bcd012 100644 (file)
@@ -878,7 +878,7 @@ class PPDStack {
        }
 
        public function pop() {
-               if ( !count( $this->stack ) ) {
+               if ( $this->stack === [] ) {
                        throw new MWException( __METHOD__ . ': no elements remaining' );
                }
                $temp = array_pop( $this->stack );
@@ -902,7 +902,7 @@ class PPDStack {
         * @return array
         */
        public function getFlags() {
-               if ( !count( $this->stack ) ) {
+               if ( $this->stack === [] ) {
                        return [
                                'findEquals' => false,
                                'findPipe' => false,
index 9570e03..c513aed 100644 (file)
@@ -781,7 +781,7 @@ class ResourceLoader implements LoggerAwareInterface {
                }
 
                // Save response to file cache unless there are errors
-               if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
+               if ( isset( $fileCache ) && !$this->errors && $missing === [] ) {
                        // Cache single modules and images...and other requests if there are enough hits
                        if ( ResourceFileCache::useFileCache( $context ) ) {
                                if ( $fileCache->isCacheWorthy() ) {
@@ -1036,7 +1036,7 @@ class ResourceLoader implements LoggerAwareInterface {
                $out = '';
                $states = [];
 
-               if ( !count( $modules ) && !count( $missing ) ) {
+               if ( $modules === [] && $missing === [] ) {
                        return <<<MESSAGE
 /* This file is the Web entry point for MediaWiki's ResourceLoader:
    <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
index ef11628..8cd5b19 100644 (file)
@@ -295,9 +295,20 @@ class ResourceLoaderImage {
                $dom = new DOMDocument;
                $dom->loadXML( file_get_contents( $this->getPath( $context ) ) );
                $root = $dom->documentElement;
-               $wrapper = $dom->createElement( 'g' );
+               $titleNode = null;
+               $wrapper = $dom->createElementNS( 'http://www.w3.org/2000/svg', 'g' );
+               // Reattach all direct children of the `<svg>` root node to the `<g>` wrapper
                while ( $root->firstChild ) {
-                       $wrapper->appendChild( $root->firstChild );
+                       $node = $root->firstChild;
+                       if ( !$titleNode && $node->nodeType === XML_ELEMENT_NODE && $node->tagName === 'title' ) {
+                               // Remember the first encountered `<title>` node
+                               $titleNode = $node;
+                       }
+                       $wrapper->appendChild( $node );
+               }
+               if ( $titleNode ) {
+                       // Reattach the `<title>` node to the `<svg>` root node rather than the `<g>` wrapper
+                       $root->appendChild( $titleNode );
                }
                $root->appendChild( $wrapper );
                $wrapper->setAttribute( 'fill', $variantConf['color'] );
index 9cd245a..0cbb41c 100644 (file)
@@ -98,7 +98,7 @@ class SearchOracle extends SearchDatabase {
                if ( is_null( $this->namespaces ) ) {
                        return '';
                }
-               if ( !count( $this->namespaces ) ) {
+               if ( $this->namespaces === [] ) {
                        $namespaces = '0';
                } else {
                        $namespaces = $this->db->makeList( $this->namespaces );
index 6332ea2..f653796 100644 (file)
@@ -199,7 +199,7 @@ class SearchSqlite extends SearchDatabase {
                if ( is_null( $this->namespaces ) ) {
                        return '';  # search all
                }
-               if ( !count( $this->namespaces ) ) {
+               if ( $this->namespaces === [] ) {
                        $namespaces = '0';
                } else {
                        $namespaces = $this->db->makeList( $this->namespaces );
diff --git a/includes/services/CannotReplaceActiveServiceException.php b/includes/services/CannotReplaceActiveServiceException.php
deleted file mode 100644 (file)
index 4993073..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to replace an already active service.
- *
- * 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
- */
-
-/**
- * Exception thrown when trying to replace an already active service.
- */
-class CannotReplaceActiveServiceException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "Cannot replace an active service: $serviceName", 0, $previous );
-       }
-
-}
diff --git a/includes/services/ContainerDisabledException.php b/includes/services/ContainerDisabledException.php
deleted file mode 100644 (file)
index ede076d..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to access a service on a disabled container or factory.
- *
- * 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
- */
-
-/**
- * Exception thrown when trying to access a service on a disabled container or factory.
- */
-class ContainerDisabledException extends RuntimeException {
-
-       /**
-        * @param Exception|null $previous
-        */
-       public function __construct( Exception $previous = null ) {
-               parent::__construct( 'Container disabled!', 0, $previous );
-       }
-
-}
diff --git a/includes/services/DestructibleService.php b/includes/services/DestructibleService.php
deleted file mode 100644 (file)
index 6ce9af2..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-/**
- * Interface for destructible 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
- */
-
-/**
- * DestructibleService defines a standard interface for shutting down a service instance.
- * The intended use is for a service container to be able to shut down services that should
- * no longer be used, and allow such services to release any system resources.
- *
- * @note There is no expectation that services will be destroyed when the process (or web request)
- * terminates.
- */
-interface DestructibleService {
-
-       /**
-        * Notifies the service object that it should expect to no longer be used, and should release
-        * any system resources it may own. The behavior of all service methods becomes undefined after
-        * destroy() has been called. It is recommended that implementing classes should throw an
-        * exception when service methods are accessed after destroy() has been called.
-        */
-       public function destroy();
-
-}
diff --git a/includes/services/NoSuchServiceException.php b/includes/services/NoSuchServiceException.php
deleted file mode 100644 (file)
index 36e50d2..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when the requested service is not known.
- *
- * 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
- */
-
-/**
- * Exception thrown when the requested service is not known.
- */
-class NoSuchServiceException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "No such service: $serviceName", 0, $previous );
-       }
-
-}
diff --git a/includes/services/SalvageableService.php b/includes/services/SalvageableService.php
deleted file mode 100644 (file)
index a613050..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-/**
- * Interface for salvageable 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.28
- */
-
-/**
- * SalvageableService defines an interface for services that are able to salvage state from a
- * previous instance of the same class. The intent is to allow new service instances to re-use
- * resources that would be expensive to re-create, such as cached data or network connections.
- *
- * @note There is no expectation that services will be destroyed when the process (or web request)
- * terminates.
- */
-interface SalvageableService {
-
-       /**
-        * Re-uses state from $other. $other must not be used after being passed to salvage(),
-        * and should be considered to be destroyed.
-        *
-        * @note Implementations are responsible for determining what parts of $other can be re-used
-        * safely. In particular, implementations should check that the relevant configuration of
-        * $other is the same as in $this before re-using resources from $other.
-        *
-        * @note Implementations must take care to detach any re-used resources from the original
-        * service instance. If $other is destroyed later, resources that are now used by the
-        * new service instance must not be affected.
-        *
-        * @note If $other is a DestructibleService, implementations should make sure that $other
-        * is in destroyed state after salvage finished. This may be done by calling $other->destroy()
-        * after carefully detaching all relevant resources.
-        *
-        * @param SalvageableService $other The object to salvage state from. $other must have the
-        * exact same type as $this.
-        */
-       public function salvage( SalvageableService $other );
-
-}
diff --git a/includes/services/ServiceAlreadyDefinedException.php b/includes/services/ServiceAlreadyDefinedException.php
deleted file mode 100644 (file)
index c6344d3..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when a service was already defined, but the
- * caller expected it to not exist.
- *
- * 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
- */
-
-/**
- * Exception thrown when a service was already defined, but the
- * caller expected it to not exist.
- */
-class ServiceAlreadyDefinedException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "Service already defined: $serviceName", 0, $previous );
-       }
-
-}
diff --git a/includes/services/ServiceContainer.php b/includes/services/ServiceContainer.php
deleted file mode 100644 (file)
index d934d27..0000000
+++ /dev/null
@@ -1,465 +0,0 @@
-<?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 implements DestructibleService {
-
-       /**
-        * @var object[]
-        */
-       private $services = [];
-
-       /**
-        * @var callable[]
-        */
-       private $serviceInstantiators = [];
-
-       /**
-        * @var callable[][]
-        */
-       private $serviceManipulators = [];
-
-       /**
-        * @var bool[] disabled status, per service name
-        */
-       private $disabled = [];
-
-       /**
-        * @var array
-        */
-       private $extraInstantiationParams;
-
-       /**
-        * @var bool
-        */
-       private $destroyed = false;
-
-       /**
-        * @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;
-       }
-
-       /**
-        * Destroys all contained service instances that implement the DestructibleService
-        * interface. This will render all services obtained from this MediaWikiServices
-        * instance unusable. In particular, this will disable access to the storage backend
-        * via any of these services. Any future call to getService() will throw an exception.
-        *
-        * @see resetGlobalInstance()
-        */
-       public function destroy() {
-               foreach ( $this->getServiceNames() as $name ) {
-                       $service = $this->peekService( $name );
-                       if ( $service !== null && $service instanceof DestructibleService ) {
-                               $service->destroy();
-                       }
-               }
-
-               // Break circular references due to the $this reference in closures, by
-               // erasing the instantiator array. This allows the ServiceContainer to
-               // be deleted when it goes out of scope.
-               $this->serviceInstantiators = [];
-               // Also remove the services themselves, to avoid confusion.
-               $this->services = [];
-               $this->destroyed = true;
-       }
-
-       /**
-        * @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 );
-               }
-       }
-
-       /**
-        * Imports all wiring defined in $container. Wiring defined in $container
-        * will override any wiring already defined locally. However, already
-        * existing service instances will be preserved.
-        *
-        * @since 1.28
-        *
-        * @param ServiceContainer $container
-        * @param string[] $skip A list of service names to skip during import
-        */
-       public function importWiring( ServiceContainer $container, $skip = [] ) {
-               $newInstantiators = array_diff_key(
-                       $container->serviceInstantiators,
-                       array_flip( $skip )
-               );
-
-               $this->serviceInstantiators = array_merge(
-                       $this->serviceInstantiators,
-                       $newInstantiators
-               );
-
-               $newManipulators = array_diff(
-                       array_keys( $container->serviceManipulators ),
-                       $skip
-               );
-
-               foreach ( $newManipulators as $name ) {
-                       if ( isset( $this->serviceManipulators[$name] ) ) {
-                               $this->serviceManipulators[$name] = array_merge(
-                                       $this->serviceManipulators[$name],
-                                       $container->serviceManipulators[$name]
-                               );
-                       } else {
-                               $this->serviceManipulators[$name] = $container->serviceManipulators[$name];
-                       }
-               }
-       }
-
-       /**
-        * 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] );
-       }
-
-       /**
-        * Returns the service instance for $name only if that service has already been instantiated.
-        * This is intended for situations where services get destroyed/cleaned up, so we can
-        * avoid creating a service just to destroy it again.
-        *
-        * @note This is intended for internal use and for test fixtures.
-        * Application logic should use getService() instead.
-        *
-        * @see getService().
-        *
-        * @param string $name
-        *
-        * @return object|null The service instance, or null if the service has not yet been instantiated.
-        * @throws RuntimeException if $name does not refer to a known service.
-        */
-       public function peekService( $name ) {
-               if ( !$this->hasService( $name ) ) {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               return $this->services[$name] ?? null;
-       }
-
-       /**
-        * @return string[]
-        */
-       public function getServiceNames() {
-               return array_keys( $this->serviceInstantiators );
-       }
-
-       /**
-        * Define a new service. The service must not be known already.
-        *
-        * @see getService().
-        * @see redefineService().
-        *
-        * @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 ServiceAlreadyDefinedException( $name );
-               }
-
-               $this->serviceInstantiators[$name] = $instantiator;
-       }
-
-       /**
-        * Replace an already defined service.
-        *
-        * @see defineService().
-        *
-        * @note This will fail if the service was already instantiated. If the service was previously
-        * disabled, it will be re-enabled by this call. Any manipulators registered for the service
-        * will remain in place.
-        *
-        * @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 NoSuchServiceException if $name is not a known service.
-        * @throws CannotReplaceActiveServiceException if the service was already instantiated.
-        */
-       public function redefineService( $name, callable $instantiator ) {
-               Assert::parameterType( 'string', $name, '$name' );
-
-               if ( !$this->hasService( $name ) ) {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               if ( isset( $this->services[$name] ) ) {
-                       throw new CannotReplaceActiveServiceException( $name );
-               }
-
-               $this->serviceInstantiators[$name] = $instantiator;
-               unset( $this->disabled[$name] );
-       }
-
-       /**
-        * Add a service manipulator callback for the given service.
-        * This method may be used by extensions that need to wrap, replace, or re-configure a
-        * service. It would typically be called from a MediaWikiServices hook handler.
-        *
-        * The manipulator callback is called just after the service is instantiated.
-        * It can call methods on the service to change configuration, or wrap or otherwise
-        * replace it.
-        *
-        * @see defineService().
-        * @see redefineService().
-        *
-        * @note This will fail if the service was already instantiated.
-        *
-        * @since 1.32
-        *
-        * @param string $name The name of the service to manipulate.
-        * @param callable $manipulator Callback function that manipulates, wraps or replaces a
-        * service instance. The callback receives the new service instance and this the
-        * ServiceContainer as parameters, as well as any extra instantiation parameters specified
-        * when constructing this ServiceContainer. If the callback returns a value, that
-        * value replaces the original service instance.
-        *
-        * @throws NoSuchServiceException if $name is not a known service.
-        * @throws CannotReplaceActiveServiceException if the service was already instantiated.
-        */
-       public function addServiceManipulator( $name, callable $manipulator ) {
-               Assert::parameterType( 'string', $name, '$name' );
-
-               if ( !$this->hasService( $name ) ) {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               if ( isset( $this->services[$name] ) ) {
-                       throw new CannotReplaceActiveServiceException( $name );
-               }
-
-               $this->serviceManipulators[$name][] = $manipulator;
-       }
-
-       /**
-        * Disables a service.
-        *
-        * @note Attempts to call getService() for a disabled service will result
-        * in a DisabledServiceException. Calling peekService for a disabled service will
-        * return null. Disabled services are listed by getServiceNames(). A disabled service
-        * can be enabled again using redefineService().
-        *
-        * @note If the service was already active (that is, instantiated) when getting disabled,
-        * and the service instance implements DestructibleService, destroy() is called on the
-        * service instance.
-        *
-        * @see redefineService()
-        * @see resetService()
-        *
-        * @param string $name The name of the service to disable.
-        *
-        * @throws RuntimeException if $name is not a known service.
-        */
-       public function disableService( $name ) {
-               $this->resetService( $name );
-
-               $this->disabled[$name] = true;
-       }
-
-       /**
-        * Resets a service by dropping the service instance.
-        * If the service instances implements DestructibleService, destroy()
-        * is called on the service instance.
-        *
-        * @warning This is generally unsafe! Other services may still retain references
-        * to the stale service instance, leading to failures and inconsistencies. Subclasses
-        * may use this method to reset specific services under specific instances, but
-        * it should not be exposed to application logic.
-        *
-        * @note This is declared final so subclasses can not interfere with the expectations
-        * disableService() has when calling resetService().
-        *
-        * @see redefineService()
-        * @see disableService().
-        *
-        * @param string $name The name of the service to reset.
-        * @param bool $destroy Whether the service instance should be destroyed if it exists.
-        *        When set to false, any existing service instance will effectively be detached
-        *        from the container.
-        *
-        * @throws RuntimeException if $name is not a known service.
-        */
-       final protected function resetService( $name, $destroy = true ) {
-               Assert::parameterType( 'string', $name, '$name' );
-
-               $instance = $this->peekService( $name );
-
-               if ( $destroy && $instance instanceof DestructibleService ) {
-                       $instance->destroy();
-               }
-
-               unset( $this->services[$name] );
-               unset( $this->disabled[$name] );
-       }
-
-       /**
-        * 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 NoSuchServiceException if $name is not a known service.
-        * @throws ContainerDisabledException if this container has already been destroyed.
-        * @throws ServiceDisabledException if the requested service has been disabled.
-        *
-        * @return object The service instance
-        */
-       public function getService( $name ) {
-               if ( $this->destroyed ) {
-                       throw new ContainerDisabledException();
-               }
-
-               if ( isset( $this->disabled[$name] ) ) {
-                       throw new ServiceDisabledException( $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 = ( $this->serviceInstantiators[$name] )(
-                               $this,
-                               ...$this->extraInstantiationParams
-                       );
-
-                       if ( isset( $this->serviceManipulators[$name] ) ) {
-                               foreach ( $this->serviceManipulators[$name] as $callback ) {
-                                       $ret = call_user_func_array(
-                                               $callback,
-                                               array_merge( [ $service, $this ], $this->extraInstantiationParams )
-                                       );
-
-                                       // If the manipulator callback returns an object, that object replaces
-                                       // the original service instance. This allows the manipulator to wrap
-                                       // or fully replace the service.
-                                       if ( $ret !== null ) {
-                                               $service = $ret;
-                                       }
-                               }
-                       }
-
-                       // NOTE: when adding more wiring logic here, make sure importWiring() is kept in sync!
-               } else {
-                       throw new NoSuchServiceException( $name );
-               }
-
-               return $service;
-       }
-
-       /**
-        * @param string $name
-        * @return bool Whether the service is disabled
-        * @since 1.28
-        */
-       public function isServiceDisabled( $name ) {
-               return isset( $this->disabled[$name] );
-       }
-}
diff --git a/includes/services/ServiceDisabledException.php b/includes/services/ServiceDisabledException.php
deleted file mode 100644 (file)
index ae15b7c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-namespace MediaWiki\Services;
-
-use Exception;
-use RuntimeException;
-
-/**
- * Exception thrown when trying to access a disabled service.
- *
- * 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
- */
-
-/**
- * Exception thrown when trying to access a disabled service.
- */
-class ServiceDisabledException extends RuntimeException {
-
-       /**
-        * @param string $serviceName
-        * @param Exception|null $previous
-        */
-       public function __construct( $serviceName, Exception $previous = null ) {
-               parent::__construct( "Service disabled: $serviceName", 0, $previous );
-       }
-
-}
index 08ff8f0..e31bc06 100644 (file)
@@ -519,7 +519,7 @@ abstract class Skin extends ContextSource {
                $out = $this->getOutput();
                $allCats = $out->getCategoryLinks();
 
-               if ( !count( $allCats ) ) {
+               if ( $allCats === [] ) {
                        return '';
                }
 
index 70b4207..b05c81a 100644 (file)
@@ -431,7 +431,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
         * Attempts to clean up broken items
         */
        private function cleanupWatchlist() {
-               if ( !count( $this->badItems ) ) {
+               if ( $this->badItems === [] ) {
                        return; // nothing to do
                }
 
index 1a04eec..ba16baf 100644 (file)
@@ -62,7 +62,7 @@ class SpecialListGrants extends SpecialPage {
                                        '<span class="mw-listgrants-right-name">' . $permission . '</span>'
                                )->parse();
                        }
-                       if ( !count( $descs ) ) {
+                       if ( $descs === [] ) {
                                $grantCellHtml = '';
                        } else {
                                sort( $descs );
index 0a3a679..573dcb5 100644 (file)
@@ -151,7 +151,7 @@ class SpecialPasswordPolicies extends SpecialPage {
                                '<span class="mw-passwordpolicies-policy-name">' . $gp . '</span>'
                        )->parse();
                }
-               if ( !count( $ret ) ) {
+               if ( $ret === [] ) {
                        return '';
                } else {
                        return '<ul><li>' . implode( "</li>\n<li>", $ret ) . '</li></ul>';
index 60e797e..b566305 100644 (file)
@@ -709,7 +709,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                $categories = array_map( 'trim', explode( '|', $opts['categories'] ) );
 
-               if ( !count( $categories ) ) {
+               if ( $categories === [] ) {
                        return;
                }
 
@@ -744,7 +744,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                }
 
                # Shortcut?
-               if ( !count( $articles ) || !count( $cats ) ) {
+               if ( $articles === [] || $cats === [] ) {
                        return;
                }
 
index d904ad1..ec6c5b9 100644 (file)
@@ -212,13 +212,13 @@ class SpecialSearch extends SpecialPage {
 
                # Extract manually requested namespaces
                $nslist = $this->powerSearch( $request );
-               if ( !count( $nslist ) ) {
+               if ( $nslist === [] ) {
                        # Fallback to user preference
                        $nslist = $this->searchConfig->userNamespaces( $user );
                }
 
                $profile = null;
-               if ( !count( $nslist ) ) {
+               if ( $nslist === [] ) {
                        $profile = 'default';
                }
 
index 585a7cd..9de31da 100644 (file)
@@ -55,7 +55,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
                $pages = MediaWikiServices::getInstance()->getSpecialPageFactory()->
                        getUsablePages( $this->getUser() );
 
-               if ( !count( $pages ) ) {
+               if ( $pages === [] ) {
                        # Yeah, that was pointless. Thanks for coming.
                        return false;
                }
index 3ee7cea..4a586b7 100644 (file)
@@ -94,7 +94,7 @@ class SpecialTrackingCategories extends SpecialPage {
                        }
 
                        # Extra message, when no category was found
-                       if ( !count( $allMsgs ) ) {
+                       if ( $allMsgs === [] ) {
                                $allMsgs[] = $this->msg( 'trackingcategories-disabled' )->parse();
                        }
 
index a0b14d2..205ffbf 100644 (file)
@@ -199,7 +199,9 @@ class BlockListPager extends TablePager {
 
                                if ( !$row->ipb_sitewide && $this->restrictions ) {
                                        $list = $this->getRestrictionListHTML( $this->restrictions, $row );
-                                       $properties[] = htmlspecialchars( $msg['blocklist-editing'] ) . $list;
+                                       if ( $list ) {
+                                               $properties[] = htmlspecialchars( $msg['blocklist-editing'] ) . $list;
+                                       }
                                }
 
                                if ( $row->ipb_anon_only ) {
index 65fc4b4..79889ae 100644 (file)
@@ -1579,7 +1579,7 @@ class User implements IDBAccessObject, UserIdentity {
 
                if ( is_array( $data ) ) {
                        if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
-                               if ( !count( $data['user_groups'] ) ) {
+                               if ( $data['user_groups'] === [] ) {
                                        $this->mGroupMemberships = [];
                                } else {
                                        $firstGroup = reset( $data['user_groups'] );
@@ -1645,7 +1645,7 @@ class User implements IDBAccessObject, UserIdentity {
                }
 
                $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
-               if ( !count( $toPromote ) ) {
+               if ( $toPromote === [] ) {
                        return [];
                }
 
index cdec27d..0240f85 100644 (file)
@@ -3938,6 +3938,7 @@ public static $zh2Hant = [
 '余威德' => '余威德',
 '余子明' => '余子明',
 '余思敏' => '余思敏',
+'余杰' => '余杰',
 '佛历' => '佛曆',
 '佛罗棱萨' => '佛羅稜薩',
 '佛钟' => '佛鐘',
@@ -5111,6 +5112,7 @@ public static $zh2Hant = [
 '年历次' => '年歷次',
 '年谷' => '年穀',
 '年里' => '年裡',
+'年里约' => '年里約',
 '年鉴' => '年鑑',
 '并州' => '并州',
 '并日而食' => '并日而食',
@@ -5500,6 +5502,7 @@ public static $zh2Hant = [
 '恒生' => '恒生',
 '恒隆' => '恒隆',
 '恕乏价催' => '恕乏价催',
+'恢复' => '恢復',
 '息交绝游' => '息交絕遊',
 '息谷' => '息穀',
 '悒郁' => '悒鬱',
@@ -7859,6 +7862,7 @@ public static $zh2Hant = [
 '复试' => '複試',
 '复课' => '複課',
 '复议' => '複議',
+'复读机' => '複讀機',
 '复变函数' => '複變函數',
 '复赛' => '複賽',
 '复辅音' => '複輔音',
@@ -11959,7 +11963,6 @@ public static $zh2Hans = [
 '趲' => '趱',
 '跡' => '迹',
 '跥' => '跺',
-'跴' => '踩',
 '踁' => '胫',
 '踐' => '践',
 '踫' => '碰',
@@ -13661,6 +13664,7 @@ public static $zh2Hans = [
 '釐正' => '厘正',
 '釐毫' => '厘毫',
 '釐清' => '厘清',
+'釐米' => '厘米',
 '釐訂' => '厘订',
 '釐革' => '厘革',
 '原著' => '原著',
@@ -14428,6 +14432,8 @@ public static $zh2TW = [
 '克林顿' => '柯林頓',
 '戴卓爾' => '柴契爾',
 '撒切尔' => '柴契爾',
+'格林納丁斯' => '格瑞那丁',
+'格林纳丁斯' => '格瑞那丁',
 '格林納達' => '格瑞那達',
 '格林纳达' => '格瑞那達',
 '桃金娘' => '桃金孃',
@@ -14448,6 +14454,7 @@ public static $zh2TW = [
 '標準杆' => '標準桿',
 '毛里求斯' => '模里西斯',
 '毛里裘斯' => '模里西斯',
+'樸茨茅夫' => '樸茨茅斯',
 '機械人' => '機器人',
 '概率' => '機率',
 '電單車' => '機車',
@@ -14588,6 +14595,7 @@ public static $zh2TW = [
 '昂山素季' => '翁山蘇姬',
 '圣基茨和尼维斯' => '聖克里斯多福及尼維斯',
 '聖吉斯納域斯' => '聖克里斯多福及尼維斯',
+'聖佐治' => '聖喬治',
 '圣文森特和格林纳丁斯' => '聖文森及格瑞那丁',
 '聖文森特和格林納丁斯' => '聖文森及格瑞那丁',
 '圣赫勒拿' => '聖赫倫那',
@@ -14772,6 +14780,8 @@ public static $zh2TW = [
 '链接' => '連結',
 '連結他' => '連結他',
 '进制' => '進位',
+'道琼斯' => '道瓊',
+'道瓊斯' => '道瓊',
 '达·芬奇' => '達·文西',
 '达芬奇' => '達文西',
 '溫納圖萬' => '那杜',
@@ -14855,7 +14865,6 @@ public static $zh2TW = [
 '高清电视' => '高畫質電視',
 '斗着' => '鬥著',
 '魯賓斯·巴里切羅' => '魯本·巴瑞切羅',
-'咪高峰' => '麥克風',
 '迈克尔' => '麥可',
 '麦克尔' => '麥可',
 '迈凯轮' => '麥拿輪',
@@ -15487,8 +15496,6 @@ public static $zh2HK = [
 '味著述' => '味著述',
 '味著錄' => '味著錄',
 '咖哩' => '咖喱',
-'麥克風' => '咪高峰',
-'麦克风' => '咪高峰',
 '咬著' => '咬着',
 '哥特式' => '哥德式',
 '哥斯大黎加' => '哥斯達黎加',
@@ -16363,6 +16370,7 @@ public static $zh2HK = [
 '奥黛丽·赫本' => '柯德莉·夏萍',
 '奧黛麗·赫本' => '柯德莉·夏萍',
 '哥廷根' => '格丁根',
+'格瑞那丁' => '格林納丁斯',
 '格瑞那達' => '格林納達',
 '格莱美奖' => '格林美獎',
 '葛萊美獎' => '格林美獎',
@@ -16397,6 +16405,8 @@ public static $zh2HK = [
 '標志著' => '標志着',
 '寶獅' => '標致',
 '標誌著' => '標誌着',
+'朴茨茅斯' => '樸茨茅夫',
+'樸茨茅斯' => '樸茨茅夫',
 '树林里' => '樹林裏',
 '工具機' => '機床',
 '机器人' => '機械人',
@@ -17069,6 +17079,8 @@ public static $zh2HK = [
 '考著者' => '考著者',
 '考著述' => '考著述',
 '考著錄' => '考著錄',
+'圣乔治' => '聖佐治',
+'聖喬治' => '聖佐治',
 '圣基茨和尼维斯' => '聖吉斯納域斯',
 '聖克里斯多福及尼維斯' => '聖吉斯納域斯',
 '聖文森及格瑞那丁' => '聖文森特和格林納丁斯',
@@ -17568,6 +17580,8 @@ public static $zh2HK = [
 '遍佈著' => '遍佈着',
 '遍布著' => '遍佈着',
 '過著' => '過着',
+'道瓊' => '道瓊斯',
+'道瓊斯' => '道瓊斯',
 '达·芬奇' => '達·文西',
 '达芬奇' => '達文西',
 '達著' => '達着',
@@ -18578,6 +18592,7 @@ public static $zh2CN = [
 '土魯斯' => '图卢兹',
 '吐瓦魯' => '图瓦卢',
 '原子筆' => '圆珠笔',
+'聖佐治' => '圣乔治',
 '汕埠' => '圣佩德罗苏拉',
 '聖露西亞' => '圣卢西亚',
 '聖克里斯多福及尼維斯' => '圣基茨和尼维斯',
@@ -19308,6 +19323,7 @@ public static $zh2CN = [
 '本著者' => '本著者',
 '本著述' => '本著述',
 '本帳' => '本账',
+'樸茨茅夫' => '朴茨茅斯',
 '機械人' => '机器人',
 '工具機' => '机床',
 '殺著' => '杀着',
@@ -19349,6 +19365,7 @@ public static $zh2CN = [
 '查維茲' => '查韦斯',
 '標志著' => '标志着',
 '標誌著' => '标志着',
+'格瑞那丁' => '格林纳丁斯',
 '格瑞那達' => '格林纳达',
 '格林美獎' => '格莱美奖',
 '葛萊美獎' => '格莱美奖',
@@ -19596,6 +19613,7 @@ public static $zh2CN = [
 '畫著稱' => '画著称',
 '畫著者' => '画著者',
 '介面' => '界面',
+'留尼旺' => '留尼汪',
 '留著' => '留着',
 '留著書' => '留着书',
 '留著作' => '留著作',
@@ -20397,6 +20415,8 @@ public static $zh2CN = [
 '遇著述' => '遇著述',
 '遍佈著' => '遍布着',
 '遍布著' => '遍布着',
+'道瓊' => '道琼斯',
+'道瓊斯' => '道琼斯',
 '部份' => '部分',
 '配合著' => '配合着',
 '配合著名' => '配合著名',
@@ -20641,7 +20661,6 @@ public static $zh2CN = [
 '高著述' => '高著述',
 '魚雷' => '鱼雷',
 '鱼雷' => '鱼雷',
-'咪高峰' => '麦克风',
 '黏著' => '黏着',
 '黏著書' => '黏著书',
 '黏著作' => '黏著作',
index d7a7cc8..0971005 100644 (file)
@@ -23,8 +23,8 @@ $namespaceNames = [
        NS_FILE_TALK        => 'फायल_चर्चा',
        NS_MEDIAWIKI        => 'मिडियाविकी',
        NS_MEDIAWIKI_TALK   => 'मिडियाविकी_चर्चा',
-       NS_TEMPLATE         => 'पà¥\8dरारà¥\82प',
-       NS_TEMPLATE_TALK    => 'पà¥\8dरारà¥\82प_चर्चा',
+       NS_TEMPLATE         => 'साà¤\82à¤\9aà¥\8b',
+       NS_TEMPLATE_TALK    => 'साà¤\82à¤\9aà¥\8b_चर्चा',
        NS_HELP             => 'आदार',
        NS_HELP_TALK        => 'आदार_चर्चा',
        NS_CATEGORY         => 'वर्ग',
@@ -38,5 +38,7 @@ $namespaceAliases = [
        'श्रेणी_चर्चा' => NS_CATEGORY_TALK,
        'मिडिया' => NS_MEDIA,
        'उपेगकर्तो' => NS_USER,
-       'उपेगकर्तो_चर्चा' => NS_USER_TALK
+       'उपेगकर्तो_चर्चा' => NS_USER_TALK,
+       'प्रारूप' => NS_TEMPLATE,
+       'प्रारूप_चर्चा' => NS_TEMPLATE_TALK,
 ];
index 31abd35..6c34e0b 100644 (file)
@@ -2650,7 +2650,6 @@ A型肝炎        甲型肝炎
 數碼訊號   数字信号
 數位音樂   数字音乐
 數位化      数字化
-咪高峰      麦克风
 幫浦 泵
 電單車      摩托车
 演化論      进化论
@@ -2710,3 +2709,9 @@ A型肝炎        甲型肝炎
 皮特肯      皮特凯恩
 安地卡      安提瓜
 撒拉威阿拉伯     阿拉伯撒哈拉
+樸茨茅夫   朴茨茅斯
+留尼旺      留尼汪
+道瓊斯      道琼斯
+道瓊 道琼斯
+聖佐治      圣乔治
+格瑞那丁   格林纳丁斯
index 1ad4540..93acb33 100644 (file)
@@ -3023,8 +3023,6 @@ IP地址  IP位址
 數位音樂   數碼音樂
 数字化      數碼化
 數位化      數碼化
-麥克風      咪高峰
-麦克风      咪高峰
 幫浦 泵
 朝鲜战争   韓戰
 万历朝鲜战争     萬曆朝鮮戰爭
@@ -3073,3 +3071,10 @@ IP地址 IP位址
 皮特凯恩   皮特肯
 安地卡      安提瓜
 撒拉威阿拉伯     阿拉伯撒哈拉
+朴茨茅斯   樸茨茅夫
+樸茨茅斯   樸茨茅夫
+道瓊斯      道瓊斯
+道瓊 道瓊斯
+圣乔治      聖佐治
+聖喬治      聖佐治
+格瑞那丁   格林納丁斯
index 5ad8eeb..aacec98 100644 (file)
 釐正 厘正
 毫釐 毫厘
 釐毫 厘毫
+釐米 厘米
 剖釐 剖厘
 一釐 一厘
 昇平 升平
index 28055e1..dd8e5d0 100644 (file)
@@ -768,7 +768,6 @@ IP地址    IP位址
 網絡遊戲   網路遊戲
 电脑网络   電腦網路
 電腦網絡   電腦網路
-咪高峰      麥克風
 電單車      機車
 搜索引擎   搜尋引擎
 福尔马林   福馬林
@@ -815,3 +814,10 @@ IP地址   IP位址
 皮特凯恩   皮特肯
 安提瓜      安地卡
 阿拉伯撒哈拉     撒拉威阿拉伯
+樸茨茅夫   樸茨茅斯
+留尼汪      留尼旺
+道琼斯      道瓊
+道瓊斯      道瓊
+聖佐治      聖喬治
+格林纳丁斯        格瑞那丁
+格林納丁斯        格瑞那丁
index 5bfe18f..a3565b8 100644 (file)
@@ -22,6 +22,7 @@
 陳杰 陳杰
 黃杰 黃杰
 謝杰 謝杰
+余杰 余杰
 博杰普爾   博杰普爾
 寶曆 寶曆
 涂謹申      涂謹申
index 7206924..8d09901 100644 (file)
@@ -690,7 +690,6 @@ U+08D11贑|U+08D63赣|
 U+08D1C贜|U+08D43赃|
 U+08D82趂|U+08D81趁|
 U+08DE5跥|U+08DFA跺|
-U+08DF4跴|U+08E29踩|
 U+08E01踁|U+080EB胫|
 U+08E2B踫|U+078B0碰|
 U+08E30踰|U+0903E逾|
index 327f8f3..24d8a42 100644 (file)
 張三丰
 復始
 往復式
+恢復 #分詞用
 複分析
 複輔音
 複元音
 複合 #因複合詞頻遠高於復合
 複方
 複穗
+複讀機
 撥穀
 扁擬穀盜蟲
 不穀
 孛里海
 布里海
 公里海
+年里約 #里约奧運
 地圖裡
 版圖裡
 配圖裡
index cd50369..b6d9b48 100644 (file)
                                isIpRange = isIp && blocktarget.match( /\/\d+$/ ),
                                isNonEmptyIp = isIp && !isEmpty,
                                expiryValue = expiryWidget.getValue(),
-                               // infinityValues  are the values the SpecialBlock class accepts as infinity (sf. wfIsInfinity)
+                               // infinityValues are the values the SpecialBlock class accepts as infinity (sf. wfIsInfinity)
                                infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ],
                                isIndefinite = infinityValues.indexOf( expiryValue ) !== -1,
                                editingRestrictionValue = editingRestrictionWidget ? editingRestrictionWidget.getValue() : undefined,
-                               editingIsSelected = editingWidget ? editingWidget.isSelected() : undefined;
+                               editingIsSelected = editingWidget ? editingWidget.isSelected() : false;
 
                        if ( enableAutoblockField ) {
-                               enableAutoblockField.toggle( !( isNonEmptyIp ) );
+                               enableAutoblockField.toggle( !isNonEmptyIp );
                        }
                        if ( hideUserField ) {
-                               hideUserField.toggle( !( isNonEmptyIp || !isIndefinite ) );
+                               hideUserField.toggle( !isNonEmptyIp && isIndefinite );
                        }
                        if ( anonOnlyField ) {
-                               anonOnlyField.toggle( !( !isIp && !isEmpty ) );
+                               anonOnlyField.toggle( isIp || isEmpty );
                        }
                        if ( watchUserField ) {
-                               watchUserField.toggle( !( isIpRange && !isEmpty ) );
+                               watchUserField.toggle( !isIpRange || isEmpty );
                        }
                        if ( pageRestrictionsWidget ) {
                                editingRestrictionWidget.setDisabled( !editingIsSelected );
                                // TODO: (T210475) this option is disabled for partial blocks unless
                                // a namespace restriction for User_talk namespace is in place.
                                // This needs to be updated once Namespace restrictions is available
-                               if ( editingRestrictionValue === 'partial' && editingIsSelected ) {
-                                       preventTalkPageEdit.setDisabled( true );
-                               } else {
-                                       preventTalkPageEdit.setDisabled( false );
-                               }
+                               preventTalkPageEdit.setDisabled( editingRestrictionValue === 'partial' && editingIsSelected );
                        }
 
                }
index 6ad7917..cedb19d 100644 (file)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+    <title>remove</title>
     <g id="remove">
         <path id="trash-can" d="M12 10h-1v6h1v-6zm-2 0h-1v6h1v-6zm4 0h-1v6h1v-6zm0-4v-1h-5v1h-3v3h1v7.966l1 1.031v-.074.077h6.984l.016-.018v.015l1-1.031v-7.966h1v-3h-3zm1 11h-7v-8h7v8zm1-9h-9v-1h9v1z"/>
     </g>
index bcbe871..641bb78 100644 (file)
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="red">
-    <g xmlns:default="http://www.w3.org/2000/svg" id="remove">
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>remove</title><g fill="red">
+    
+    <g id="remove">
         <path id="trash-can" d="M12 10h-1v6h1v-6zm-2 0h-1v6h1v-6zm4 0h-1v6h1v-6zm0-4v-1h-5v1h-3v3h1v7.966l1 1.031v-.074.077h6.984l.016-.018v.015l1-1.031v-7.966h1v-3h-3zm1 11h-7v-8h7v8zm1-9h-9v-1h9v1z"/>
     </g>
 </g></svg>
index ab9a472..1cd40ed 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Services\DestructibleService;
-use MediaWiki\Services\SalvageableService;
-use MediaWiki\Services\ServiceDisabledException;
+use Wikimedia\Services\DestructibleService;
+use Wikimedia\Services\SalvageableService;
+use Wikimedia\Services\ServiceDisabledException;
 
 /**
  * @covers MediaWiki\MediaWikiServices
@@ -219,7 +219,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                        'Test',
                        function () use ( &$serviceCounter ) {
                                $serviceCounter++;
-                               $service = $this->createMock( MediaWiki\Services\DestructibleService::class );
+                               $service = $this->createMock( Wikimedia\Services\DestructibleService::class );
                                $service->expects( $this->once() )->method( 'destroy' );
                                return $service;
                        }
@@ -248,7 +248,7 @@ class MediaWikiServicesTest extends MediaWikiTestCase {
                $services->defineService(
                        'Test',
                        function () {
-                               $service = $this->createMock( MediaWiki\Services\DestructibleService::class );
+                               $service = $this->createMock( Wikimedia\Services\DestructibleService::class );
                                $service->expects( $this->never() )->method( 'destroy' );
                                return $service;
                        }
index 43edf60..a8ea3f0 100644 (file)
@@ -200,7 +200,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                $content = new WikitextContent( "hello world" );
 
                $text = ContentHandler::getContentText( $content );
-               $this->assertEquals( $content->getNativeData(), $text );
+               $this->assertEquals( $content->getText(), $text );
        }
 
        /**
@@ -242,9 +242,9 @@ class ContentHandlerTest extends MediaWikiTestCase {
 
        public static function dataMakeContent() {
                return [
-                       [ 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ],
-                       [ 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ],
-                       [ serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", 'hallo', false ],
+                       [ 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, false ],
+                       [ 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, false ],
+                       [ serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", false ],
 
                        [
                                'hallo',
@@ -252,7 +252,6 @@ class ContentHandlerTest extends MediaWikiTestCase {
                                null,
                                CONTENT_FORMAT_WIKITEXT,
                                CONTENT_MODEL_WIKITEXT,
-                               'hallo',
                                false
                        ],
                        [
@@ -261,19 +260,17 @@ class ContentHandlerTest extends MediaWikiTestCase {
                                null,
                                CONTENT_FORMAT_JAVASCRIPT,
                                CONTENT_MODEL_JAVASCRIPT,
-                               'hallo',
                                false
                        ],
-                       [ serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", 'hallo', false ],
+                       [ serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", false ],
 
-                       [ 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ],
+                       [ 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, false ],
                        [
                                'hallo',
                                'MediaWiki:Test.js',
                                CONTENT_MODEL_CSS,
                                null,
                                CONTENT_MODEL_CSS,
-                               'hallo',
                                false
                        ],
                        [
@@ -282,13 +279,12 @@ class ContentHandlerTest extends MediaWikiTestCase {
                                CONTENT_MODEL_CSS,
                                null,
                                CONTENT_MODEL_CSS,
-                               serialize( 'hallo' ),
                                false
                        ],
 
-                       [ 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ],
-                       [ 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ],
-                       [ 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ],
+                       [ 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, true ],
+                       [ 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, true ],
+                       [ 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, true ],
                ];
        }
 
@@ -297,7 +293,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
         * @covers ContentHandler::makeContent
         */
        public function testMakeContent( $data, $title, $modelId, $format,
-               $expectedModelId, $expectedNativeData, $shouldFail
+               $expectedModelId, $shouldFail
        ) {
                $title = Title::newFromText( $title );
                MediaWikiServices::getInstance()->getLinkCache()->addBadLinkObj( $title );
@@ -309,7 +305,7 @@ class ContentHandlerTest extends MediaWikiTestCase {
                        }
 
                        $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' );
-                       $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' );
+                       $this->assertEquals( $data, $content->serialize(), 'bad serialized data' );
                } catch ( MWException $ex ) {
                        if ( !$shouldFail ) {
                                $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() );
index 2c61b7d..a4dd1fc 100644 (file)
@@ -234,7 +234,7 @@ class JavaScriptContentTest extends TextContentTest {
                $content = new JavaScriptContent( $oldText );
                $newContent = $content->updateRedirect( $target );
 
-               $this->assertEquals( $expectedText, $newContent->getNativeData() );
+               $this->assertEquals( $expectedText, $newContent->getText() );
        }
 
        public static function provideUpdateRedirect() {
diff --git a/tests/phpunit/includes/content/MessageContentTest.php b/tests/phpunit/includes/content/MessageContentTest.php
new file mode 100644 (file)
index 0000000..60f68e7
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class MessageContentTest extends MediaWikiLangTestCase {
+
+       public function testGetHtml() {
+               $msg = new Message( 'about' );
+               $cnt = new MessageContent( $msg );
+
+               $this->assertSame( $msg->parse(), $cnt->getHtml() );
+       }
+
+       public function testGetWikitext() {
+               $msg = new Message( 'about' );
+               $cnt = new MessageContent( $msg );
+
+               $this->assertSame( $msg->text(), $cnt->getWikitext() );
+       }
+
+       public function testGetMessage() {
+               $msg = new Message( 'about' );
+               $cnt = new MessageContent( $msg );
+
+               $this->assertEquals( $msg, $cnt->getMessage() );
+       }
+
+       public function testGetParserOutput() {
+               $msg = new Message( 'about' );
+               $cnt = new MessageContent( $msg );
+
+               $title = Title::makeTitle( NS_MEDIAWIKI, 'about' );
+
+               $this->assertSame( $msg->parse(), $cnt->getParserOutput( $title )->getText() );
+       }
+
+       public function testSerialize() {
+               $msg = new Message( 'about' );
+               $cnt = new MessageContent( $msg );
+
+               $this->assertSame( $msg->plain(), $cnt->serialize() );
+       }
+
+       public function testEquals() {
+               $msg1 = new Message( 'about' );
+               $cnt1 = new MessageContent( $msg1 );
+
+               $msg2 = new Message( 'about' );
+               $cnt2 = new MessageContent( $msg2 );
+
+               $msg3 = new Message( 'faq' );
+               $cnt3 = new MessageContent( $msg3 );
+               $cnt4 = new WikitextContent( $msg3->plain() );
+
+               $this->assertTrue( $cnt1->equals( $cnt2 ) );
+               $this->assertFalse( $cnt1->equals( $cnt3 ) );
+               $this->assertFalse( $cnt1->equals( $cnt4 ) );
+       }
+}
index 4f04e64..8e537d6 100644 (file)
@@ -45,6 +45,10 @@ class TextContentTest extends MediaWikiLangTestCase {
                parent::tearDown();
        }
 
+       /**
+        * @param string $text
+        * @return TextContent
+        */
        public function newContent( $text ) {
                return new TextContent( $text );
        }
@@ -131,7 +135,7 @@ class TextContentTest extends MediaWikiLangTestCase {
                        $options
                );
 
-               $this->assertEquals( $expected, $content->getNativeData() );
+               $this->assertEquals( $expected, $content->getText() );
        }
 
        public static function dataPreloadTransform() {
@@ -154,7 +158,7 @@ class TextContentTest extends MediaWikiLangTestCase {
                $content = $this->newContent( $text );
                $content = $content->preloadTransform( $this->context->getTitle(), $options );
 
-               $this->assertEquals( $expected, $content->getNativeData() );
+               $this->assertEquals( $expected, $content->getText() );
        }
 
        public static function dataGetRedirectTarget() {
@@ -269,7 +273,7 @@ class TextContentTest extends MediaWikiLangTestCase {
                $copy = $content->copy();
 
                $this->assertTrue( $content->equals( $copy ), 'copy must be equal to original' );
-               $this->assertEquals( 'hello world.', $copy->getNativeData() );
+               $this->assertEquals( 'hello world.', $copy->getText() );
        }
 
        /**
@@ -281,13 +285,22 @@ class TextContentTest extends MediaWikiLangTestCase {
                $this->assertEquals( 12, $content->getSize() );
        }
 
+       /**
+        * @covers TextContent::getText
+        */
+       public function testGetText() {
+               $content = $this->newContent( 'hello world.' );
+
+               $this->assertEquals( 'hello world.', $content->getText() );
+       }
+
        /**
         * @covers TextContent::getNativeData
         */
        public function testGetNativeData() {
                $content = $this->newContent( 'hello world.' );
 
-               $this->assertEquals( 'hello world.', $content->getNativeData() );
+               $this->assertEquals( 'hello world.', $content->getText() );
        }
 
        /**
@@ -438,13 +451,14 @@ class TextContentTest extends MediaWikiLangTestCase {
        public function testConvert( $text, $model, $lossy, $expectedNative ) {
                $content = $this->newContent( $text );
 
+               /** @var TextContent $converted */
                $converted = $content->convert( $model, $lossy );
 
                if ( $expectedNative === false ) {
                        $this->assertFalse( $converted, "conversion to $model was expected to fail!" );
                } else {
                        $this->assertInstanceOf( Content::class, $converted );
-                       $this->assertEquals( $expectedNative, $converted->getNativeData() );
+                       $this->assertEquals( $expectedNative, $converted->getText() );
                }
        }
 
@@ -473,4 +487,10 @@ class TextContentTest extends MediaWikiLangTestCase {
                ];
        }
 
+       public function testSerialize() {
+               $cnt = $this->newContent( 'testing text' );
+
+               $this->assertSame( 'testing text', $cnt->serialize() );
+       }
+
 }
index 31d90cb..5f78a5c 100644 (file)
@@ -44,10 +44,10 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
         */
        public function testUnserializeContent() {
                $content = $this->handler->unserializeContent( 'hello world' );
-               $this->assertEquals( 'hello world', $content->getNativeData() );
+               $this->assertEquals( 'hello world', $content->getText() );
 
                $content = $this->handler->unserializeContent( 'hello world', CONTENT_FORMAT_WIKITEXT );
-               $this->assertEquals( 'hello world', $content->getNativeData() );
+               $this->assertEquals( 'hello world', $content->getText() );
 
                try {
                        $this->handler->unserializeContent( 'hello world', 'dummy/foo' );
@@ -64,7 +64,7 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
                $content = $this->handler->makeEmptyContent();
 
                $this->assertTrue( $content->isEmpty() );
-               $this->assertEquals( '', $content->getNativeData() );
+               $this->assertEquals( '', $content->getText() );
        }
 
        public static function dataIsSupportedFormat() {
@@ -172,7 +172,7 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
 
                $merged = $this->handler->merge3( $oldContent, $myContent, $yourContent );
 
-               $this->assertEquals( $expected, $merged ? $merged->getNativeData() : $merged );
+               $this->assertEquals( $expected, $merged ? $merged->getText() : $merged );
        }
 
        public static function dataGetAutosummary() {
index be93563..f689cae 100644 (file)
@@ -130,7 +130,7 @@ just a test"
 
                $sectionContent = $content->getSection( $sectionId );
                if ( is_object( $sectionContent ) ) {
-                       $sectionText = $sectionContent->getNativeData();
+                       $sectionText = $sectionContent->getText();
                } else {
                        $sectionText = $sectionContent;
                }
@@ -184,7 +184,7 @@ just a test"
                $content = $this->newContent( $text );
                $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle );
 
-               $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() );
+               $this->assertEquals( $expected, is_null( $c ) ? null : $c->getText() );
        }
 
        /**
@@ -194,7 +194,7 @@ just a test"
                $content = $this->newContent( 'hello world' );
                $content = $content->addSectionHeader( 'test' );
 
-               $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() );
+               $this->assertEquals( "== test ==\n\nhello world", $content->getText() );
        }
 
        public static function dataPreSaveTransform() {
diff --git a/tests/phpunit/includes/libs/services/ServiceContainerTest.php b/tests/phpunit/includes/libs/services/ServiceContainerTest.php
new file mode 100644 (file)
index 0000000..6674a15
--- /dev/null
@@ -0,0 +1,496 @@
+<?php
+use Wikimedia\Services\ServiceContainer;
+
+/**
+ * @covers Wikimedia\Services\ServiceContainer
+ */
+class ServiceContainerTest extends PHPUnit\Framework\TestCase {
+
+       use MediaWikiCoversValidator; // TODO this library is supposed to be independent of MediaWiki
+       use PHPUnit4And6Compat;
+
+       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( Wikimedia\Services\NoSuchServiceException::class );
+
+               $services->getService( $name );
+       }
+
+       public function testPeekService() {
+               $services = $this->newServiceContainer();
+
+               $services->defineService(
+                       'Foo',
+                       function () {
+                               return new stdClass();
+                       }
+               );
+
+               $services->defineService(
+                       'Bar',
+                       function () {
+                               return new stdClass();
+                       }
+               );
+
+               // trigger instantiation of Foo
+               $services->getService( 'Foo' );
+
+               $this->assertInternalType(
+                       'object',
+                       $services->peekService( 'Foo' ),
+                       'Peek should return the service object if it had been accessed before.'
+               );
+
+               $this->assertNull(
+                       $services->peekService( 'Bar' ),
+                       'Peek should return null if the service was never accessed.'
+               );
+       }
+
+       public function testPeekService_fail_unknown() {
+               $services = $this->newServiceContainer();
+
+               $name = 'TestService92834576';
+
+               $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
+
+               $services->peekService( $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( Wikimedia\Services\ServiceAlreadyDefinedException::class );
+
+               $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 testImportWiring() {
+               $services = $this->newServiceContainer();
+
+               $wiring = [
+                       'Foo' => function () {
+                               return 'Foo!';
+                       },
+                       'Bar' => function () {
+                               return 'Bar!';
+                       },
+                       'Car' => function () {
+                               return 'FUBAR!';
+                       },
+               ];
+
+               $services->applyWiring( $wiring );
+
+               $services->addServiceManipulator( 'Foo', function ( $service ) {
+                       return $service . '+X';
+               } );
+
+               $services->addServiceManipulator( 'Car', function ( $service ) {
+                       return $service . '+X';
+               } );
+
+               $newServices = $this->newServiceContainer();
+
+               // create a service with manipulator
+               $newServices->defineService( 'Foo', function () {
+                       return 'Foo!';
+               } );
+
+               $newServices->addServiceManipulator( 'Foo', function ( $service ) {
+                       return $service . '+Y';
+               } );
+
+               // create a service before importing, so we can later check that
+               // existing service instances survive importWiring()
+               $newServices->defineService( 'Car', function () {
+                       return 'Car!';
+               } );
+
+               // force instantiation
+               $newServices->getService( 'Car' );
+
+               // Define another service, so we can later check that extra wiring
+               // is not lost.
+               $newServices->defineService( 'Xar', function () {
+                       return 'Xar!';
+               } );
+
+               // import wiring, but skip `Bar`
+               $newServices->importWiring( $services, [ 'Bar' ] );
+
+               $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
+               $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
+
+               // import all wiring, but preserve existing service instance
+               $newServices->importWiring( $services );
+
+               $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
+               $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
+               $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
+               $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
+       }
+
+       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( Wikimedia\Services\ServiceAlreadyDefinedException::class );
+
+               $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_disabled() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $theService1 = new stdClass();
+               $name = 'TestService92834576';
+
+               $services->defineService( $name, function () {
+                       return 'Foo';
+               } );
+
+               // disable the service. we should be able to redefine it anyway.
+               $services->disableService( $name );
+
+               $services->redefineService( $name, function () use ( $theService1 ) {
+                       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( Wikimedia\Services\NoSuchServiceException::class );
+
+               $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( Wikimedia\Services\CannotReplaceActiveServiceException::class );
+
+               $services->redefineService( $name, function () use ( $theService ) {
+                       return $theService;
+               } );
+       }
+
+       public function testAddServiceManipulator() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $theService1 = new stdClass();
+               $theService2 = new stdClass();
+               $name = 'TestService92834576';
+
+               $services->defineService(
+                       $name,
+                       function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
+                               PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
+                               PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
+                               return $theService1;
+                       }
+               );
+
+               $services->addServiceManipulator(
+                       $name,
+                       function (
+                               $theService, $actualLocator, $extra
+                       ) use (
+                               $services, $theService1, $theService2
+                       ) {
+                               PHPUnit_Framework_Assert::assertSame( $theService1, $theService );
+                               PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
+                               PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
+                               return $theService2;
+                       }
+               );
+
+               // force instantiation, check result
+               $this->assertSame( $theService2, $services->getService( $name ) );
+       }
+
+       public function testAddServiceManipulator_fail_undefined() {
+               $services = $this->newServiceContainer();
+
+               $theService = new stdClass();
+               $name = 'TestService92834576';
+
+               $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
+
+               $services->addServiceManipulator( $name, function () use ( $theService ) {
+                       return $theService;
+               } );
+       }
+
+       public function testAddServiceManipulator_fail_in_use() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $theService = new stdClass();
+               $name = 'TestService92834576';
+
+               $services->defineService( $name, function () use ( $theService ) {
+                       return $theService;
+               } );
+
+               // create the service, so it can no longer be redefined
+               $services->getService( $name );
+
+               $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
+
+               $services->addServiceManipulator( $name, function () {
+                       return 'Foo';
+               } );
+       }
+
+       public function testDisableService() {
+               $services = $this->newServiceContainer( [ 'Foo' ] );
+
+               $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
+                       ->getMock();
+               $destructible->expects( $this->once() )
+                       ->method( 'destroy' );
+
+               $services->defineService( 'Foo', function () use ( $destructible ) {
+                       return $destructible;
+               } );
+               $services->defineService( 'Bar', function () {
+                       return new stdClass();
+               } );
+               $services->defineService( 'Qux', function () {
+                       return new stdClass();
+               } );
+
+               // instantiate Foo and Bar services
+               $services->getService( 'Foo' );
+               $services->getService( 'Bar' );
+
+               // disable service, should call destroy() once.
+               $services->disableService( 'Foo' );
+
+               // disabled service should still be listed
+               $this->assertContains( 'Foo', $services->getServiceNames() );
+
+               // getting other services should still work
+               $services->getService( 'Bar' );
+
+               // disable non-destructible service, and not-yet-instantiated service
+               $services->disableService( 'Bar' );
+               $services->disableService( 'Qux' );
+
+               $this->assertNull( $services->peekService( 'Bar' ) );
+               $this->assertNull( $services->peekService( 'Qux' ) );
+
+               // disabled service should still be listed
+               $this->assertContains( 'Bar', $services->getServiceNames() );
+               $this->assertContains( 'Qux', $services->getServiceNames() );
+
+               $this->setExpectedException( Wikimedia\Services\ServiceDisabledException::class );
+               $services->getService( 'Qux' );
+       }
+
+       public function testDisableService_fail_undefined() {
+               $services = $this->newServiceContainer();
+
+               $theService = new stdClass();
+               $name = 'TestService92834576';
+
+               $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
+
+               $services->redefineService( $name, function () use ( $theService ) {
+                       return $theService;
+               } );
+       }
+
+       public function testDestroy() {
+               $services = $this->newServiceContainer();
+
+               $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
+                       ->getMock();
+               $destructible->expects( $this->once() )
+                       ->method( 'destroy' );
+
+               $services->defineService( 'Foo', function () use ( $destructible ) {
+                       return $destructible;
+               } );
+
+               $services->defineService( 'Bar', function () {
+                       return new stdClass();
+               } );
+
+               // create the service
+               $services->getService( 'Foo' );
+
+               // destroy the container
+               $services->destroy();
+
+               $this->setExpectedException( Wikimedia\Services\ContainerDisabledException::class );
+               $services->getService( 'Bar' );
+       }
+
+}
diff --git a/tests/phpunit/includes/libs/services/TestWiring1.php b/tests/phpunit/includes/libs/services/TestWiring1.php
new file mode 100644 (file)
index 0000000..b6ff4eb
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+/**
+ * Test file for testing ServiceContainer::loadWiringFiles
+ */
+
+return [
+       'Foo' => function () {
+               return 'Foo!';
+       },
+];
diff --git a/tests/phpunit/includes/libs/services/TestWiring2.php b/tests/phpunit/includes/libs/services/TestWiring2.php
new file mode 100644 (file)
index 0000000..dfff64f
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+/**
+ * Test file for testing ServiceContainer::loadWiringFiles
+ */
+
+return [
+       'Bar' => function () {
+               return 'Bar!';
+       },
+];
diff --git a/tests/phpunit/includes/services/ServiceContainerTest.php b/tests/phpunit/includes/services/ServiceContainerTest.php
deleted file mode 100644 (file)
index aca88aa..0000000
+++ /dev/null
@@ -1,498 +0,0 @@
-<?php
-use MediaWiki\Services\ServiceContainer;
-
-/**
- * @covers MediaWiki\Services\ServiceContainer
- *
- * @group MediaWiki
- */
-class ServiceContainerTest extends PHPUnit\Framework\TestCase {
-
-       use MediaWikiCoversValidator;
-       use PHPUnit4And6Compat;
-
-       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( MediaWiki\Services\NoSuchServiceException::class );
-
-               $services->getService( $name );
-       }
-
-       public function testPeekService() {
-               $services = $this->newServiceContainer();
-
-               $services->defineService(
-                       'Foo',
-                       function () {
-                               return new stdClass();
-                       }
-               );
-
-               $services->defineService(
-                       'Bar',
-                       function () {
-                               return new stdClass();
-                       }
-               );
-
-               // trigger instantiation of Foo
-               $services->getService( 'Foo' );
-
-               $this->assertInternalType(
-                       'object',
-                       $services->peekService( 'Foo' ),
-                       'Peek should return the service object if it had been accessed before.'
-               );
-
-               $this->assertNull(
-                       $services->peekService( 'Bar' ),
-                       'Peek should return null if the service was never accessed.'
-               );
-       }
-
-       public function testPeekService_fail_unknown() {
-               $services = $this->newServiceContainer();
-
-               $name = 'TestService92834576';
-
-               $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
-
-               $services->peekService( $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( MediaWiki\Services\ServiceAlreadyDefinedException::class );
-
-               $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 testImportWiring() {
-               $services = $this->newServiceContainer();
-
-               $wiring = [
-                       'Foo' => function () {
-                               return 'Foo!';
-                       },
-                       'Bar' => function () {
-                               return 'Bar!';
-                       },
-                       'Car' => function () {
-                               return 'FUBAR!';
-                       },
-               ];
-
-               $services->applyWiring( $wiring );
-
-               $services->addServiceManipulator( 'Foo', function ( $service ) {
-                       return $service . '+X';
-               } );
-
-               $services->addServiceManipulator( 'Car', function ( $service ) {
-                       return $service . '+X';
-               } );
-
-               $newServices = $this->newServiceContainer();
-
-               // create a service with manipulator
-               $newServices->defineService( 'Foo', function () {
-                       return 'Foo!';
-               } );
-
-               $newServices->addServiceManipulator( 'Foo', function ( $service ) {
-                       return $service . '+Y';
-               } );
-
-               // create a service before importing, so we can later check that
-               // existing service instances survive importWiring()
-               $newServices->defineService( 'Car', function () {
-                       return 'Car!';
-               } );
-
-               // force instantiation
-               $newServices->getService( 'Car' );
-
-               // Define another service, so we can later check that extra wiring
-               // is not lost.
-               $newServices->defineService( 'Xar', function () {
-                       return 'Xar!';
-               } );
-
-               // import wiring, but skip `Bar`
-               $newServices->importWiring( $services, [ 'Bar' ] );
-
-               $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
-               $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
-
-               // import all wiring, but preserve existing service instance
-               $newServices->importWiring( $services );
-
-               $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
-               $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
-               $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
-               $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
-       }
-
-       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( MediaWiki\Services\ServiceAlreadyDefinedException::class );
-
-               $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_disabled() {
-               $services = $this->newServiceContainer( [ 'Foo' ] );
-
-               $theService1 = new stdClass();
-               $name = 'TestService92834576';
-
-               $services->defineService( $name, function () {
-                       return 'Foo';
-               } );
-
-               // disable the service. we should be able to redefine it anyway.
-               $services->disableService( $name );
-
-               $services->redefineService( $name, function () use ( $theService1 ) {
-                       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( MediaWiki\Services\NoSuchServiceException::class );
-
-               $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( MediaWiki\Services\CannotReplaceActiveServiceException::class );
-
-               $services->redefineService( $name, function () use ( $theService ) {
-                       return $theService;
-               } );
-       }
-
-       public function testAddServiceManipulator() {
-               $services = $this->newServiceContainer( [ 'Foo' ] );
-
-               $theService1 = new stdClass();
-               $theService2 = new stdClass();
-               $name = 'TestService92834576';
-
-               $services->defineService(
-                       $name,
-                       function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
-                               PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
-                               PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
-                               return $theService1;
-                       }
-               );
-
-               $services->addServiceManipulator(
-                       $name,
-                       function (
-                               $theService, $actualLocator, $extra
-                       ) use (
-                               $services, $theService1, $theService2
-                       ) {
-                               PHPUnit_Framework_Assert::assertSame( $theService1, $theService );
-                               PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
-                               PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
-                               return $theService2;
-                       }
-               );
-
-               // force instantiation, check result
-               $this->assertSame( $theService2, $services->getService( $name ) );
-       }
-
-       public function testAddServiceManipulator_fail_undefined() {
-               $services = $this->newServiceContainer();
-
-               $theService = new stdClass();
-               $name = 'TestService92834576';
-
-               $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
-
-               $services->addServiceManipulator( $name, function () use ( $theService ) {
-                       return $theService;
-               } );
-       }
-
-       public function testAddServiceManipulator_fail_in_use() {
-               $services = $this->newServiceContainer( [ 'Foo' ] );
-
-               $theService = new stdClass();
-               $name = 'TestService92834576';
-
-               $services->defineService( $name, function () use ( $theService ) {
-                       return $theService;
-               } );
-
-               // create the service, so it can no longer be redefined
-               $services->getService( $name );
-
-               $this->setExpectedException( MediaWiki\Services\CannotReplaceActiveServiceException::class );
-
-               $services->addServiceManipulator( $name, function () {
-                       return 'Foo';
-               } );
-       }
-
-       public function testDisableService() {
-               $services = $this->newServiceContainer( [ 'Foo' ] );
-
-               $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
-                       ->getMock();
-               $destructible->expects( $this->once() )
-                       ->method( 'destroy' );
-
-               $services->defineService( 'Foo', function () use ( $destructible ) {
-                       return $destructible;
-               } );
-               $services->defineService( 'Bar', function () {
-                       return new stdClass();
-               } );
-               $services->defineService( 'Qux', function () {
-                       return new stdClass();
-               } );
-
-               // instantiate Foo and Bar services
-               $services->getService( 'Foo' );
-               $services->getService( 'Bar' );
-
-               // disable service, should call destroy() once.
-               $services->disableService( 'Foo' );
-
-               // disabled service should still be listed
-               $this->assertContains( 'Foo', $services->getServiceNames() );
-
-               // getting other services should still work
-               $services->getService( 'Bar' );
-
-               // disable non-destructible service, and not-yet-instantiated service
-               $services->disableService( 'Bar' );
-               $services->disableService( 'Qux' );
-
-               $this->assertNull( $services->peekService( 'Bar' ) );
-               $this->assertNull( $services->peekService( 'Qux' ) );
-
-               // disabled service should still be listed
-               $this->assertContains( 'Bar', $services->getServiceNames() );
-               $this->assertContains( 'Qux', $services->getServiceNames() );
-
-               $this->setExpectedException( MediaWiki\Services\ServiceDisabledException::class );
-               $services->getService( 'Qux' );
-       }
-
-       public function testDisableService_fail_undefined() {
-               $services = $this->newServiceContainer();
-
-               $theService = new stdClass();
-               $name = 'TestService92834576';
-
-               $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
-
-               $services->redefineService( $name, function () use ( $theService ) {
-                       return $theService;
-               } );
-       }
-
-       public function testDestroy() {
-               $services = $this->newServiceContainer();
-
-               $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
-                       ->getMock();
-               $destructible->expects( $this->once() )
-                       ->method( 'destroy' );
-
-               $services->defineService( 'Foo', function () use ( $destructible ) {
-                       return $destructible;
-               } );
-
-               $services->defineService( 'Bar', function () {
-                       return new stdClass();
-               } );
-
-               // create the service
-               $services->getService( 'Foo' );
-
-               // destroy the container
-               $services->destroy();
-
-               $this->setExpectedException( MediaWiki\Services\ContainerDisabledException::class );
-               $services->getService( 'Bar' );
-       }
-
-}
diff --git a/tests/phpunit/includes/services/TestWiring1.php b/tests/phpunit/includes/services/TestWiring1.php
deleted file mode 100644 (file)
index b6ff4eb..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?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
deleted file mode 100644 (file)
index dfff64f..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-/**
- * Test file for testing ServiceContainer::loadWiringFiles
- */
-
-return [
-       'Bar' => function () {
-               return 'Bar!';
-       },
-];