Browse Source

Allow key deletion. Replace overridden indexed arrays (no merge).

- Fixed parameter errors, coverage analysis, add phpstan.
- Remove unused code, clarify mergeDeepArray difference from Drupal.
Eric BELLOT 2 years ago
parent
commit
ea929fcaa9

+ 3 - 0
.editorconfig

@@ -0,0 +1,3 @@
+[*]
+indent_style = space
+indent_size=2

+ 3 - 2
.gitignore

@@ -1,4 +1,5 @@
-/.idea/$*
-/.idea/workspace.xml
+/.idea/
+/.phpunit.cache
 /composer.lock
 /composer.lock
+/coverage
 /vendor/
 /vendor/

+ 11 - 6
README.md

@@ -1,13 +1,18 @@
 # drupal_composer__builder
 # drupal_composer__builder
 
 
-## Third party code
-
-Due to instabilities in the Composer plugin system, the plugin embeds a copy
-of the Drupal 8 NestedArray class. See src/NestedArray.php for details about the
-Composer issue and its equivalent in other products.
-
 ## License
 ## License
 
 
 The plugin is licensed under the General Public License version 2.0 or later,
 The plugin is licensed under the General Public License version 2.0 or later,
 just like the Drupal code it embeds.
 just like the Drupal code it embeds.
 
 
+## Changes
+
+- 0.1.3 
+  - `mergeDeepArray` moved from `NestedArray` to `MergeParamsCommand` to
+    avoid confusion with the Drupal version.
+  - first unit test
+- 0.1.2
+  - commands can now use configurable templates defined in `composer.json`
+  - new `BuildServicesCommand` to build the `services.yml` file.
+- 0.1.1
+  - first actual release

+ 25 - 5
composer.json

@@ -10,6 +10,12 @@
       "Fgm\\Drupal\\Composer\\": "src/"
       "Fgm\\Drupal\\Composer\\": "src/"
     }
     }
   },
   },
+  "config": {
+    "bin-dir": "vendor/bin",
+    "discard-changes": true,
+    "optimize-autoloader": true,
+    "sort-packages": true
+  },
   "description": "A Composer plugin to help build Drupal sites",
   "description": "A Composer plugin to help build Drupal sites",
   "extra": {
   "extra": {
     "class": "Fgm\\Drupal\\Composer\\Builder"
     "class": "Fgm\\Drupal\\Composer\\Builder"
@@ -17,17 +23,31 @@
   "license": "GPL-2.0-or-later",
   "license": "GPL-2.0-or-later",
   "minimum-stability": "dev",
   "minimum-stability": "dev",
   "name": "fgm/drupal_composer_builder",
   "name": "fgm/drupal_composer_builder",
+  "prefer-stable": true,
   "require": {
   "require": {
     "composer-plugin-api": "^1.1",
     "composer-plugin-api": "^1.1",
     "composer/installers": "^1.2"
     "composer/installers": "^1.2"
   },
   },
-  "type": "composer-plugin",
   "require-dev": {
   "require-dev": {
-    "squizlabs/php_codesniffer": "^3.0@dev",
+    "composer/composer": "^1",
     "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
     "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
-    "drupal/coder": "^8.3.6"
+    "drupal/coder": "^8.3.6",
+    "phpstan/phpstan": "^0.12.99",
+    "phpunit/phpunit": "^9.5",
+    "squizlabs/php_codesniffer": "^3.0@dev",
+    "twig/twig": "^1"
   },
   },
   "scripts": {
   "scripts": {
-    "cs": "phpcs --standard=PSR1,Drupal -v src"
-  }
+    "check": [
+      "@cs",
+      "@stan",
+      "@test",
+      "@cover"
+    ],
+    "cover": "phpunit --coverage-filter=src --coverage-html=coverage --coverage-text",
+    "cs": "phpcs --standard=PSR1,Drupal -v src",
+    "stan": "phpstan analyse --level 5 src tests",
+    "test": "phpunit tests"
+  },
+  "type": "composer-plugin"
 }
 }

+ 33 - 0
phpunit.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
+         colors="true"
+         verbose="true"
+         cacheResultFile=".phpunit.cache/test-results"
+         failOnRisky="true"
+         failOnWarning="true">
+  <testsuites>
+    <testsuite name="unit">
+      <directory>tests</directory>
+    </testsuite>
+  </testsuites>
+
+  <coverage cacheDirectory=".phpunit.cache/code-coverage"
+            processUncoveredFiles="true"
+            ignoreDeprecatedCodeUnits="true">
+    <include>
+      <directory suffix=".php">src</directory>
+    </include>
+
+    <exclude>
+      <directory>vendor</directory>
+    </exclude>
+  </coverage>
+
+  <php>
+    <ini name="precision" value="14"/>
+    <ini name="serialize_precision" value="14"/>
+
+    <const name="PHPUNIT_TESTSUITE" value="true"/>
+  </php>
+</phpunit>

+ 13 - 3
src/BaseBuilderCommand.php

@@ -44,12 +44,18 @@ abstract class BaseBuilderCommand extends BaseCommand {
    *   Command input.
    *   Command input.
    * @param \Symfony\Component\Console\Output\OutputInterface $output
    * @param \Symfony\Component\Console\Output\OutputInterface $output
    *   Command output.
    *   Command output.
+   * @param string $buildName
+   *   The nature of the build: services, settings, etc.
    *
    *
    * @return array
    * @return array
    *   - string Template name
    *   - string Template name
    *   - int Error
    *   - int Error
    */
    */
-  public function validateTemplateName(InputInterface $input, OutputInterface $output, string $buildName): array {
+  public function validateTemplateName(
+    InputInterface $input,
+    OutputInterface $output,
+    string $buildName
+  ): array {
     $conf = $this->getComposer()->getPackage()->getExtra()[Builder::NAME] ?? [];
     $conf = $this->getComposer()->getPackage()->getExtra()[Builder::NAME] ?? [];
     $templateName = $conf['templates'][$buildName] ?? '';
     $templateName = $conf['templates'][$buildName] ?? '';
     if (empty($templateName)) {
     if (empty($templateName)) {
@@ -117,7 +123,11 @@ abstract class BaseBuilderCommand extends BaseCommand {
    * @throws \Twig\Error\RuntimeError
    * @throws \Twig\Error\RuntimeError
    * @throws \Twig\Error\SyntaxError
    * @throws \Twig\Error\SyntaxError
    */
    */
-  protected function prepare(InputInterface $input, OutputInterface $output, string $buildName): array {
+  protected function prepare(
+    InputInterface $input,
+    OutputInterface $output,
+    string $buildName
+  ): array {
     [
     [
       $templateName,
       $templateName,
       $err,
       $err,
@@ -163,7 +173,7 @@ abstract class BaseBuilderCommand extends BaseCommand {
    *   - string: error message
    *   - string: error message
    *   - int: error, 0 if OK. If non-zero, only the error message is reliable.
    *   - int: error, 0 if OK. If non-zero, only the error message is reliable.
    */
    */
-  public function getParams() {
+  public function getParams(): array {
     $settingsPath = $this->getSettingsPath();
     $settingsPath = $this->getSettingsPath();
     $paramsPath = "${settingsPath}/merged.params.local.yml";
     $paramsPath = "${settingsPath}/merged.params.local.yml";
     $realParamsPath = realpath($paramsPath);
     $realParamsPath = realpath($paramsPath);

+ 2 - 6
src/BuildServicesCommand.php

@@ -30,10 +30,6 @@ class BuildServicesCommand extends BaseBuilderCommand {
    */
    */
   protected $eventName;
   protected $eventName;
 
 
-  protected function UseTemplate() {
-    return FALSE;
-  }
-
   /**
   /**
    * Configures the current command.
    * Configures the current command.
    */
    */
@@ -89,14 +85,14 @@ EOT
       if (file_exists($destination)) {
       if (file_exists($destination)) {
         $ok = unlink($destination);
         $ok = unlink($destination);
         if (!$ok) {
         if (!$ok) {
-          return [sprintf("Could not remove old %s file", $destination), 1];
+          return 1;
         }
         }
       }
       }
       // Convert PHP array to Yaml.
       // Convert PHP array to Yaml.
       $yamlServices = Yaml::dump($services, 4, 2);
       $yamlServices = Yaml::dump($services, 4, 2);
       $ok = file_put_contents($destination, $yamlServices, LOCK_EX);
       $ok = file_put_contents($destination, $yamlServices, LOCK_EX);
       if (!$ok) {
       if (!$ok) {
-        return [sprintf('Could not write new %s file', $destination), 2];
+        return 2;
       }
       }
     }
     }
 
 

+ 2 - 2
src/BuildSettingsCommand.php

@@ -65,10 +65,10 @@ EOT
 
 
     foreach ($params['sites'] as $name => $siteParams) {
     foreach ($params['sites'] as $name => $siteParams) {
       foreach (['build', 'run'] as $stage) {
       foreach (['build', 'run'] as $stage) {
-        $stageSettings = NestedArray::mergeDeepArray([
+        $stageSettings = MergeParamsCommand::mergeDeepArray([
           $siteParams['settings']['both'] ?? [],
           $siteParams['settings']['both'] ?? [],
           $siteParams['settings'][$stage] ?? [],
           $siteParams['settings'][$stage] ?? [],
-        ], TRUE);
+        ]);
         $stageParams = $siteParams;
         $stageParams = $siteParams;
         $stageParams['settings'] = $stageSettings;
         $stageParams['settings'] = $stageSettings;
         $context = [
         $context = [

+ 54 - 2
src/MergeParamsCommand.php

@@ -62,14 +62,14 @@ EOT
     }
     }
 
 
     // Merge local into defaults.
     // Merge local into defaults.
-    $merged = NestedArray::mergeDeepArray([$dist, $local], TRUE);
+    $merged = static::mergeDeepArray([$dist, $local]);
 
 
     // Generate per-site data from settings/_default.
     // Generate per-site data from settings/_default.
     $default = $merged['sites'][static::DEFAULT_SITE] ?? [];
     $default = $merged['sites'][static::DEFAULT_SITE] ?? [];
     $sites = array_diff_key($merged['sites'], [static::DEFAULT_SITE => NULL]);
     $sites = array_diff_key($merged['sites'], [static::DEFAULT_SITE => NULL]);
     $merged['sites'] = [];
     $merged['sites'] = [];
     foreach ($sites as $name => $params) {
     foreach ($sites as $name => $params) {
-      $merged['sites'][$name] = NestedArray::mergeDeep($default, $params);
+      $merged['sites'][$name] = static::mergeDeepArray([$default, $params]);
     }
     }
     return $merged;
     return $merged;
   }
   }
@@ -115,6 +115,58 @@ EOT
       $output->writeln("Failed to write merged params.");
       $output->writeln("Failed to write merged params.");
       return 2;
       return 2;
     }
     }
+
+    return 0;
+  }
+
+  /**
+   * Merges multiple arrays, recursively, and returns the merged array.
+   *
+   * Only keyed arrays are merged recursively, indexed arrays are not merged :
+   * the deepest array replace the less deep array. This is unlike the original
+   * function introduced in Drupal 8.x, from which this derives.
+   *
+   * @param array $arrays
+   *   An arrays of arrays to merge.
+   *
+   * @return array
+   *   The merged array.
+   *
+   * @see \Fgm\Drupal\Composer\MergeParamsCommandTest::testMergeDeepArray()
+   */
+  public static function mergeDeepArray(array $arrays) {
+
+    $result = [];
+    foreach ($arrays as $array) {
+      foreach ($array as $key => $value) {
+        // Remove keys having NULL value to allow deletion of key previously
+        // defined.
+        if ($value === NULL) {
+          unset($result[$key]);
+          continue;
+        }
+        // Renumber integer keys as array_merge_recursive() does. Note that
+        // PHP automatically converts array keys that are integer strings
+        // (e.g., '1') to integers.
+        if (is_int($key)) {
+          // When an indexed array is overrided, the new values replace
+          // all overrided values (no merge).
+          if ($key === 0) {
+            $result = [];
+          }
+          $result[] = $value;
+        }
+        // Recurse when both values are arrays.
+        elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
+          $result[$key] = static::mergeDeepArray([$result[$key], $value]);
+        }
+        // Otherwise, use the latter value, overriding any previous value.
+        else {
+          $result[$key] = $value;
+        }
+      }
+    }
+    return $result;
   }
   }
 
 
 }
 }

+ 0 - 381
src/NestedArray.php

@@ -1,381 +0,0 @@
-<?php
-
-// @codingStandardsIgnoreFile
-
-/**
- * @file
- * This is a copy of <Drupal 8.8>/core/lib/Drupal/Component/Utility/NestedArray.php
- *
- * It used to be autoloaded from a drupal/core-utility dependency, but in some
- * configurations, this triggers a Composer bug similar to:
- *  - https://github.com/wikimedia/composer-merge-plugin/issues/139
- *  - https://github.com/composer/installers/issues/427
- */
-
-namespace Fgm\Drupal\Composer;
-
-/**
- * Provides helpers to perform operations on nested arrays and array keys of variable depth.
- *
- * @ingroup utility
- */
-class NestedArray {
-
-  /**
-   * Retrieves a value from a nested array with variable depth.
-   *
-   * This helper function should be used when the depth of the array element
-   * being retrieved may vary (that is, the number of parent keys is variable).
-   * It is primarily used for form structures and renderable arrays.
-   *
-   * Without this helper function the only way to get a nested array value with
-   * variable depth in one line would be using eval(), which should be avoided:
-   * @code
-   * // Do not do this! Avoid eval().
-   * // May also throw a PHP notice, if the variable array keys do not exist.
-   * eval('$value = $array[\'' . implode("']['", $parents) . "'];");
-   * @endcode
-   *
-   * Instead, use this helper function:
-   * @code
-   * $value = NestedArray::getValue($form, $parents);
-   * @endcode
-   *
-   * A return value of NULL is ambiguous, and can mean either that the requested
-   * key does not exist, or that the actual value is NULL. If it is required to
-   * know whether the nested array key actually exists, pass a third argument
-   * that is altered by reference:
-   * @code
-   * $key_exists = NULL;
-   * $value = NestedArray::getValue($form, $parents, $key_exists);
-   * if ($key_exists) {
-   *   // Do something with $value.
-   * }
-   * @endcode
-   *
-   * However if the number of array parent keys is static, the value should
-   * always be retrieved directly rather than calling this function.
-   * For instance:
-   * @code
-   * $value = $form['signature_settings']['signature'];
-   * @endcode
-   *
-   * @param array $array
-   *   The array from which to get the value.
-   * @param array $parents
-   *   An array of parent keys of the value, starting with the outermost key.
-   * @param bool $key_exists
-   *   (optional) If given, an already defined variable that is altered by
-   *   reference.
-   *
-   * @return mixed
-   *   The requested nested value. Possibly NULL if the value is NULL or not all
-   *   nested parent keys exist. $key_exists is altered by reference and is a
-   *   Boolean that indicates whether all nested parent keys exist (TRUE) or not
-   *   (FALSE). This allows to distinguish between the two possibilities when
-   *   NULL is returned.
-   *
-   * @see NestedArray::setValue()
-   * @see NestedArray::unsetValue()
-   */
-  public static function &getValue(array &$array, array $parents, &$key_exists = NULL) {
-    $ref = &$array;
-    foreach ($parents as $parent) {
-      if (is_array($ref) && (isset($ref[$parent]) || array_key_exists($parent, $ref))) {
-        $ref = &$ref[$parent];
-      }
-      else {
-        $key_exists = FALSE;
-        $null = NULL;
-        return $null;
-      }
-    }
-    $key_exists = TRUE;
-    return $ref;
-  }
-
-  /**
-   * Sets a value in a nested array with variable depth.
-   *
-   * This helper function should be used when the depth of the array element you
-   * are changing may vary (that is, the number of parent keys is variable). It
-   * is primarily used for form structures and renderable arrays.
-   *
-   * Example:
-   * @code
-   * // Assume you have a 'signature' element somewhere in a form. It might be:
-   * $form['signature_settings']['signature'] = array(
-   *   '#type' => 'text_format',
-   *   '#title' => t('Signature'),
-   * );
-   * // Or, it might be further nested:
-   * $form['signature_settings']['user']['signature'] = array(
-   *   '#type' => 'text_format',
-   *   '#title' => t('Signature'),
-   * );
-   * @endcode
-   *
-   * To deal with the situation, the code needs to figure out the route to the
-   * element, given an array of parents that is either
-   * @code array('signature_settings', 'signature') @endcode
-   * in the first case or
-   * @code array('signature_settings', 'user', 'signature') @endcode
-   * in the second case.
-   *
-   * Without this helper function the only way to set the signature element in
-   * one line would be using eval(), which should be avoided:
-   * @code
-   * // Do not do this! Avoid eval().
-   * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;');
-   * @endcode
-   *
-   * Instead, use this helper function:
-   * @code
-   * NestedArray::setValue($form, $parents, $element);
-   * @endcode
-   *
-   * However if the number of array parent keys is static, the value should
-   * always be set directly rather than calling this function. For instance,
-   * for the first example we could just do:
-   * @code
-   * $form['signature_settings']['signature'] = $element;
-   * @endcode
-   *
-   * @param array $array
-   *   A reference to the array to modify.
-   * @param array $parents
-   *   An array of parent keys, starting with the outermost key.
-   * @param mixed $value
-   *   The value to set.
-   * @param bool $force
-   *   (optional) If TRUE, the value is forced into the structure even if it
-   *   requires the deletion of an already existing non-array parent value. If
-   *   FALSE, PHP throws an error if trying to add into a value that is not an
-   *   array. Defaults to FALSE.
-   *
-   * @see NestedArray::unsetValue()
-   * @see NestedArray::getValue()
-   */
-  public static function setValue(array &$array, array $parents, $value, $force = FALSE) {
-    $ref = &$array;
-    foreach ($parents as $parent) {
-      // PHP auto-creates container arrays and NULL entries without error if $ref
-      // is NULL, but throws an error if $ref is set, but not an array.
-      if ($force && isset($ref) && !is_array($ref)) {
-        $ref = [];
-      }
-      $ref = &$ref[$parent];
-    }
-    $ref = $value;
-  }
-
-  /**
-   * Unsets a value in a nested array with variable depth.
-   *
-   * This helper function should be used when the depth of the array element you
-   * are changing may vary (that is, the number of parent keys is variable). It
-   * is primarily used for form structures and renderable arrays.
-   *
-   * Example:
-   * @code
-   * // Assume you have a 'signature' element somewhere in a form. It might be:
-   * $form['signature_settings']['signature'] = array(
-   *   '#type' => 'text_format',
-   *   '#title' => t('Signature'),
-   * );
-   * // Or, it might be further nested:
-   * $form['signature_settings']['user']['signature'] = array(
-   *   '#type' => 'text_format',
-   *   '#title' => t('Signature'),
-   * );
-   * @endcode
-   *
-   * To deal with the situation, the code needs to figure out the route to the
-   * element, given an array of parents that is either
-   * @code array('signature_settings', 'signature') @endcode
-   * in the first case or
-   * @code array('signature_settings', 'user', 'signature') @endcode
-   * in the second case.
-   *
-   * Without this helper function the only way to unset the signature element in
-   * one line would be using eval(), which should be avoided:
-   * @code
-   * // Do not do this! Avoid eval().
-   * eval('unset($form[\'' . implode("']['", $parents) . '\']);');
-   * @endcode
-   *
-   * Instead, use this helper function:
-   * @code
-   * NestedArray::unset_nested_value($form, $parents, $element);
-   * @endcode
-   *
-   * However if the number of array parent keys is static, the value should
-   * always be set directly rather than calling this function. For instance, for
-   * the first example we could just do:
-   * @code
-   * unset($form['signature_settings']['signature']);
-   * @endcode
-   *
-   * @param array $array
-   *   A reference to the array to modify.
-   * @param array $parents
-   *   An array of parent keys, starting with the outermost key and including
-   *   the key to be unset.
-   * @param bool $key_existed
-   *   (optional) If given, an already defined variable that is altered by
-   *   reference.
-   *
-   * @see NestedArray::setValue()
-   * @see NestedArray::getValue()
-   */
-  public static function unsetValue(array &$array, array $parents, &$key_existed = NULL) {
-    $unset_key = array_pop($parents);
-    $ref = &self::getValue($array, $parents, $key_existed);
-    if ($key_existed && is_array($ref) && (isset($ref[$unset_key]) || array_key_exists($unset_key, $ref))) {
-      $key_existed = TRUE;
-      unset($ref[$unset_key]);
-    }
-    else {
-      $key_existed = FALSE;
-    }
-  }
-
-  /**
-   * Determines whether a nested array contains the requested keys.
-   *
-   * This helper function should be used when the depth of the array element to
-   * be checked may vary (that is, the number of parent keys is variable). See
-   * NestedArray::setValue() for details. It is primarily used for form
-   * structures and renderable arrays.
-   *
-   * If it is required to also get the value of the checked nested key, use
-   * NestedArray::getValue() instead.
-   *
-   * If the number of array parent keys is static, this helper function is
-   * unnecessary and the following code can be used instead:
-   * @code
-   * $value_exists = isset($form['signature_settings']['signature']);
-   * $key_exists = array_key_exists('signature', $form['signature_settings']);
-   * @endcode
-   *
-   * @param array $array
-   *   The array with the value to check for.
-   * @param array $parents
-   *   An array of parent keys of the value, starting with the outermost key.
-   *
-   * @return bool
-   *   TRUE if all the parent keys exist, FALSE otherwise.
-   *
-   * @see NestedArray::getValue()
-   */
-  public static function keyExists(array $array, array $parents) {
-    // Although this function is similar to PHP's array_key_exists(), its
-    // arguments should be consistent with getValue().
-    $key_exists = NULL;
-    self::getValue($array, $parents, $key_exists);
-    return $key_exists;
-  }
-
-  /**
-   * Merges multiple arrays, recursively, and returns the merged array.
-   *
-   * This function is similar to PHP's array_merge_recursive() function, but it
-   * handles non-array values differently. When merging values that are not both
-   * arrays, the latter value replaces the former rather than merging with it.
-   *
-   * Example:
-   * @code
-   * $link_options_1 = array('fragment' => 'x', 'attributes' => array('title' => t('X'), 'class' => array('a', 'b')));
-   * $link_options_2 = array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('c', 'd')));
-   *
-   * // This results in array('fragment' => array('x', 'y'), 'attributes' => array('title' => array(t('X'), t('Y')), 'class' => array('a', 'b', 'c', 'd'))).
-   * $incorrect = array_merge_recursive($link_options_1, $link_options_2);
-   *
-   * // This results in array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('a', 'b', 'c', 'd'))).
-   * $correct = NestedArray::mergeDeep($link_options_1, $link_options_2);
-   * @endcode
-   *
-   * @param array ...
-   *   Arrays to merge.
-   *
-   * @return array
-   *   The merged array.
-   *
-   * @see NestedArray::mergeDeepArray()
-   */
-  public static function mergeDeep() {
-    return self::mergeDeepArray(func_get_args());
-  }
-
-  /**
-   * Merges multiple arrays, recursively, and returns the merged array.
-   *
-   * This function is equivalent to NestedArray::mergeDeep(), except the
-   * input arrays are passed as a single array parameter rather than a variable
-   * parameter list.
-   *
-   * The following are equivalent:
-   * - NestedArray::mergeDeep($a, $b);
-   * - NestedArray::mergeDeepArray(array($a, $b));
-   *
-   * The following are also equivalent:
-   * - call_user_func_array('NestedArray::mergeDeep', $arrays_to_merge);
-   * - NestedArray::mergeDeepArray($arrays_to_merge);
-   *
-   * @param array $arrays
-   *   An arrays of arrays to merge.
-   * @param bool $preserve_integer_keys
-   *   (optional) If given, integer keys will be preserved and merged instead of
-   *   appended. Defaults to FALSE.
-   *
-   * @return array
-   *   The merged array.
-   *
-   * @see NestedArray::mergeDeep()
-   */
-  public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FALSE) {
-    $result = [];
-    foreach ($arrays as $array) {
-      foreach ($array as $key => $value) {
-        // Renumber integer keys as array_merge_recursive() does unless
-        // $preserve_integer_keys is set to TRUE. Note that PHP automatically
-        // converts array keys that are integer strings (e.g., '1') to integers.
-        if (is_int($key) && !$preserve_integer_keys) {
-          $result[] = $value;
-        }
-        // Recurse when both values are arrays.
-        elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
-          $result[$key] = self::mergeDeepArray([$result[$key], $value], $preserve_integer_keys);
-        }
-        // Otherwise, use the latter value, overriding any previous value.
-        else {
-          $result[$key] = $value;
-        }
-      }
-    }
-    return $result;
-  }
-
-  /**
-   * Filters a nested array recursively.
-   *
-   * @param array $array
-   *   The filtered nested array.
-   * @param callable|null $callable
-   *   The callable to apply for filtering.
-   *
-   * @return array
-   *   The filtered array.
-   */
-  public static function filter(array $array, callable $callable = NULL) {
-    $array = is_callable($callable) ? array_filter($array, $callable) : array_filter($array);
-    foreach ($array as &$element) {
-      if (is_array($element)) {
-        $element = static::filter($element, $callable);
-      }
-    }
-
-    return $array;
-  }
-
-}

+ 4 - 5
src/PhpMemcacheAdminConfigCommand.php

@@ -4,8 +4,7 @@ declare(strict_types=1);
 
 
 namespace Fgm\Drupal\Composer;
 namespace Fgm\Drupal\Composer;
 
 
-use Composer\Package\CompletePackageInterface;
-use Osinet\Deploy\BuilderBase;
+use Composer\Package\PackageInterface;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputDefinition;
 use Symfony\Component\Console\Input\InputDefinition;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputInterface;
@@ -125,8 +124,8 @@ EOT
     $variables = array_merge(
     $variables = array_merge(
       $params[static::BUILD_NAME]['basic'],
       $params[static::BUILD_NAME]['basic'],
       $params[static::BUILD_NAME]['advanced'], [
       $params[static::BUILD_NAME]['advanced'], [
-      'servers' => $params['memcache']['servers'],
-    ],
+        'servers' => $params['memcache']['servers'],
+      ],
       ['local_params_path' => realpath($this->getSettingsPath() . "/params.local.yml")]
       ['local_params_path' => realpath($this->getSettingsPath() . "/params.local.yml")]
     );
     );
     return $variables;
     return $variables;
@@ -144,7 +143,7 @@ EOT
       ->getRepositoryManager()
       ->getRepositoryManager()
       ->getLocalRepository()
       ->getLocalRepository()
       ->getPackages();
       ->getPackages();
-    $package = current(array_filter($packages, function (CompletePackageInterface $package) {
+    $package = current(array_filter($packages, function (PackageInterface $package) {
       return $package->getName() === static::PACKAGE;
       return $package->getName() === static::PACKAGE;
     }));
     }));
     $targetBase = $composer
     $targetBase = $composer

+ 70 - 0
tests/MergeParamsCommandTest.php

@@ -0,0 +1,70 @@
+<?php
+declare(strict_types=1);
+
+namespace Fgm\Drupal\Composer;
+
+use PHPUnit\Framework\TestCase;
+
+final class MergeParamsCommandTest extends TestCase {
+
+  /**
+   * Test MergeParamsCommand::merge().
+   *
+   * @covers \Fgm\Drupal\Composer\MergeParamsCommand::mergeDeepArray
+   */
+  public function testMergeDeepArray() {
+    // Base array (like a dist.params.local.yml).
+    $dist = [
+      'keyedArray1' => [
+        'key1' => 'dist1',
+        'key2' => 'dist2',
+        'key3' => 'dist3',
+      ],
+      'indexedArray1' => [
+        'dist1',
+        'dist2',
+        'dist3',
+      ],
+    ];
+    // Overriding array (like params.local.yml).
+    $local = [
+      'keyedArray1' => [
+        'key1' => 'local1',
+        'key3' => NULL,
+        'key4' => 'local4',
+      ],
+      'keyedArray2' => [
+        'key1' => 'local1',
+      ],
+      'indexedArray1' => [
+        'local1',
+        'local2',
+      ],
+    ];
+    // Merged array (like merged.params.local.yml).
+    $merged = MergeParamsCommand::mergeDeepArray([$dist, $local]);
+    $expected = [
+      'keyedArray1' => [
+        // Key1 : Dist value is overrided by local value.
+        'key1' => 'local1',
+        // Key2 : No overriding in local, dist value is transmitted.
+        'key2' => 'dist2',
+        // Key3 : Dist value is overrided with a NULL value: key3 is removed.
+        // Key4 : No key4 in dist array, local Key4 is added.
+        'key4' => 'local4',
+      ],
+      // No Array2 in dist, local Array2 is added.
+      'keyedArray2' => [
+        'key1' => 'local1',
+      ],
+      // Dist indexed array is replaced by local index array.
+      'indexedArray1' => [
+        'local1',
+        'local2',
+      ],
+    ];
+
+    $this->assertEquals($expected, $merged);
+  }
+
+}