Form Validation gives empty array if validation fails

I am validating the form. Whenever the field length for the item exceeds a certain limit I do get the error but I do not get the error message, that is the messages array is empty. Please advise what could be going wrong there. Attached are the complete form and the corresponding validation files.

<?php

namespace Application\Form;

use Laminas\Form\Form;
use Laminas\Form\View\Helper\FormInput;
use Laminas\Form\Element;

use Application\Validator\ItemValidator;

/**
 * This form is used to collect ItemForm data.
 */
class ItemForm extends Form
{
    
    /**
     * Entity manager.
     * @var Doctrine\ORM\EntityManager 
     */
    private $entityManager = null;

    /**
     * 
     * @var Application\Entity\Item 
     */
    private $iItem = null;	
	
	
	/**
     * Constructor.     
     */
    public function __construct($translator,$entityManager = null, $iItem = null)
    {
        // Define form name
        parent::__construct('item-form');
     
        // Set POST method for this form
        $this->setAttribute('method', 'post');
		
		// Save parameters for internal use.
        $this->entityManager = $entityManager;
		$this->iItem = $iItem;
		
        $this->setAttribute('novalidate', true);
        
        $this->addElements($translator);
        $this->addInputFilter();  
        
    }
    
    /**
     * This method adds elements to form (input fields and submit button).
     */
    protected function addElements($translator) 
    {
        
		
        $this->add([
            'type'  => 'textarea',  
            'name' => 'item-description',
            'attributes' => [
                'id' => 'item-description',
                'class' => "form-control",
                'rows' => "15",
                'placeholder' => $translator->translate('Enter Item Description'),
                'required' => false
            ],
            'options' => [
                'label' => $translator->translate('Item Description'),
            ],
        ]);
		
		// Add the CSRF field
        $this->add([
            'type' => 'csrf', 
            'name' => 'item_csrf',
            'options' => [
                'csrf_options' => [
                    'timeout' => 600
                ]
            ],
        ]);
		
        // Add the submit button
        $this->add([
            'type'  => 'submit',
            'name' => 'submit-iItem',
            'attributes' => [       
                'value' => $translator->translate('Submit'),
                'id' => 'submit-iItem',
                'class' => "btn btn-info",
            ],
        ]);
    }
    
    /**
     * This method creates input filter (used for form filtering/validation).
     */
    private function addInputFilter() 
    {
        // Create main input filter
        $inputFilter = $this->getInputFilter();
		
		$inputFilter->add([ 
            'name'     => 'item-description',
            'required' => false,
            'filters'  => [
                ['name' => 'StringTrim'],                    
            ],                
            'validators' => [
                [
                    'name' => ItemValidator::class,
                    'options' => [
                        'entityManager' => $this->entityManager,
                        'itemType' => 'item-description',
                        'max' => 25,
                    ],
                ],                    
            ],
        ]);
		
    }
}

/******VALIDATOR/

<?php
namespace Application\Validator;

use Laminas\Validator\AbstractValidator;
use Laminas\Validator\InArray;
use Laminas\Validator\Uri;

/**
 * ItemValidator
 */
class ItemValidator extends AbstractValidator 
{
    
	protected $maximum = 0;
    protected $company = null;
	protected $item_type = '';

    protected $messageVariables = array(
        'max' => 'maximum',
		'item_type' => 'item_type',
    );
	
	/**
     * Available validator options.
     * @var array
     */
    protected $options = [
           
        'itemType' => null,
        'entityManager' => null,
	];
    
    // Validation failure message IDs.
    const NOT_SCALAR  = 'notScalar';
    const ITEM_DO_NOT_EXIST = 'itemDoNotExist';
	const ITEM_DESCRIPTION_TOO_LONG = 'itemDescriptionTooLong';
        
    /**
     * Validation failure messages.
     * @var array
     */
	protected $messageTemplates = [

        self::NOT_SCALAR  => [
            'errorType' => 'NOT_SCALAR',
            'itemType' => '%item_type%',
        ],     
		self::ITEM_DO_NOT_EXIST  => [
				'errorType' => 'ITEM_DO_NOT_EXIST',
				'itemType' => '%item_type%',
        ],    
		self::ITEM_DESCRIPTION_TOO_LONG  => [
				'errorType' => 'ITEM_DESCRIPTION_TOO_LONG',
				'itemType' => '%item_type%',
				'itemValue' => '%value%',
				'maxValue' => '%max%'
        ]
	];	
    
    /**
     * Constructor.     
     */
    public function __construct($options = null) 
    {
        // Set filter options (if provided).
        if(is_array($options)) {     

            if(isset($options['itemType']))
                $this->options['itemType'] = $options['itemType'];     
            if(isset($options['entityManager']))
                $this->options['entityManager'] = $options['entityManager'];
            if(isset($options['max']))
                $this->maximum = $options['max'];                   
                
        }
        
        // Call the parent class constructor
        parent::__construct($options);
    }
    
	/**
     * Check if item length is less than max specified and there is no other 
	 *	item exists with the same name.
     */
    public function isValid($value) 
    {
		if(!is_scalar($value)) {
            $this->error(self::NOT_SCALAR);
            return false; 
        }
		
		$this->setValue($value);

        $isItemAlreadyExistValid = true;
        if(($this->options['itemType'] == 'item-description'))
            $isItemAlreadyExistValid = $this->checkIfItemAlreadyExist();

		//Check if name length is not more than the specified  max value
        if(($this->options['itemType'] == 'item-description'))
            $isItemDescriptionLengthValid = $this->checkItemDescriptionLength($value);
		
		$isValid = true;
		//if any of the above criteria is invalid, isValid is false
		if(!$isItemAlreadyExistValid || !$isItemDescriptionLengthValid )
			$isValid = false;

        // Return validation result.
        return $isValid;
        
    }	

    protected function checkIfItemAlreadyExist(){
        
        $isItemAlreadyExistValid = true;
        
        $entityManager = $this->options['entityManager'];
        $itemType = $this->options['itemType'];

        $item = null;
        
        if($itemType == 'item-description')
            $item = $entityManager->getRepository(DataBreachItem::class)->findByCompany($company);

        //invalid if item don't exist
        if(is_null($item)){
            $isItemAlreadyExistValid = false;
            $this->error(self::ITEM_DO_NOT_EXIST);
        }
        
        return $isItemAlreadyExistValid;
    }

	//Check if name length is not more than the specified max value
	protected function checkItemDescriptionLength($value) {
		
		$isItemDescriptionLengthValid = true;
		
		$itemLength = strlen($value);		

        if ($itemLength > $this->maximum) {
			$isItemDescriptionLengthValid = false;
            $this->error(self::ITEM_DESCRIPTION_TOO_LONG);
            
        }
		
		return $isItemDescriptionLengthValid;	
	}
	
	
}

The method error of the abstract validator class expected a string, not an array:

This means the property $messageTemplates of your validator is wrong.

You can simplify your form:

  • remove the entity manager because the validator needs the entity manager, not the form
  • remove the translator because the view helpers of laminas-form already handles the translation
  • remove $iItem property because it is not used or use it as option for the form
  • remove the constructor, use the init method of the form to add the elements and set attributes for the form
  • setting the method to post is not needed because it is the default method
  • use the interface Laminas\InputFilter\InputFilterProviderInterface on your form to configure the input filter

And create a factory for your validator and add the entity manager there, then register your validator in your application.

Then use the form element manager to fetch your form and all dependencies will be handled.

Thanks Froshdesign for helpful review feedback comments.
I realized that the

" The method error of the abstract validator class expected a string, not an array".

I wanted to have my strings translated instead of standard translation strings from Laminas. I solved it by passing a string to error function and then parsing it in controller, for example:

/**
     * Validation failure messages.
     * @var array
     */
    protected $messageTemplates = [
		
        ...
	self::ITEM_NAME_TOO_LONG  => "
			errorType:ITEM_NAME_TOO_LONG,
			itemType:%item_type%,
			itemValue:%value%,
			maxValue:%max%",
	....
    ];	

Meanwhile it would further help if you can please clarify:

remove the translator because the view helpers of laminas-form already handles the translation

My understanding is the translation offered by laminas-form is for standard messages; where as I wanted to have my own messages translated.

remove the constructor, use the init method of the form to add the elements and set attributes for the form

where from the init method will be called. Can you please post a snippet for the same. Thanks in advance.

This sounds wrong, because if you need the form in another controller action, you have to duplicate the corresponding code.

In a typical laminas-mvc based application there is only one translator and you can add your own translations to this translator.
laminas-form itself does not bring any messages because all translatable messages are provided by the user like labels and placeholders or comes from laminas-validator.

Adding your translation files can be done via the application configuration. Configuration keys and values can be found in the documentation:

Use the form element manager in a controller:

namespace ExampleModule\Controller;

use Laminas\Form\FormElementManager;
use Laminas\Mvc\Controller\AbstractActionController;

final class HelloController extends AbstractActionController
{
    private FormElementManager $formElementManager;

    public function __construct(FormElementManager $formElementManager)
    {
        $this->formElementManager = $formElementManager;
    }

    public function worldAction()
    {
        $form = $this->formElementManager->get(ExampleForm::class);

        // …
    }
}

Register the controller in the configuration:

return [
    'controllers' => [
        'factories' => [
            ExampleModule\Controller\HelloController::class => Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory::class,
        ],
    ],
    // …
];

The form element manager will call the init method:

How do I pass parameters to the form such as translator and other attributes.?

You can use the second parameter of the get method of the form element manager:

But again: passing the translator to the form is not the correct way!
The labels and placeholders are already translated by laminas-form’s view helpers.

Thanks! This all started with my requirement of customization of translation messages. Anyways, I will again have a look.

Labels and placeholders of form elements are always custom messages but you do not need to translate them in the form because the related view helpers do this already for you.
If you want do overwrite the error messages of laminas-validator then add a translation file or do this in the input filter configuration via adding Laminas\InputFilter\InputFilterProviderInterface to the form.
In all cases, a translator is not required in your form class.

Thanks! How would it know the language of the currently logged in user?

Also, in a scenario where there could be only one item of a particular type, for example Company Name for the logged in user. I would need to pass the Company Object to the validator so that it can return appropriate validation message.

Please create a new post for this question otherwise this thread mixes to much topics.
Thanks in advance! :+1:t3:


Btw. the same here: If you use laminas-authentication then the identity helps here.

See my suggestion from above:

If you use laminas-authentication then the identity helps here. Here is an example: