registration: Let extensions add PHP version requirements
authorMGChecker <hgasuser@gmail.com>
Sat, 8 Sep 2018 00:02:53 +0000 (02:02 +0200)
committerMGChecker <hgasuser@gmail.com>
Sat, 22 Sep 2018 01:43:28 +0000 (03:43 +0200)
While MediaWiki Core already sets requirements for PHP versions, it should be
possible for extensions to tighten these requirements. This mirrors the PHP
parameter of extension infoboxes as well.

This change introduces a new 'platform' key (in addition to 'MediaWiki', 'skins'
and 'extensions', where non-MediaWiki software requirements will be listed
in the future, starting with a PHP version constraint. Further keys are
supposed to be added to allow setting constraints against php extensions
and other abilities of the platform.

Bug: T197535
Change-Id: I6744cc0be2363b603331af9dc860eb8603a1a89a

docs/extension.schema.v1.json
docs/extension.schema.v2.json
includes/registration/ExtensionDependencyError.php
includes/registration/ExtensionRegistry.php
includes/registration/VersionChecker.php
tests/phpunit/includes/registration/ExtensionProcessorTest.php
tests/phpunit/includes/registration/VersionCheckerTest.php

index 0ff169c..e6ec971 100644 (file)
                },
                "requires": {
                        "type": "object",
-                       "description": "Indicates what versions of MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.",
+                       "description": "Indicates what versions of PHP, MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.",
                        "additionalProperties": false,
                        "properties": {
                                "MediaWiki": {
                                        "type": "string",
                                        "description": "Version constraint string against MediaWiki core."
                                },
+                               "platform": {
+                                       "type": "object",
+                                       "description": "Indicates version constraints against platform services.",
+                                       "additionalProperties": false,
+                                       "properties": {
+                                               "php": {
+                                                       "type": "string",
+                                                       "description": "Version constraint string against PHP."
+                                               }
+                                       }
+                               },
                                "extensions": {
                                        "type": "object",
                                        "description": "Set of version constraint strings against specific extensions."
index 7de5ed5..93bf0d9 100644 (file)
                },
                "requires": {
                        "type": "object",
-                       "description": "Indicates what versions of MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.",
+                       "description": "Indicates what versions of PHP, MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.",
                        "additionalProperties": false,
                        "properties": {
                                "MediaWiki": {
                                        "type": "string",
                                        "description": "Version constraint string against MediaWiki core."
                                },
+                               "platform": {
+                                       "type": "object",
+                                       "description": "Indicates version constraints against platform services.",
+                                       "additionalProperties": false,
+                                       "properties": {
+                                               "php": {
+                                                       "type": "string",
+                                                       "description": "Version constraint string against PHP."
+                                               }
+                                       }
+                               },
                                "extensions": {
                                        "type": "object",
                                        "description": "Set of version constraint strings against specific extensions."
index d380d07..dfd5985 100644 (file)
@@ -48,6 +48,11 @@ class ExtensionDependencyError extends Exception {
         */
        public $incompatibleCore = false;
 
+       /**
+        * @var bool
+        */
+       public $incompatiblePhp = false;
+
        /**
         * @param array $errors Each error has a 'msg' and 'type' key at minimum
         */
@@ -59,6 +64,9 @@ class ExtensionDependencyError extends Exception {
                                case 'incompatible-core':
                                        $this->incompatibleCore = true;
                                        break;
+                               case 'incompatible-php':
+                                       $this->incompatiblePhp = true;
+                                       break;
                                case 'missing-skins':
                                        $this->missingSkins[] = $info['missing'];
                                        break;
index 1f8a27e..3138b37 100644 (file)
@@ -213,7 +213,8 @@ class ExtensionRegistry {
                $autoloadNamespaces = [];
                $autoloaderPaths = [];
                $processor = new ExtensionProcessor();
-               $versionChecker = new VersionChecker( $wgVersion );
+               $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
+               $versionChecker = new VersionChecker( $wgVersion, $phpVersion );
                $extDependencies = [];
                $incompatible = [];
                $warnings = false;
index 1569e08..93b4a14 100644 (file)
@@ -35,6 +35,11 @@ class VersionChecker {
         */
        private $coreVersion = false;
 
+       /**
+        * @var Constraint|bool representing PHP version
+        */
+       private $phpVersion = false;
+
        /**
         * @var array Loaded extensions
         */
@@ -48,9 +53,10 @@ class VersionChecker {
        /**
         * @param string $coreVersion Current version of core
         */
-       public function __construct( $coreVersion ) {
+       public function __construct( $coreVersion, $phpVersion ) {
                $this->versionParser = new VersionParser();
                $this->setCoreVersion( $coreVersion );
+               $this->setPhpVersion( $phpVersion );
        }
 
        /**
@@ -82,6 +88,21 @@ class VersionChecker {
                }
        }
 
+       /**
+        * Set PHP version.
+        *
+        * @param string $phpVersion Current PHP version. Must be well-formed.
+        * @throws UnexpectedValueException
+        */
+       private function setPhpVersion( $phpVersion ) {
+               // normalize to make this throw an exception if the version is invalid
+               $this->phpVersion = new Constraint(
+                       '==',
+                       $this->versionParser->normalize( $phpVersion )
+               );
+               $this->phpVersion->setPrettyString( $phpVersion );
+       }
+
        /**
         * Check all given dependencies if they are compatible with the named
         * installed extensions in the $credits array.
@@ -90,6 +111,9 @@ class VersionChecker {
         *     {
         *       'FooBar' => {
         *         'MediaWiki' => '>= 1.25.0',
+        *         'platform': {
+        *           'php': '>= 7.0.0'
+        *         },
         *         'extensions' => {
         *           'FooBaz' => '>= 1.25.0'
         *         },
@@ -108,14 +132,47 @@ class VersionChecker {
                        foreach ( $dependencies as $dependencyType => $values ) {
                                switch ( $dependencyType ) {
                                        case ExtensionRegistry::MEDIAWIKI_CORE:
-                                               $mwError = $this->handleMediaWikiDependency( $values, $extension );
+                                               $mwError = $this->handleDependency(
+                                                       $this->coreVersion,
+                                                       $values,
+                                                       $extension
+                                               );
                                                if ( $mwError !== false ) {
                                                        $errors[] = [
-                                                               'msg' => $mwError,
+                                                               'msg' =>
+                                                                       "{$extension} is not compatible with the current MediaWiki "
+                                                                       . "core (version {$this->coreVersion->getPrettyString()}), "
+                                                                       . "it requires: $values."
+                                                               ,
                                                                'type' => 'incompatible-core',
                                                        ];
                                                }
                                                break;
+                                       case 'platform':
+                                               foreach ( $values as $dependency => $constraint ) {
+                                                       if ( $dependency === 'php' ) {
+                                                               $phpError = $this->handleDependency(
+                                                                       $this->phpVersion,
+                                                                       $constraint,
+                                                                       $extension
+                                                               );
+                                                               if ( $phpError !== false ) {
+                                                                       $errors[] = [
+                                                                               'msg' =>
+                                                                                       "{$extension} is not compatible with the current PHP "
+                                                                                       . "version {$this->phpVersion->getPrettyString()}), "
+                                                                                       . "it requires: $constraint."
+                                                                               ,
+                                                                               'type' => 'incompatible-php',
+                                                                       ];
+                                                               }
+                                                       } else {
+                                                               // add other platform dependencies here
+                                                               throw new UnexpectedValueException( 'Dependency type ' . $dependency .
+                                                                       ' unknown in ' . $extension );
+                                                       }
+                                               }
+                                               break;
                                        case 'extensions':
                                        case 'skins':
                                                foreach ( $values as $dependency => $constraint ) {
@@ -138,29 +195,27 @@ class VersionChecker {
        }
 
        /**
-        * Handle a dependency to MediaWiki core. It will check, if a MediaWiki version constraint was
-        * set with self::setCoreVersion before this call (if not, it will return an empty array) and
-        * checks the version constraint given against it.
+        * Handle a simple dependency to MediaWiki core or PHP. See handleMediaWikiDependency and
+        * handlePhpDependency for details.
         *
+        * @param Constraint|bool $version The version installed
         * @param string $constraint The required version constraint for this dependency
         * @param string $checkedExt The Extension, which depends on this dependency
-        * @return bool|string false if no error, or a string with the message
+        * @return bool false if no error, true else
         */
-       private function handleMediaWikiDependency( $constraint, $checkedExt ) {
-               if ( $this->coreVersion === false ) {
-                       // Couldn't parse the core version, so we can't check anything
+       private function handleDependency( $version, $constraint, $checkedExt ) {
+               if ( $version === false ) {
+                       // Couldn't parse the version, so we can't check anything
                        return false;
                }
 
                // if the installed and required version are compatible, return an empty array
                if ( $this->versionParser->parseConstraints( $constraint )
-                       ->matches( $this->coreVersion ) ) {
+                       ->matches( $version ) ) {
                        return false;
                }
-               // otherwise mark this as incompatible.
-               return "{$checkedExt} is not compatible with the current "
-                       . "MediaWiki core (version {$this->coreVersion->getPrettyString()}), it requires: "
-                       . "$constraint.";
+
+               return true;
        }
 
        /**
index d9e091d..71a3a4f 100644 (file)
@@ -678,6 +678,9 @@ class ExtensionProcessorTest extends MediaWikiTestCase {
                $info = self::$default + [
                        'requires' => [
                                'MediaWiki' => '>= 1.25.0',
+                               'platform' => [
+                                       'php' => '>= 5.5.9'
+                               ],
                                'extensions' => [
                                        'Bar' => '*'
                                ]
index b668a9a..20f97bf 100644 (file)
@@ -9,10 +9,10 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
        use PHPUnit4And6Compat;
 
        /**
-        * @dataProvider provideCheck
+        * @dataProvider provideMediaWikiCheck
         */
-       public function testCheck( $coreVersion, $constraint, $expected ) {
-               $checker = new VersionChecker( $coreVersion );
+       public function testMediaWikiCheck( $coreVersion, $constraint, $expected ) {
+               $checker = new VersionChecker( $coreVersion, '7.0.0' );
                $this->assertEquals( $expected, !(bool)$checker->checkArray( [
                        'FakeExtension' => [
                                'MediaWiki' => $constraint,
@@ -20,7 +20,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                ] ) );
        }
 
-       public static function provideCheck() {
+       public static function provideMediaWikiCheck() {
                return [
                        // [ $wgVersion, constraint, expected ]
                        [ '1.25alpha', '>= 1.26', false ],
@@ -44,11 +44,64 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                ];
        }
 
+       /**
+        * @dataProvider providePhpValidCheck
+        */
+       public function testPhpValidCheck( $phpVersion, $constraint, $expected ) {
+               $checker = new VersionChecker( '1.0.0', $phpVersion );
+               $this->assertEquals( $expected, !(bool)$checker->checkArray( [
+                       'FakeExtension' => [
+                               'platform' => [
+                                       'php' => $constraint,
+                               ],
+                       ],
+               ] ) );
+       }
+
+       public static function providePhpValidCheck() {
+               return [
+                       // [ phpVersion, constraint, expected ]
+                       [ '7.0.23', '>= 7.0.0', true ],
+                       [ '7.0.23', '^7.1.0', false ],
+                       [ '7.0.23', '7.0.23', true ],
+               ];
+       }
+
+       /**
+        * @expectedException UnexpectedValueException
+        */
+       public function testPhpInvalidConstraint() {
+               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $checker->checkArray( [
+                       'FakeExtension' => [
+                               'platform' => [
+                                       'php' => 'totallyinvalid',
+                               ],
+                       ],
+               ] );
+       }
+
+       /**
+        * @dataProvider providePhpInvalidVersion
+        * @expectedException UnexpectedValueException
+        */
+       public function testPhpInvalidVersion( $phpVersion ) {
+                $checker = new VersionChecker( '1.0.0', $phpVersion );
+       }
+
+       public static function providePhpInvalidVersion() {
+               return [
+                       // [ phpVersion ]
+                       [ '7.abc' ],
+                       [ '5.a.x' ],
+               ];
+       }
+
        /**
         * @dataProvider provideType
         */
        public function testType( $given, $expected ) {
-               $checker = new VersionChecker( '1.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0' );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => '1.0.0',
@@ -150,7 +203,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
         * returns any error message.
         */
        public function testInvalidConstraint() {
-               $checker = new VersionChecker( '1.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0' );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => 'not really valid',
@@ -169,7 +222,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                        ],
                ] ) );
 
-               $checker = new VersionChecker( '1.0.0' );
+               $checker = new VersionChecker( '1.0.0', '7.0.0' );
                $checker->setLoadedExtensionsAndSkins( [
                                'FakeDependency' => [
                                        'version' => '1.24.3',
@@ -184,24 +237,49 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase {
                ] );
        }
 
-       /**
-        * T197478
-        */
-       public function testInvalidDependency() {
-               $checker = new VersionChecker( '1.0.0' );
-               $this->setExpectedException( UnexpectedValueException::class,
-                       'Dependency type skin unknown in FakeExtension' );
-               $this->assertEquals( [
+       public function provideInvalidDependency() {
+               return [
                        [
-                               'type' => 'invalid-version',
-                               'msg' => 'FakeDependency does not have a valid version string.',
+                               [
+                                       'FakeExtension' => [
+                                               'platform' => [
+                                                       'undefinedPlatformDependency' => '*',
+                                               ],
+                                       ],
+                               ],
+                               'undefinedPlatformDependency',
                        ],
-               ], $checker->checkArray( [
-                       'FakeExtension' => [
-                               'skin' => [
-                                       'FakeSkin' => '*',
+                       [
+                               [
+                                       'FakeExtension' => [
+                                               'undefinedDependencyType' => '*',
+                                       ],
                                ],
+                               'undefinedDependencyType',
                        ],
-               ] ) );
+                       // T197478
+                       [
+                               [
+                                       'FakeExtension' => [
+                                               'skin' => [
+                                                       'FakeSkin' => '*',
+                                               ],
+                                       ],
+                               ],
+                               'skin',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideInvalidDependency
+        */
+       public function testInvalidDependency( $depencency, $type ) {
+               $checker = new VersionChecker( '1.0.0', '7.0.0' );
+               $this->setExpectedException(
+                       UnexpectedValueException::class,
+                       "Dependency type $type unknown in FakeExtension"
+               );
+               $checker->checkArray( $depencency );
        }
 }