Introduce ParserFactory service
authorAryeh Gregor <ayg@aryeh.name>
Fri, 3 Aug 2018 08:25:15 +0000 (11:25 +0300)
committerKunal Mehta <legoktm@member.fsf.org>
Sat, 11 Aug 2018 06:15:52 +0000 (00:15 -0600)
Bug: T200881
Change-Id: I257e78200983cb10afb76de1f07dd1b9d531c52a

autoload.php
includes/MediaWikiServices.php
includes/ServiceWiring.php
includes/parser/Parser.php
includes/parser/ParserFactory.php [new file with mode: 0644]
tests/phpunit/includes/parser/ParserFactoryTest.php [new file with mode: 0644]

index e02a743..feddcdd 100644 (file)
@@ -1078,6 +1078,7 @@ $wgAutoloadLocalClasses = [
        'Parser' => __DIR__ . '/includes/parser/Parser.php',
        'ParserCache' => __DIR__ . '/includes/parser/ParserCache.php',
        'ParserDiffTest' => __DIR__ . '/includes/parser/ParserDiffTest.php',
+       'ParserFactory' => __DIR__ . '/includes/parser/ParserFactory.php',
        'ParserOptions' => __DIR__ . '/includes/parser/ParserOptions.php',
        'ParserOutput' => __DIR__ . '/includes/parser/ParserOutput.php',
        'ParsoidVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/ParsoidVirtualRESTService.php',
index 080a7f9..79e55f8 100644 (file)
@@ -39,6 +39,7 @@ use MimeAnalyzer;
 use ObjectCache;
 use Parser;
 use ParserCache;
+use ParserFactory;
 use PasswordFactory;
 use ProxyLookup;
 use SearchEngine;
@@ -682,6 +683,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'ParserCache' );
        }
 
+       /**
+        * @since 1.32
+        * @return ParserFactory
+        */
+       public function getParserFactory() {
+               return $this->getService( 'ParserFactory' );
+       }
+
        /**
         * @since 1.32
         * @return PasswordFactory
index 08f7d66..5b64a60 100644 (file)
@@ -383,6 +383,15 @@ return [
                );
        },
 
+       'ParserFactory' => function ( MediaWikiServices $services ) : ParserFactory {
+               return new ParserFactory(
+                       $services->getMainConfig()->get( 'ParserConf' ),
+                       $services->getMagicWordFactory(),
+                       $services->getContentLanguage(),
+                       wfUrlProtocols()
+               );
+       },
+
        'PasswordFactory' => function ( MediaWikiServices $services ) : PasswordFactory {
                $config = $services->getMainConfig();
                return new PasswordFactory(
index 4073864..833e5c8 100644 (file)
@@ -263,13 +263,21 @@ class Parser {
        /** @var MagicWordFactory */
        private $magicWordFactory;
 
+       /** @var Language */
+       private $contLang;
+
        /**
-        * @param array $conf
+        * @param array $conf See $wgParserConf documentation
         * @param MagicWordFactory|null $magicWordFactory
+        * @param Language|null $contLang Content language
+        * @param string|null $urlProtocols As returned from wfUrlProtocols()
         */
-       public function __construct( $conf = [], MagicWordFactory $magicWordFactory = null ) {
+       public function __construct(
+               array $conf = [], MagicWordFactory $magicWordFactory = null, Language $contLang = null,
+               $urlProtocols = null
+       ) {
                $this->mConf = $conf;
-               $this->mUrlProtocols = wfUrlProtocols();
+               $this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
                $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
                        self::EXT_LINK_ADDR .
                        self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
@@ -289,10 +297,10 @@ class Parser {
                }
                wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
 
-               $this->magicWordFactory = $magicWordFactory;
-               if ( !$magicWordFactory ) {
-                       $this->magicWordFactory = MediaWikiServices::getInstance()->getMagicWordFactory();
-               }
+               $this->magicWordFactory = $magicWordFactory ??
+                       MediaWikiServices::getInstance()->getMagicWordFactory();
+
+               $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
        }
 
        /**
@@ -972,7 +980,7 @@ class Parser {
         * @return Language
         */
        public function getContentLanguage() {
-               return $this->magicWordFactory->getContentLanguage();
+               return $this->contLang;
        }
 
        /**
@@ -2154,7 +2162,7 @@ class Parser {
                if ( $useLinkPrefixExtension ) {
                        # Match the end of a line for a word that's not followed by whitespace,
                        # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
-                       $charset = $this->getContentLanguage()->linkPrefixCharset();
+                       $charset = $this->contLang->linkPrefixCharset();
                        $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
                }
 
@@ -2710,11 +2718,10 @@ class Parser {
                                break;
                        case 'namespace':
                                $value = str_replace( '_', ' ',
-                                       $this->getContentLanguage()->getNsText( $this->mTitle->getNamespace() ) );
+                                       $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
                                break;
                        case 'namespacee':
-                               $value = wfUrlencode( $this->getContentLanguage()->
-                                       getNsText( $this->mTitle->getNamespace() ) );
+                               $value = wfUrlencode( $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
                                break;
                        case 'namespacenumber':
                                $value = $this->mTitle->getNamespace();
@@ -2863,7 +2870,7 @@ class Parser {
                if ( !$this->getRevisionObject() ) {
                        # Get the timezone-adjusted timestamp $mtts seconds in the future
                        $resThen = substr(
-                               $this->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
+                               $this->contLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
                                $start,
                                $len
                        );
@@ -3415,7 +3422,7 @@ class Parser {
                        $function = $this->mFunctionSynonyms[1][$function];
                } else {
                        # Case insensitive functions
-                       $function = $this->getContentLanguage()->lc( $function );
+                       $function = $this->contLang->lc( $function );
                        if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
                                $function = $this->mFunctionSynonyms[0][$function];
                        } else {
@@ -4519,14 +4526,14 @@ class Parser {
         */
        private function pstPass2( $text, $user ) {
                # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
-               # $this->getContentLanguage() here in order to give everyone the same signature and use the
-               # default one rather than the one selected in each user's preferences.  (see also T14815)
+               # $this->contLang here in order to give everyone the same signature and use the default one
+               # rather than the one selected in each user's preferences.  (see also T14815)
                $ts = $this->mOptions->getTimestamp();
                $timestamp = MWTimestamp::getLocalInstance( $ts );
                $ts = $timestamp->format( 'YmdHis' );
                $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
 
-               $d = $this->getContentLanguage()->timeanddate( $ts, false, false ) . " ($tzMsg)";
+               $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
 
                # Variable replacement
                # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
@@ -4892,7 +4899,7 @@ class Parser {
                foreach ( $synonyms as $syn ) {
                        # Case
                        if ( !$sensitive ) {
-                               $syn = $this->getContentLanguage()->lc( $syn );
+                               $syn = $this->contLang->lc( $syn );
                        }
                        # Add leading hash
                        if ( !( $flags & self::SFH_NO_HASH ) ) {
@@ -5736,7 +5743,7 @@ class Parser {
                        # Since this value will be saved into the parser cache, served
                        # to other users, and potentially even used inside links and such,
                        # it needs to be consistent for all visitors.
-                       $this->mRevisionTimestamp = $this->getContentLanguage()->userAdjust( $timestamp, '' );
+                       $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
 
                }
                return $this->mRevisionTimestamp;
diff --git a/includes/parser/ParserFactory.php b/includes/parser/ParserFactory.php
new file mode 100644 (file)
index 0000000..7b66f9c
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @since 1.32
+ */
+class ParserFactory {
+       /** @var array */
+       private $conf;
+
+       /** @var MagicWordFactory */
+       private $magicWordFactory;
+
+       /** @var Language */
+       private $contLang;
+
+       /** @var string */
+       private $urlProtocols;
+
+       /**
+        * @param array $conf See $wgParserConf documentation
+        * @param MagicWordFactory $magicWordFactory
+        * @param Language $contLang Content language
+        * @param string $urlProtocols As returned from wfUrlProtocols()
+        * @since 1.32
+        */
+       public function __construct(
+               array $conf, MagicWordFactory $magicWordFactory, Language $contLang, $urlProtocols
+       ) {
+               $this->conf = $conf;
+               $this->magicWordFactory = $magicWordFactory;
+               $this->contLang = $contLang;
+               $this->urlProtocols = $urlProtocols;
+       }
+
+       /**
+        * @return Parser
+        * @since 1.32
+        */
+       public function create() : Parser {
+               return new Parser( $this->conf, $this->magicWordFactory, $this->contLang,
+                       $this->urlProtocols );
+       }
+}
diff --git a/tests/phpunit/includes/parser/ParserFactoryTest.php b/tests/phpunit/includes/parser/ParserFactoryTest.php
new file mode 100644 (file)
index 0000000..55a1af4
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @covers ParserFactory
+ */
+class ParserFactoryTest extends MediaWikiTestCase {
+       /**
+        * For backwards compatibility, all parameters to the parser constructor are optional and
+        * default to the appropriate global service, so it's easy to forget to update ParserFactory to
+        * actually pass the parameters it's supposed to.
+        */
+       public function testConstructorArgNum() {
+               $factoryConstructor = new ReflectionMethod( 'ParserFactory', '__construct' );
+               $instanceConstructor = new ReflectionMethod( 'Parser', '__construct' );
+               $this->assertSame( $instanceConstructor->getNumberOfParameters(),
+                       $factoryConstructor->getNumberOfParameters(),
+                       'Parser and ParserFactory constructors have an inconsistent number of parameters. ' .
+                       'Did you add a parameter to one and not the other?' );
+       }
+
+       public function testAllArgumentsWerePassed() {
+               $factoryConstructor = new ReflectionMethod( 'ParserFactory', '__construct' );
+               $mocks = [];
+               foreach ( $factoryConstructor->getParameters() as $param ) {
+                       $type = (string)$param->getType();
+                       if ( $type === 'array' ) {
+                               $val = [ 'porcupines will tell me your secrets' . count( $mocks ) ];
+                       } elseif ( class_exists( $type ) || interface_exists( $type ) ) {
+                               $val = $this->createMock( $type );
+                       } elseif ( $type === '' ) {
+                               // Optimistically assume a string is okay
+                               $val = 'I will de-quill them first' . count( $mocks );
+                       } else {
+                               $this->fail( "Unrecognized parameter type $type in ParserFactory constructor" );
+                       }
+                       $mocks[] = $val;
+               }
+
+               $factory = new ParserFactory( ...$mocks );
+               $parser = $factory->create();
+
+               foreach ( ( new ReflectionObject( $parser ) )->getProperties() as $prop ) {
+                       $prop->setAccessible( true );
+                       foreach ( $mocks as $idx => $mock ) {
+                               if ( $prop->getValue( $parser ) === $mock ) {
+                                       unset( $mocks[$idx] );
+                               }
+                       }
+               }
+
+               $this->assertCount( 0, $mocks, 'Not all arguments to the ParserFactory constructor were ' .
+                       'found in Parser member variables' );
+       }
+}