Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

3.0 remove deep merging from mergeVars #2922

Merged
merged 2 commits into from

4 participants

@markstory
Owner

As per #2914. This removes deep merging from the MergeVariablesTrait. Instead only the top level keys are merged for associative properties.
List style properties are still simply merged.

Hopefully, this new behavior makes merged properties simpler to understand with more predictable results.

markstory added some commits
@markstory markstory Add test for merging associative properties.
Associated/hash properties should be merged like
dictionaries/array_merge.

Refs #2914
d2cde87
@markstory markstory Do not merge the contents of merged properties.
Merging the contents/configuration in merged properties often results in
the wrong answer as non-associative values will be duplicated N times
where N is the number of ancestors with the same value. Instead, only
merge missing top level items. The config for each item will be taken
from the top most class it is defined in.

Refs #2914
1a9a8fb
@markstory markstory added this to the 3.0.0 milestone
@lorenzo
Owner

This makes sense to me, but I think it shows a more fundamental issue. We often use AppController and AppModel for settings global behavior, for example you want AuthComponent in all controllers, but one controller in particular needs an additional authorization adapter. This is almost imposible to achieve using the magic merged vars.

I think we should encourage another style of declaring common behavior in 3.0, that is not automagic variable merging.

@AD7six AD7six commented on the diff
src/Utility/MergeVariablesTrait.php
((18 lines not shown))
}
$this->{$property} = $thisValue;
}
+/**
+ * Merge each of the keys in a property together.
+ *
+ * @param array $current The current merged value.
+ * @param array $parent The parent class' value.
+ * @param boolean $isAssoc Whether or not the merging should be done in associative mode.
+ * @return mixed The updated value.
+ */
+ protected function _mergePropertyData($current, $parent, $isAssoc) {
+ if (!$isAssoc) {
+ return array_merge($parent, $current);
+ }
+ $parent = Hash::normalize($parent);
+ foreach ($parent as $key => $value) {
@AD7six Collaborator
AD7six added a note

Is this foreach loop any different than

$current += $parent

?

After reading the discussion of removing merging from inherited properties (:+1:) do we need a trait? Is this doing any more than "normalize and += parent/defaults" ?

@markstory Owner

The only difference vs. + would be around nulls. I think a trait is still useful as this behavior is still used in shells as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dereuromark
Collaborator

That reminds me of my ticket with 2.x and merge issues when aliasing helpers and components in AppController. Once a child (or plugin child) controller has Form as $helpers value, the aliasing ('Form' => array('className' => 'PluginName.FormExt')) is lost/overwritten again.
It would be nice if there was some better way in general to allow proper merging with such a setup and without too much magic.
In such a case for instance, it would be preferred for the parent controller (AppController) to have precedence (as you cannot just modify third party plugins that easily). But that cannot always be assumed, of course.

@AD7six
Collaborator

@dereuromark I'm more or less of the opinion that default merging logic should be as simple as possible (in principle: instance $var is $var += parent::$var), and for anything beyond that e.g. users are encouraged to override _mergeControllerVars (or class equivalent) - that would remove the "what's going to happen here"-ness of the existing implementation where users want different behavior even within the same nested array structure.

@dereuromark
Collaborator

@AD7six I agree. That allows both ways and certainly makes most sense.

@markstory
Owner

@lorenzo Are you thinking an initialize() hook like we added in the orm?

@lorenzo
Owner

@markstory Yes, that is exactly what I'm thinking

@lorenzo
Owner

Merging this as there were no further comments and any future actions can be addressed in separate pull requests

@lorenzo lorenzo merged commit eff4f0b into from
@lorenzo lorenzo deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 1, 2014
  1. @markstory

    Add test for merging associative properties.

    markstory authored
    Associated/hash properties should be merged like
    dictionaries/array_merge.
    
    Refs #2914
  2. @markstory

    Do not merge the contents of merged properties.

    markstory authored
    Merging the contents/configuration in merged properties often results in
    the wrong answer as non-associative values will be duplicated N times
    where N is the number of ancestors with the same value. Instead, only
    merge missing top level items. The config for each item will be taken
    from the top most class it is defined in.
    
    Refs #2914
This page is out of date. Refresh to see the latest.
View
30 src/Utility/MergeVariablesTrait.php
@@ -80,19 +80,37 @@ protected function _mergeProperty($property, $parentClasses, $options) {
}
foreach ($parentClasses as $class) {
$parentProperties = get_class_vars($class);
- if (!isset($parentProperties[$property])) {
+ if (empty($parentProperties[$property])) {
continue;
}
$parentProperty = $parentProperties[$property];
- if (empty($parentProperty) || $parentProperty === true) {
+ if (!is_array($parentProperty)) {
continue;
}
- if ($isAssoc) {
- $parentProperty = Hash::normalize($parentProperty);
- }
- $thisValue = Hash::merge($parentProperty, $thisValue);
+ $thisValue = $this->_mergePropertyData($thisValue, $parentProperty, $isAssoc);
}
$this->{$property} = $thisValue;
}
+/**
+ * Merge each of the keys in a property together.
+ *
+ * @param array $current The current merged value.
+ * @param array $parent The parent class' value.
+ * @param boolean $isAssoc Whether or not the merging should be done in associative mode.
+ * @return mixed The updated value.
+ */
+ protected function _mergePropertyData($current, $parent, $isAssoc) {
+ if (!$isAssoc) {
+ return array_merge($parent, $current);
+ }
+ $parent = Hash::normalize($parent);
+ foreach ($parent as $key => $value) {
@AD7six Collaborator
AD7six added a note

Is this foreach loop any different than

$current += $parent

?

After reading the discussion of removing merging from inherited properties (:+1:) do we need a trait? Is this doing any more than "normalize and += parent/defaults" ?

@markstory Owner

The only difference vs. + would be around nulls. I think a trait is still useful as this behavior is still used in shells as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ if (!isset($current[$key])) {
+ $current[$key] = $value;
+ }
+ }
+ return $current;
+ }
+
}
View
47 tests/TestCase/Utility/MergeVariablesTraitTest.php
@@ -44,6 +44,15 @@ class Child extends Base {
'Orange'
];
+ public $nestedProperty = [
+ 'Red' => [
+ 'apple' => 'gala',
+ ],
+ 'Green' => [
+ 'citrus' => 'lime'
+ ],
+ ];
+
}
class Grandchild extends Child {
@@ -54,6 +63,15 @@ class Grandchild extends Child {
'Green' => ['apple'],
'Yellow' => ['banana']
];
+
+ public $nestedProperty = [
+ 'Red' => [
+ 'citrus' => 'blood orange',
+ ],
+ 'Green' => [
+ 'citrus' => 'key lime'
+ ],
+ ];
}
/**
@@ -76,7 +94,7 @@ public function testMergeVarsAsList() {
}
/**
- * Test merging vars as an assoc list.
+ * Test merging vars as an associative list.
*
* @return void
*/
@@ -86,14 +104,37 @@ public function testMergeVarsAsAssoc() {
$expected = [
'Red' => null,
'Orange' => null,
- 'Green' => ['lime', 'apple'],
+ 'Green' => ['apple'],
'Yellow' => ['banana'],
];
$this->assertEquals($expected, $object->assocProperty);
}
/**
+ * Test merging variable in associated properties that contain
+ * additional keys.
+ *
+ * @return void
+ */
+ public function testMergeVarsAsAssocWithKeyValues() {
+ $object = new Grandchild();
+ $object->mergeVars(['nestedProperty'], ['associative' => ['nestedProperty']]);
+
+ $expected = [
+ 'Red' => [
+ 'citrus' => 'blood orange',
+ ],
+ 'Green' => [
+ 'citrus' => 'key lime',
+ ],
+ ];
+ $this->assertEquals($expected, $object->nestedProperty);
+ }
+
+/**
* Test merging vars with mixed modes.
+ *
+ * @return void
*/
public function testMergeVarsMixedModes() {
$object = new Grandchild();
@@ -101,7 +142,7 @@ public function testMergeVarsMixedModes() {
$expected = [
'Red' => null,
'Orange' => null,
- 'Green' => ['lime', 'apple'],
+ 'Green' => ['apple'],
'Yellow' => ['banana'],
];
$this->assertEquals($expected, $object->assocProperty);
Something went wrong with that request. Please try again.