symfony: [2.2] Allow Form Crawler to add missing inputs when submitting

Often on web page load js generates additional inputs and adds them to the FORM DOM element, for example it happens often for adding browser and system information, if available.

When we use Form then and pass values for inputs to be set, Form class says then something like “I cannot reach these inputs” - because they initially where not in the HTML loaded.

Adding some bool parameter like createMissingInputs may help in such a case. Otherwise we have to first manually add input dom nodes to the form node, and only then call ->form() method. Passing an array of desired inputs will simplify Form usage in such a case.

About this issue

  • Original URL
  • State: closed
  • Created 12 years ago
  • Comments: 17 (13 by maintainers)

Most upvoted comments

I just had the same problem and created a more sophisticated workaround. My html looks something like this:

<div id="collection_type" data-prototype="[html]" data-prototype-name="[placeholder]">

So I created a method like this:

    /**
     * @param \DOMElement $node
     * @param string $index
     */
    protected function appendPrototypeDom(\DOMElement $node, $index = '0')
    {
        $prototypeHTML = $node->getAttribute('data-prototype');
        $prototypeHTML = str_replace($node->getAttribute('data-prototype-name'), $index, $prototypeHTML);
        $prototypeFragment = new \DOMDocument();
        $prototypeFragment->loadHTML($prototypeHTML);
        foreach ($prototypeFragment->getElementsByTagName('body')->item(0)->childNodes as $prototypeNode) {
            $node->appendChild($node->ownerDocument->importNode($prototypeNode, true));
        }
    }

Which can then simply be used before creating the form instance like this:

        $formCrawler = $crawler->filter('form[name="form_name"]');
        $this->appendPrototypeDom($crawler->filter('#collection_type')->getNode(0), '1');
        $form = $formCrawler->form();
        $form["some_field_that_wasnt_there_before"] = 'value';

This is a more accurate test because all form fields are submitted like the browser would. Collection elements can be removed in the same way. The only important thing is to create the form instance after all dom changes are applied.

@Nemo64 's solution is fine. I just propose a small improvement to make it more ready to use

       /**
     * @param \DOMElement $node
     * @param int $currentIndex
     * @param int $count
     */
    protected function appendPrototypeDom(\DOMElement $node, $currentIndex = 0, $count = 1)
    {
        $prototypeHTML = $node->getAttribute('data-prototype');
        $accumulatedHtml = '';
        for ($i = 0; $i < $count; $i++) {
            $accumulatedHtml .= str_replace('__name__', $currentIndex + $i, $prototypeHTML);
        }
        $prototypeFragment = new \DOMDocument();
        $prototypeFragment->loadHTML($accumulatedHtml);
        foreach ($prototypeFragment->getElementsByTagName('body')->item(0)->childNodes as $prototypeNode) {
            $node->appendChild($node->ownerDocument->importNode($prototypeNode, true));
        }
    }

replacing the string __name__ is more adherent to the cookbok recipe http://symfony.com/doc/current/form/form_collections.html#allowing-new-tags-with-the-prototype and this way you can choose how many new elements you want to add

Also, this works well with file uploads as well:

        $formCrawler = $crawler->filter('form[name="some_form"]');
        $this->appendPrototypeDom($crawler->filter('.attachment')->getNode(0), 0, 2);
        $form = $formCrawler->form();
        $values = $form->getValues();
        $values['some_form[attachment][0][description]'] = 'some description';
        $values['some_form[attachment][0][file][file]'] = new UploadedFile(
            __DIR__.'/../Assets/some.pdf',
            'some.pdf',
            'application/postscript',
            filesize(__DIR__.'/../Assets/some.pdf')
        );
        $values['some_form[attachment][1][description]'] = 'some description';
        $values['some_form[attachment][1][file][file]'] = new UploadedFile(
            __DIR__.'/../Assets/some.pdf',
            'some.pdf',
            'application/postscript',
            filesize(__DIR__.'/../Assets/some.pdf')
        );

I think this could be put in the documentation or become a PR against the crawler code

P.S. I tried the solution proposed in this pr https://github.com/symfony/symfony-docs/pull/6427 but it wasn’t working in my case. Also in a E2E testing scenario this approach makes more sense IMHO

Closing as the pull request was closed in favor of some documentation in symfony/symfony-docs#6427

@alexislefebvre unfortunately adding an argument to the submit() method would be a BC break. We’ll probably need a new method to add additional fields to the form.

Thanks for looking into this!