Legoktm has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/237971

Change subject: registration: Overhaul merging of globals
......................................................................

registration: Overhaul merging of globals

Instead of hardcoding specific global settings in ExtensionRegistry,
create specific "merge strategies" that are used to merge globals.

Merge strategies are set for core properties in the ExtensionProcessor,
and extensions can set them for their own configuration settings using
the magic "_merge_strategy" key.

The following merge strategies are included:
* array_merge_recursive - call `array_merge_recursive` on the two arrays
* array_plus - use the "+" operator to combine arrays, preserving
               integer keys
* array_plus_2d - A version of array_plus that works on 2d arrays, used
                  for merging arrays like $wgGroupPermissions
* array_merge - call `array_merge` (default)

This changes the merging of various namespaces related settings to use
array_plus so they actually work.

Bug: T107646
Change-Id: I64cb0553864e3b78b0f203333f58bb73b86a6434
---
M docs/extension.schema.json
M includes/registration/ExtensionProcessor.php
M includes/registration/ExtensionRegistry.php
M tests/phpunit/includes/registration/ExtensionProcessorTest.php
M tests/phpunit/includes/registration/ExtensionRegistryTest.php
5 files changed, 159 insertions(+), 23 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/71/237971/1

diff --git a/docs/extension.schema.json b/docs/extension.schema.json
index d5c17a1..14d7c22 100644
--- a/docs/extension.schema.json
+++ b/docs/extension.schema.json
@@ -627,7 +627,24 @@
                },
                "config": {
                        "type": "object",
-                       "description": "Configuration options for this 
extension"
+                       "description": "Configuration options for this 
extension",
+                       "patternProperties": {
+                               
"^[a-zA-Z_\u007f-\u00ff][a-zA-Z0-9_\u007f-\u00ff]*$": {
+                                       "type": ["object", "array", "string", 
"integer", "null", "boolean"],
+                                       "properties": {
+                                               "_merge_strategy": {
+                                                       "type": "string",
+                                                       "enum": [
+                                                               
"array_merge_recursive",
+                                                               "array_plus_2d",
+                                                               "array_plus",
+                                                               "array_merge"
+                                                       ],
+                                                       "default": "array_merge"
+                                               }
+                                       }
+                               }
+                       }
                },
                "ParserTestFiles": {
                        "type": "array",
diff --git a/includes/registration/ExtensionProcessor.php 
b/includes/registration/ExtensionProcessor.php
index a11f1aa..81ea969 100644
--- a/includes/registration/ExtensionProcessor.php
+++ b/includes/registration/ExtensionProcessor.php
@@ -47,6 +47,24 @@
        );
 
        /**
+        * Mapping of global settings to their specific merge strategies.
+        *
+        * @see ExtensionRegistry::exportExtractedData
+        * @see getExtractedInfo
+        * @var array
+        */
+       protected static $mergeStrategies = array(
+               'wgGroupPermissions' => 'array_plus_2d',
+               'wgRevokePermissions' => 'array_plus_2d',
+               'wgHooks' => 'array_merge_recursive',
+               'wgExtensionCredits' => 'array_merge_recursive',
+               'wgExtraNamespaces' => 'array_plus',
+               'wgExtraGenderNamespaces' => 'array_plus',
+               'wgNamespacesWithSubpages' => 'array_plus',
+               'wgNamespaceContentModels' => 'array_plus',
+       );
+
+       /**
         * Keys that are part of the extension credits
         *
         * @var array
@@ -155,6 +173,13 @@
        }
 
        public function getExtractedInfo() {
+               // Make sure the merge strategies are set
+               foreach ( $this->globals as $key => $val ) {
+                       if ( isset( self::$mergeStrategies[$key] ) ) {
+                               
$this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = 
self::$mergeStrategies[$key];
+                       }
+               }
+
                return array(
                        'globals' => $this->globals,
                        'defines' => $this->defines,
diff --git a/includes/registration/ExtensionRegistry.php 
b/includes/registration/ExtensionRegistry.php
index 8ea12ee..271b3a9 100644
--- a/includes/registration/ExtensionRegistry.php
+++ b/includes/registration/ExtensionRegistry.php
@@ -12,6 +12,23 @@
 class ExtensionRegistry {
 
        /**
+        * Version of the highest supported manifest version
+        */
+       const MANIFEST_VERSION = 1;
+
+       /**
+        * Version of the oldest supported manifest version
+        */
+       const OLDEST_MANIFEST_VERSION = 1;
+
+       /**
+        * Special key that defines the merge strategy
+        *
+        * @since 1.26
+        */
+       const MERGE_STRATEGY = '_merge_strategy';
+
+       /**
         * @var BagOStuff
         */
        protected $cache;
@@ -161,25 +178,54 @@
 
        protected function exportExtractedData( array $info ) {
                foreach ( $info['globals'] as $key => $val ) {
+                       // If a merge strategy is set, read it and remove it 
from the value
+                       // so it doesn't accidentally end up getting set.
+                       // Need to check $val is an array for PHP 5.3 which 
will return
+                       // true on isset( 'string'['foo'] ).
+                       if ( isset( $val[self::MERGE_STRATEGY] ) && is_array( 
$val ) ) {
+                               $mergeStrategy = $val[self::MERGE_STRATEGY];
+                               unset( $val[self::MERGE_STRATEGY] );
+                       } else {
+                               $mergeStrategy = 'array_merge';
+                       }
+
+                       // Optimistic: If the global is not set, or is an empty 
array, replace it entirely.
+                       // Will be O(1) performance.
                        if ( !isset( $GLOBALS[$key] ) || ( is_array( 
$GLOBALS[$key] ) && !$GLOBALS[$key] ) ) {
                                $GLOBALS[$key] = $val;
-                       } elseif ( $key === 'wgHooks' || $key === 
'wgExtensionCredits' ) {
-                               // Special case $wgHooks and 
$wgExtensionCredits, which require a recursive merge.
-                               // Ideally it would have been taken care of in 
the first if block though.
-                               $GLOBALS[$key] = array_merge_recursive( 
$GLOBALS[$key], $val );
-                       } elseif ( $key === 'wgGroupPermissions' || $key === 
'wgRevokePermissions' ) {
-                               // First merge individual groups
-                               foreach ( $GLOBALS[$key] as $name => &$groupVal 
) {
-                                       if ( isset( $val[$name] ) ) {
-                                               $groupVal += $val[$name];
+                               continue;
+                       }
+
+                       if ( !is_array( $GLOBALS[$key] ) || !is_array( $val ) ) 
{
+                               // config setting that has already been 
overridden, don't set it
+                               continue;
+                       }
+
+                       switch ( $mergeStrategy ) {
+                               case 'array_merge_recursive':
+                                       $GLOBALS[$key] = array_merge_recursive( 
$GLOBALS[$key], $val );
+                                       break;
+                               case 'array_plus_2d':
+                                       // First merge items that are in both 
arrays
+                                       foreach ( $GLOBALS[$key] as $name => 
&$groupVal ) {
+                                               if ( isset( $val[$name] ) ) {
+                                                       $groupVal += 
$val[$name];
+                                               }
                                        }
-                               }
-                               // Now merge groups that didn't exist yet
-                               $GLOBALS[$key] += $val;
-                       } elseif ( is_array( $GLOBALS[$key] ) && is_array( $val 
) ) {
-                               $GLOBALS[$key] = array_merge( $val, 
$GLOBALS[$key] );
-                       } // else case is a config setting where it has already 
been overriden, so don't set it
+                                       // Now add items that didn't exist yet
+                                       $GLOBALS[$key] += $val;
+                                       break;
+                               case 'array_plus':
+                                       $GLOBALS[$key] = $val + $GLOBALS[$key];
+                                       break;
+                               case 'array_merge':
+                                       $GLOBALS[$key] = array_merge( $val, 
$GLOBALS[$key] );
+                                       break;
+                               default:
+                                       throw new UnexpectedValueException( 
"Unknown merge strategy '$mergeStrategy'" );
+                       }
                }
+
                foreach ( $info['defines'] as $name => $val ) {
                        define( $name, $val );
                }
diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php 
b/tests/phpunit/includes/registration/ExtensionProcessorTest.php
index 9474496..b86279d 100644
--- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php
+++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php
@@ -38,6 +38,7 @@
        }
 
        public static function provideRegisterHooks() {
+               $merge = array( ExtensionRegistry::MERGE_STRATEGY => 
'array_merge_recursive' );
                // Format:
                // Current $wgHooks
                // Content in extension.json
@@ -47,19 +48,19 @@
                        array(
                                array(),
                                self::$default,
-                               array(),
+                               $merge,
                        ),
                        // No current hooks, adding one for "FooBaz"
                        array(
                                array(),
                                array( 'Hooks' => array( 'FooBaz' => 
'FooBazCallback' ) ) + self::$default,
-                               array( 'FooBaz' => array( 'FooBazCallback' ) ),
+                               array( 'FooBaz' => array( 'FooBazCallback' ) ) 
+ $merge,
                        ),
                        // Hook for "FooBaz", adding another one
                        array(
                                array( 'FooBaz' => array( 'PriorCallback' ) ),
                                array( 'Hooks' => array( 'FooBaz' => 
'FooBazCallback' ) ) + self::$default,
-                               array( 'FooBaz' => array( 'PriorCallback', 
'FooBazCallback' ) ),
+                               array( 'FooBaz' => array( 'PriorCallback', 
'FooBazCallback' ) ) + $merge,
                        ),
                        // Hook for "BarBaz", adding one for "FooBaz"
                        array(
@@ -68,7 +69,7 @@
                                array(
                                        'BarBaz' => array( 'BarBazCallback' ),
                                        'FooBaz' => array( 'FooBazCallback' ),
-                               ),
+                               ) + $merge,
                        ),
                        // Callbacks for FooBaz wrapped in an array
                        array(
@@ -76,7 +77,7 @@
                                array( 'Hooks' => array( 'FooBaz' => array( 
'Callback1' ) ) ) + self::$default,
                                array(
                                        'FooBaz' => array( 'Callback1' ),
-                               ),
+                               ) + $merge,
                        ),
                        // Multiple callbacks for FooBaz hook
                        array(
@@ -84,7 +85,7 @@
                                array( 'Hooks' => array( 'FooBaz' => array( 
'Callback1', 'Callback2' ) ) ) + self::$default,
                                array(
                                        'FooBaz' => array( 'Callback1', 
'Callback2' ),
-                               ),
+                               ) + $merge,
                        ),
                );
        }
diff --git a/tests/phpunit/includes/registration/ExtensionRegistryTest.php 
b/tests/phpunit/includes/registration/ExtensionRegistryTest.php
index d7d4f19..55824bc 100644
--- a/tests/phpunit/includes/registration/ExtensionRegistryTest.php
+++ b/tests/phpunit/includes/registration/ExtensionRegistryTest.php
@@ -102,6 +102,50 @@
                                )
                        ),
                        array(
+                               'Global already set, 1d array that appends',
+                               array(
+                                       'mwAvailableRights' => array(
+                                               'foobar',
+                                               'foo'
+                                       ),
+                               ),
+                               array(
+                                       'mwAvailableRights' => array(
+                                               'barbaz',
+                                       ),
+                               ),
+                               array(
+                                       'mwAvailableRights' => array(
+                                               'barbaz',
+                                               'foobar',
+                                               'foo',
+                                       ),
+                               )
+                       ),
+                       array(
+                               'Global already set, 2d array with integer 
keys',
+                               array(
+                                       'mwNamespacesFoo' => array(
+                                               100 => true,
+                                               102 => false
+                                       ),
+                               ),
+                               array(
+                                       'mwNamespacesFoo' => array(
+                                               100 => false,
+                                               500 => true,
+                                               
ExtensionRegistry::MERGE_STRATEGY => 'array_plus',
+                                       ),
+                               ),
+                               array(
+                                       'mwNamespacesFoo' => array(
+                                               100 => false,
+                                               102 => false,
+                                               500 => true,
+                                       ),
+                               )
+                       ),
+                       array(
                                'No global already set, $wgHooks',
                                array(
                                        'wgHooks' => array(),
@@ -111,6 +155,7 @@
                                                'FooBarEvent' => array(
                                                        
'FooBarClass::onFooBarEvent'
                                                ),
+                                               
ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive'
                                        ),
                                ),
                                array(
@@ -138,6 +183,7 @@
                                                'FooBarEvent' => array(
                                                        
'BazBarClass::onFooBarEvent',
                                                ),
+                                               
ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive',
                                        ),
                                ),
                                array(
@@ -172,7 +218,8 @@
                                                'user' => array(
                                                        'right' => true,
                                                        'somethingtwo' => false,
-                                               )
+                                               ),
+                                               
ExtensionRegistry::MERGE_STRATEGY => 'array_plus_2d',
                                        ),
                                ),
                                array(

-- 
To view, visit https://gerrit.wikimedia.org/r/237971
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I64cb0553864e3b78b0f203333f58bb73b86a6434
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: REL1_25
Gerrit-Owner: Legoktm <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to