ZF-4319: solution for Transfer-Adapter's file-hashmap structure defect, supporting subforms

Description

The problem which causes file upload to fail is about the internal key naming of upload files. For subforms files are mapped to a key-array-pair rather than a key-string-pair. So no string hashmap is built internal.

For me following fix works fine. I've tested with nested subform depths 0 to 3.

Testcode:

 
        // Creating form
        $form = new Zend_Form();
        $form->setEnctype('multipart/form-data');
        
        // File Upload Element
        $element = new Zend_Form_Element_File('file1', array('label' => 'Datei'));
        $element->setRequired(TRUE);

        // Test1: no subform
        $form->addElement($element);
        
        // Test2: simple subform
        //$subformFile = new Zend_Form_SubForm();
        //$subformFile->addElement($element);
        //$form->addSubform($subformFile, 'subformFile');
        
        // Test3: one nested subform
        //$subformFile = new Zend_Form_SubForm();
        //$subformFile->addElement($element);
        //$subform1 = new Zend_Form_SubForm();
        //$subform1->addSubform($subformFile, 'subformFile');
        //$form->addSubform($subform1, 'subform1');
        
        // Test4: doublenested subform
        //$subformFile = new Zend_Form_SubForm();
        //$subformFile->addElement($element);
        //$subform1 = new Zend_Form_SubForm();
        //$subform1->addSubform($subformFile, 'subformFile');
        //$subform2 = new Zend_Form_SubForm();
        //$subform2->addSubform($subform1, 'subform1');
        //$form->addSubform($subform2, 'subform2');
        
        // Test5: triplenested subform
        //$subformFile = new Zend_Form_SubForm();
        //$subformFile->addElement($element);
        //$subform1 = new Zend_Form_SubForm();
        //$subform1->addSubform($subformFile, 'subformFile');
        //$subform2 = new Zend_Form_SubForm();
        //$subform2->addSubform($subform1, 'subform1');
        //$subform3 = new Zend_Form_SubForm();
        //$subform3->addSubform($subform2, 'subform2');
        //$form->addSubform($subform3, 'subform3');

Fix in Zend_File_Transfer_Adapter_Http:

 
    /**
     * Building file key an returns value, includes nested subform support.
     * 
     * Example:
     *      The given key looks like this:
     *      'subform3__subform2'
     *      
     *      The given array looks like this:
     *      array(1) {
     *              ["subform1"]=>
     *              array(1) {
     *                  ["subformFile"]=>
     *                  array(1) {
     *                      ["file1"]=>
     *                      string(0) "/uploaded/file.ext"
     *                  }
     *              }
     *      }
     *      
     *      The returned key would be:
     *      'subform3__subform2__subform1__subformFile__file1'
     *      
     *      The returned value would be: '/uploaded/file.ext'
     * 
     * @param array $fileKeyValue
     * @param string $key Return key
     * @return string Value
     */
    protected function _getFileKeyValue(array $fileKeyValue, &$key) {
        list($keyAppend, $innerFileKeyValue) = each($fileKeyValue);
        $key .= '__'.$keyAppend;
        
        if (is_array($innerFileKeyValue)) {
            $innerFileKeyValue = $this->_getFileKeyValue($innerFileKeyValue, $key);
        }
        
        return $innerFileKeyValue;
    }

    /**
     * Prepare the $_FILES array to match the internal syntax of one file per entry
     *
     * @param  array $files
     * @return array
     */
    protected function _prepareFiles(array $files = array())
    {
        $result = array();
        foreach ($files as $form => $content) {
            if (is_array($content['name'])) {
                foreach ($content as $param => $file) {
                    $key = $form;
                    $value = $this->_getFileKeyValue($content[$param], $key);
                    $result[$key][$param] = $value;
                }
            } else {
                $result[$form] = $content;
            }
        }
        return $result;
    }

=====================================================================================================

Damned, i didn't look to multiple files. Sorry.

I don't know why, but i don't have rights to comment any issues here, so let me append my answer. Hope it helps.

Testcode (not patched, just ZF 1.6.0):

 
        // Creating form
        $form = new Zend_Form();
        $form->setEnctype('multipart/form-data');
        
        // 2 File Upload Elements
        $element = new Zend_Form_Element_File('file1', array('label' => 'Datei'));
        $element->setRequired(TRUE);
        $element2 = new Zend_Form_Element_File('file2', array('label' => 'Datei2'));
        $element2->setRequired(TRUE);

        // triplenested subform - 2 elements        
        $subformFile = new Zend_Form_SubForm();
        $subformFile->addElement($element);
        $subformFile->addElement($element2);
        $subform1 = new Zend_Form_SubForm();
        $subform1->addSubform($subformFile, 'subformFile');
        $subform2 = new Zend_Form_SubForm();
        $subform2->addSubform($subform1, 'subform1');
        $subform3 = new Zend_Form_SubForm();
        $subform3->addSubform($subform2, 'subform2');
        $form->addSubform($subform3, 'subform3');
        
        var_dump($_FILES);
        var_dump($element->getFullyQualifiedName());
        var_dump($element->getId());
        
        var_dump($element2->getFullyQualifiedName());
        var_dump($element2->getId());
        
        var_dump($subformFile->getFullyQualifiedName());
        var_dump($subformFile->getId());
        
        var_dump($subform1->getFullyQualifiedName());
        var_dump($subform1->getId());
        
        var_dump($subform2->getFullyQualifiedName());
        var_dump($subform2->getId());
        
        var_dump($subform3->getFullyQualifiedName());
        var_dump($subform3->getId());
        
        var_dump($form->getFullyQualifiedName());
        var_dump($form->getId());

dump:

 
array(1) {
  ["subform3"]=>
  array(5) {
    ["name"]=>
    array(1) {
      ["subform2"]=>
      array(1) {
        ["subform1"]=>
        array(1) {
          ["subformFile"]=>
          array(2) {
            ["file1"]=>
            string(0) ""
            ["file2"]=>
            string(0) ""
          }
        }
      }
    }
    ["type"]=>
    array(1) {
      ["subform2"]=>
      array(1) {
        ["subform1"]=>
        array(1) {
          ["subformFile"]=>
          array(2) {
            ["file1"]=>
            string(0) ""
            ["file2"]=>
            string(0) ""
          }
        }
      }
    }
    ["tmp_name"]=>
    array(1) {
      ["subform2"]=>
      array(1) {
        ["subform1"]=>
        array(1) {
          ["subformFile"]=>
          array(2) {
            ["file1"]=>
            string(0) ""
            ["file2"]=>
            string(0) ""
          }
        }
      }
    }
    ["error"]=>
    array(1) {
      ["subform2"]=>
      array(1) {
        ["subform1"]=>
        array(1) {
          ["subformFile"]=>
          array(2) {
            ["file1"]=>
            int(4)
            ["file2"]=>
            int(4)
          }
        }
      }
    }
    ["size"]=>
    array(1) {
      ["subform2"]=>
      array(1) {
        ["subform1"]=>
        array(1) {
          ["subformFile"]=>
          array(2) {
            ["file1"]=>
            int(0)
            ["file2"]=>
            int(0)
          }
        }
      }
    }
  }
}
string(5) "file1"
string(5) "file1"
string(5) "file2"
string(5) "file2"
string(11) "subformFile"
string(11) "subformFile"
string(8) "subform1"
string(8) "subform1"
string(8) "subform2"
string(8) "subform2"
string(8) "subform3"
string(8) "subform3"
NULL
NULL

Comments

I'm sorry, but this is not a proper solution. Set 2 file elements in every form/subform and you will see why your solution does not work.

But you can help me out: Can you give me your $_FILES array using this 3-nested form (with 2 file elements) ? Also the value of getFullyQualifiedName() and getId() would be interesting. And when you set a different name and a different id in the form.

The solution I have worked out will be available in a few days because we always need unittests and documentation before doing a commit.

This solution does not work. A working solution has been added with r11518

Changing issues in preparation for the 1.7.0 release.