Symfony2 forms without an entity and with a conditional validator

Recently on a project I had a situation where I was using the Symfony2 forms component without an entity. In addition to each field’s constraints, I needed to something similar to symfony 1.4’s conditional validator so that I could make sure that the form on the whole was valid. There are a bunch of docs out there on how to use callback functions on an entity to do this, however I didn’t see much on how to get the entire form that has no entity to do a callback. After reading some of the source code, found that you can set up some ‘form level’ constraints in the setDefaultOptions method. So it will look something like this:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(
        array('constraints'=>array(new Callback(array('methods'=>array(array($this,'checkAvailableFunds'))))),			
	)
    );
	
}

You pass the Callback constraint an array methods which it can call. If you pass one of those methods is an array it is parsed as class::method. In my case by passing $this it uses the currently instantiated form, rather than trying to call the method statically.

From there you can do something like this:

public function checkAvailableFunds($data,ExecutionContextInterface $context)
{
    if($data['oneField']===1&&$data['twoField']===3){
        $context->addViolation('No or not enough giftcodes for selected export are available!');
    }
	 
}

The first parameter is the form’s data fields. From there you can add global level errors to the form, such as if a combination of fields are not valid.

Good luck out there.

Symfony2 - Getting All Errors From a Form in a Controller

Recently I was working on an API call which uses a form to validate the data you are passing in. I ran into the issue of getting all the errors for a form in Symfony2 is not as straight forward as you would think. At first I tried $form->getErrors() and then looped through the errors. That didn’t get all of them. After looking around I found:

<?php
    public function getAllErrors($children, $template = true) {
    	$this->getAllFormErrors($children);
    	return $this->allErrors;
    }
   
    
    private function getAllFormErrors($children, $template = true) {
    	foreach ($children as $child) {
    		if ($child->hasErrors()) {
    			$vars = $child->createView()->getVars();
    			$errors = $child->getErrors();
    			foreach ($errors as $error) {
    				$this->allErrors[$vars["name"]][] = $this->convertFormErrorObjToString($error);
    			}
    		}
    
    		if ($child->hasChildren()) {
    			$this->getAllErrors($child);
    		}
    	}
    }
    

    
    private function convertFormErrorObjToString($error) {
    	$errorMessageTemplate = $error->getMessageTemplate();
    	foreach ($error->getMessageParameters() as $key => $value) {
    		$errorMessageTemplate = str_replace($key, $value, $errorMessageTemplate);
    	}
    	return $errorMessageTemplate;
    }

This works really well for any errors which are bound to a field. However it will not catch global errors such as a unique validator. It should probably be renamed from getAllErrors(). In order to get those you need to also loop through $form->getErrors(). This was returning the global errors only for me. Here is my code in the end:

<?php
        foreach($form->getErrors() as $e)
            $errors[]=$translator->trans($this->convertFormErrorObjToString($e), array(), 'validators');
        
        
        foreach($this->getAllErrors($children) as $key=>$error)
            $errors[]=$key.': '.$translator->trans($error[0], array(), 'validators');

There may be a better way, just wanted to shoot this out as not many people had good solutions on it.

Bonus: If you are using the translator service on validators and you get an error which is the ‘validators’ translation files, make sure you use the proper domain, ie: $translator->trans(‘key’,array(),‘validators’).