zendframework / zend-form

Form component from Zend Framework
BSD 3-Clause "New" or "Revised" License
69 stars 87 forks source link

Annotations: Use Annotation to build Fieldset don't preserve Validator/filter #87

Open GeeH opened 8 years ago

GeeH commented 8 years ago

This issue has been moved from the zendframework repository as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html


Original Issue: https://api.github.com/repos/zendframework/zendframework/issues/7686 User: @gregGit Created On: 2016-03-14T13:33:17Z Updated At: 2016-03-15T00:15:27Z Body I'm using Form Annotation in a project with DoctrineORM 2.0. So i wan't to have all my fields specifications in my entities including the Validator/Filter for each element. When i need to use an entity, i first build a form (without annotation) and add to this blank form a fieldset representing an entity which i build with annotation. By this way i can more than one entities fieldset on the same form (and collection if i need it). Then i realise there is a pb with this method, because as the entities Form is a fieldset, all validators/filter are loose (they are not applied to the form).

It's not really a bug because input filters applies only to form, not to fieldset. But for me it's logical and pratical to define ZF2 Validator/Filter and entity's column specification (ie a varchar(20) not null column need to have StringLength and Required validator's).

The following simple exemple illustrate the problem :

Using a new skeleton application with doctrine common, i build a very simple class :

<?php

namespace Application\Form;

use Zend\Form\Annotation as Form;
/**
 * @Form\Name("test")
 */
class Test 
{
    /**
     * @var string
     *
     * @Form\Attributes({"type":"text", "required":true})
     * @Form\Options({"label":"Max 5 car field :"})
     * @Form\Required(true)
     * @Form\Filter({"name": "StripTags"})
     * @Form\Filter({"name": "StringTrim"})
     * @Form\Validator({"name":"StringLength", "options":{"encoding" : "UTF-8","min":1, "max":5}})
     */
    private $max5field;

}

Then in Index controller, i add the folowing

public function testAction() {

        $builder = new \Zend\Form\Annotation\AnnotationBuilder();
        $form = $builder->createForm(new \Application\Form\Test());
        $form->setAttribute('method', 'POST');
        $form->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type' => 'submit',
                'value' => 'OK',
                'id' => 'submitbutton',
            ),
        ));

        $result="";
        $request = $this->getRequest();
        if ($request->isPost()) {

           $form->setData($request->getPost());
            if ($form->isValid()) {
                $result="Form is Valid";
            } else {
                $result = print_r($form->getMessages(), true);
            }
        }
        return new ViewModel(array('form'=>$form, 'result'=>$result));
    }

And finally the view test.phtml only contains :

<?php echo $this->form($form);
echo $result;
?>

By this all is ok, validator and filter are applied.

Now i want to use the same in a fieldset, so in controller i build form like this :

        $builder = new \Zend\Form\Annotation\AnnotationBuilder();
        $form=new \Zend\Form\Form();
        $fieldset = $builder->createForm(new \Application\Form\Test());
        $form->add($fieldset);
        ....

With this no validator/filter are applied even if i had the annotation @Form\Type("\Zend\Form\Fieldset")

So if you use annotation to build a fieldset, validator/filter applyed to fieldset elements are ignored.

Regarding to ZF2 FormFactory code it seem to be "normal" because prepareAndInjectInputFilter method is only call in configureForm method.

To be complete, my workaround is to create a new Form Factroy (wich extend Zend/Form/Factory) and redefine configureFieldset like this :

public function configureFieldset(FieldsetInterface $fieldset, $spec)
    {
       $fieldset=parent::configureFieldset($fieldset, $spec);
       $fieldset->input_filter_factory= $spec['input_filter'];
       return $fieldset;
    }

This factory is used by my annotationBuilder (using setFormFactory method)

Then all my Entities Fieldset Classes implement inputFilterProvider and the get method is :

   public function getInputFilterSpecification()
    {
        if (isset($this->input_filter_factory)){
            return $this->input_filter_factory;
        }        
        else {
            return array();
        }
    }

Then it works as i want, Validator/Filter defined in the entity annotation are set in the form using the fieldset.


cybercandyman commented 5 years ago

Hi !!! I am facing the same issue. I' am trying your fix but in my case input_filter_factory in my entity is always null...

This is my form :

class WebtvForm extends Form
{
    private $formSpecification;

    /**
     * Constructor
     */
    public function __construct (ObjectManager $objectManager)
    {
        // we want to ignore the name passed
        parent::__construct('entity-create-form');
        $this->setAttribute('method', 'post');
        $entity = new Webtv();

        $builder = new ZFBuilder($objectManager);
        $builder->setFormFactory(new ZNFormFactory());
        //Add the fieldset, and set it as the base fieldset
        $fieldset = $builder->createForm($entity);
        $this->formSpecification = $builder->getFormSpecification($entity)->getArrayCopy();

        $fieldset->setHydrator(new DoctrineHydrator($objectManager));
        $fieldset->setName("Webtv");
        $fieldset->setUseAsBaseFieldset(true);

        //\Zend\Debug\Debug::dump($this->formSpecification);

        $this->add($fieldset);
        /*
        $this->add(array(
            'type' => 'Zend\Form\Element\Csrf',
            'name' => 'csrf'
        ));
        */
        $this->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type' => 'submit',
                'value' => 'Save'
            )
        ));
    }

My form factory, that part is called without problem

<?php
namespace ZN\Form;
use Zend\Form\FieldsetInterface;

class Factory extends \Zend\Form\Factory {
    /**
     * @var FieldsetInterface
     */
    private $fieldset;
    public function configureFieldset(FieldsetInterface $fieldset, $spec)
    {
        $fieldset=parent::configureFieldset($fieldset, $spec);
        $fieldset->input_filter_factory  = $spec['input_filter'];
        $this->input_filter_factory = $spec['input_filter'];
        return $fieldset;
    }

}

My entity, getInputFilterSpecification is called but input_filter_factory is null

/**
 * Webtv
 *
 * @ORM\Table(name="webtv")
 * @ORM\Entity
 * @Annotation\Name("Webtv")
 * @Annotation\Type("fieldset")
 */
class Webtv implements InputFilterProviderInterface
{

    public function getInputFilterSpecification()
    {
        if (isset($this->input_filter_factory)){
            return $this->input_filter_factory;
        }
        else {
            return array();
        }
    }

And finally my controller

    public function editAction()
    {
        $viewModel = new ViewModel();
        $viewModel->setTerminal(true);
        $webtv = new Webtv();

        $form = $this->getServiceLocator()->get('WebtvForm');

        if($this->getRequest()->getQuery("id",null)!=null) {
            $webtv = $this->getWebtvService()->loadById($this->getRequest()->getQuery("id"));
            $form->bind($webtv);
        }
        /** @var Form $form */
        if ($this->getRequest()->isPost()) {
            $params = $this->getRequest()->getPost();
            $form->setData( $params );
            if ($form->isValid()) {
                try{
                    $this->getWebtvService()->save( $form->getData() );
                    $this->getWebtvService()->getEntityManager()->flush();
                    $this->flashMessenger()->addSuccessMessage('Saved');
                    $this->redirect()->toRoute('administration',array("controller"=>"webtv","action"=>"list"));
                }catch (\Exception $error){

                    print($error->getMessage());
                    print($error->getTraceAsString());
                    exit();
                    \Zend\Debug\Debug::dump($error);
                }
            }
        }
        $viewModel->setVariable("form",$form);
        return $viewModel;
    }
cybercandyman commented 5 years ago

So my solution is a way different :

1- I have to inject in my form factory the FormElementManager because FormElementManager is instantiated and not called from ServiceManager, so my custom config was not loading. 2- Create a custom class Fieldset implementing InputFilterProviderInterface and called by FormElementManager

I am almost happy with that, but I am surprised that validation of fieldset created from annotation was not implemented.

michalbundyra commented 4 years ago

This repository has been closed and moved to laminas/laminas-form; a new issue has been opened at https://github.com/laminas/laminas-form/issues/41.