Issues

ZF-5222: Zend_Form::getValues() mishandles SubForms data when elementsBelongTo is set

Description

When setting elementsBelongTo for a Zend_Form_SubForm to either an empty value '' (isArray() == false) or to an array such as "__children[Employees][Company][__ids][pk_33]" these methods do not handle the arrays properly:

getValues() getErrors() getMessages()

1.) Not all subforms are arrays, such as when elementsBelongTo is set empty. This causes extra levels in the returned array structure due to calling _attachToArray() with empty $arrayPath. Analogous calls to _dissolveArray() correctly check for isArray().

2.) When collecting data recursively from subforms, only data from the last subform is kept because array_merge() overwrites array entries where the first key is the same among the subforms. Note that array_merge() works for the simple case where just the subform name (or simple elementsBelongTo) is used for the value keys. Using array_merge_recursive() instead of array_merge() fixes this bug when elementsBelongTo is set to a deeper array structure.

For example, sibling subforms may typically have elementsBelongTo set to common base arrays with only the last key differing:

__children[Employees][Company][__ids][pk_33]
__children[Employees][Company][__ids][pk_31]
__children[Employees][Company][__ids][pk_58]

Here is a proposed patch:

Index: Zend/Form.php
===================================================================
--- Zend/Form.php   (revision 11917)
+++ Zend/Form.php   (working copy)
@@ -1282,8 +1282,20 @@
             }
         }
         foreach ($this->getSubForms() as $key => $subForm) {
-            $fValues = $this->_attachToArray($subForm->getValues(true), $subForm->getElementsBelongTo());
-            $values = array_merge($values, $fValues);
+            $fValues = $subForm->getValues(true);
+            
+            // Not all subForms are arrays, such as when elementsBelongTo is set empty
+            if ($subForm->isArray()) {
+                $fValues = $this->_attachToArray($fValues, $subForm->getElementsBelongTo());
+            }
+
+            // Need to use array_merge_recursive() instead of array_merge() because
+            // elementsBelongTo when set for sibling subForms may typically
+            // have common base arrays; for example:
+            //     __children[Users][ManagementCompany][__ids][pk_33]
+            //     __children[Users][ManagementCompany][__ids][pk_31]
+            //     __children[Users][ManagementCompany][__ids][pk_58]
+            $values = array_merge_recursive($values, $fValues);
         }
 
         if (!$suppressArrayNotation && $this->isArray()) {
@@ -2208,8 +2220,20 @@
                 $errors[$key] = $element->getErrors();
             }
             foreach ($this->getSubForms() as $key => $subForm) {
-                $fErrors = $this->_attachToArray($subForm->getErrors(), $subForm->getElementsBelongTo());
-                $errors = array_merge($errors, $fErrors);
+                $fErrors = $subForm->getErrors();
+                
+                // Not all subForms are arrays, such as when elementsBelongTo is set empty
+                if ($subForm->isArray()) {
+                    $fErrors = $this->_attachToArray($fErrors, $subForm->getElementsBelongTo());
+                }
+
+                // Need to use array_merge_recursive() instead of array_merge() because
+                // elementsBelongTo when set for sibling subForms may typically
+                // have common base arrays; for example:
+                //     __children[Users][ManagementCompany][__ids][pk_33]
+                //     __children[Users][ManagementCompany][__ids][pk_31]
+                //     __children[Users][ManagementCompany][__ids][pk_58]
+                $errors = array_merge_recursive($errors, $fErrors);
             }
         }
         return $errors;
@@ -2261,8 +2285,18 @@
             $fMessages = $subForm->getMessages(null, true);
             if (!empty($fMessages)) {
                 if (array_key_exists($key, $arrayKeys)) {
-                    $fMessages = $this->_attachToArray($fMessages, $arrayKeys[$key]);
-                    $messages = array_merge($messages, $fMessages);
+                    // Not all subForms are arrays, such as when elementsBelongTo is set empty
+                    if ($subForm->isArray()) {
+                        $fMessages = $this->_attachToArray($fMessages, $arrayKeys[$key]);
+                    }
+
+                    // Need to use array_merge_recursive() instead of array_merge() because
+                    // elementsBelongTo when set for sibling subForms may typically
+                    // have common base arrays; for example:
+                    //     __children[Users][ManagementCompany][__ids][pk_33]
+                    //     __children[Users][ManagementCompany][__ids][pk_31]
+                    //     __children[Users][ManagementCompany][__ids][pk_58]
+                    $messages = array_merge_recursive($messages, $fMessages);
                 } else {
                     $messages[$key] = $fMessages;
                 }

Comments

getValues() is Fixed in [ZF-9584] and in [ZF-9586].

getErrors Fixed in [ZF-9467]

Reopened because suggested fix is not reviewed and committed yet.

getMessages Fixed in ZF-9593

Resolving as duplicate of ZF-9584, ZF-9586, ZF-9467 and ZF-9593