Search code examples
formszend-framework2factoryinstantiationcustom-validators

ZF2 - Custom Validator via factory


I cant load my CustomValidator via factory:

namespace My\Validator;
class CustomValidator extends AbstractValidator
{
public function __construct(EntityManager $em, AuthenticationService $auth)
{
    $this->auth = $auth;
    $this->em = $em;

    parent::__construct();
}

Module (My) Config:

namespace My;
...
'service_manager' => array(
  'abstract_factories' => array(
    'Application\Factory\FormAbstractFactory'
  ),
),
'controllers' => array(
    'factories' => array(
        'My\Controller\CustomController' => 'My\Factory\Controller\CustomControllerFactory',
    ),
),
'validators' => array(
    'factories' => array(
        'My\Validator\CustomValidator' => 'My\Factory\Validator\CustomValidatorFactory'
    ),
),

CustomForm:

namespace My\Form;
class CustomForm extends SearchForm implements InputFilterProviderInterface {
    public function __construct(ObjectManager $objectManager, Translator $translator) {
        parent::__construct ($objectManager, $translator,  "customForm" );
        // Here are some elements added...
        $this->add(array("name" => "search", /* some more properties */));
    }

    public function getInputFilterSpecification()
    {
        return array(
            'search' => array(
                'validators' => array(
                    array(
                        'name' => 'My\Validator\CustomValidator'
                    )
                )
            )
        );
    }
}

CustomValidatorFactory:

namespace My\Factory\Validator;
class CustomValidatorFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator)
{
    $sl = $serviceLocator->get("service_manager");
    $entityManager = $sl->get('doctrine.entitymanager.orm_default');
    $auth = $sl->get('zfcuser_auth_service');

    return new CustomValidator($entityManager, $auth);
}

I get a PHP error saying that __construct's first parameter ($em) must be an instance of EntityManager, none given... My Factory is not instantiated... I think my CustomValidator is still created by some other factory (or ValidatorPluginManager?)... How do I have to change my code to get my CustomValidatorFactory working?

EDIT - complete Exception by rendering view with form which has my custom validator attached:

( ! ) Fatal error: Uncaught TypeError: Argument 1 passed to My\Validator\CustomValidator::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /xxxxxx/vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.php on line 252 and defined in /xxxxxx/module/My/src/My/Validator/CustomValidator.php on line 32
( ! ) TypeError: Argument 1 passed to My\Validator\CustomValidator::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /xxxxxx/vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.php on line 252 in /xxxxxx/module/My/src/My/Validator/CustomValidator.php on line 32
Call Stack
#   Time    Memory  Function    Location
1   0.0001  369208  {main}( )   .../index.php:0
2   0.1165  11279664    Zend\Mvc\Application->run( )    .../index.php:28
3   0.2780  17578688    Zend\Mvc\Application->completeRequest( )    .../Application.php:356
4   0.2780  17578688    Zend\EventManager\EventManager->triggerEvent( ) .../Application.php:384
5   0.2780  17578688    Zend\EventManager\EventManager->triggerListeners( ) .../EventManager.php:251
6   0.2781  17580376    call_user_func:{/xxxxxx/vendor/zendframework/zend-eventmanager/src/EventManager.php:490} ( )    .../EventManager.php:490
7   0.2781  17580376    Zend\Mvc\View\Http\DefaultRenderingStrategy->render( )  .../EventManager.php:490
8   0.2781  17580376    Zend\View\View->render( )   .../DefaultRenderingStrategy.php:103
9   0.2786  17591848    Zend\View\View->renderChildren( )   .../View.php:200
10  0.2787  17592840    Zend\View\View->render( )   .../View.php:236
11  0.2789  17593272    Zend\View\Renderer\PhpRenderer->render( )   .../View.php:207
12  0.2792  17633952    include( '/xxxxxx/module/My/view/my/view/index.phtml' ) .../PhpRenderer.php:502
13  0.2810  17743208    Zend\Form\Form->prepare( )  .../index.phtml:13
14  0.2810  17743208    Zend\Form\Form->getInputFilter( )   .../Form.php:203
15  0.2819  17885496    Zend\Form\Form->attachInputFilterDefaults( )    .../Form.php:696
16  0.2836  18054248    Zend\InputFilter\Factory->createInput( )    .../Form.php:801
17  0.2837  18054888    Zend\InputFilter\Factory->populateValidators( ) .../Factory.php:265
18  0.2837  18054944    Zend\Validator\ValidatorChain->attachByName( )  .../Factory.php:408
19  0.2837  18054944    Zend\Validator\ValidatorChain->plugin( )    .../ValidatorChain.php:194
20  0.2837  18057312    Zend\ServiceManager\AbstractPluginManager->get( )   .../ValidatorChain.php:97
21  0.2839  18068224    Zend\ServiceManager\ServiceManager->get( )  .../AbstractPluginManager.php:161
22  0.2839  18068920    Zend\ServiceManager\ServiceManager->create( )   .../ServiceManager.php:532
23  0.2839  18068920    Zend\ServiceManager\ServiceManager->doCreate( ) .../ServiceManager.php:599
24  0.2839  18068920    Zend\ServiceManager\AbstractPluginManager->createFromInvokable( )   .../ServiceManager.php:640
25  0.2839  18069160    My\Validator\CustomValidator->__construct( )    .../AbstractPluginManager.php:252

EDIT2 - FormAbstractFactory:

class FormAbstractFactory implements AbstractFactoryInterface {

    public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        return (fnmatch('*Form', $requestedName)) ? true : false;
    }

    public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
    {
        if (class_exists($requestedName)) {
            $entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
            $translator    = $serviceLocator->get('translator');

            return new $requestedName($entityManager, $translator);
        }

        return false;
    }

}

CustomControllerFactory:

namespace My\Factory\Controller;
use My\Controller\CustomController;
class CustomControllerFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $realServiceLocator = $serviceLocator->getServiceLocator();
        $entityManager      = $realServiceLocator->get('doctrine.entitymanager.orm_default');
        // Created through FormAbstractFactory
        $customForm          = $realServiceLocator->get('My\Form\CustomForm');
        $orderByService     = $realServiceLocator->get('Application\Service\OrderByServiceAbstract');

        return new CustomController($entityManager, $customForm, $orderByService);
    }
}

CustomController

namespace My\Controller;

class CustomController extends AbstractActionController
{
    public function __construct(ObjectManager $entityManager, CustomForm $customForm, OrderByService $orderByService)
    {
        $this->em = $entityManager;
        $this->form = $customForm;
        $this->orderByService = $orderByService;
    }

    public function indexAction()
    {
        // some code...

        $vm = new ViewModel (array(
            "form" => $this->form
        ));

        $vm->setTemplate("my/custom/index");

        return $vm;
    }
}

my/custom/index - View:

<?php
namespace My;
$form = $this->form;
$form->prepare(); ?>
<!-- HTML Stuff... ->
<?php echo $this->form($form, null); ?>
<!-- HTML Stuff... ->

Well I am using a custom form-renderer too - https://github.com/neilime/zf2-twb-bundle.

I filed an issue at zf2's git: https://github.com/zendframework/zf2/issues/7688


Solution

  • You are creating the form's via the ServiceManager.

    $customForm = $realServiceLocator->get('My\Form\CustomForm');
    

    Which means you have the abstract factory registered with the service manager rather than the form element manager. By doing this the form will be created without form factory's input filter being correctly prepared.

    Move this config to the 'form_elements' key.

    'form_elements' => [
         'abstract_factories' => [
              'My\Form\FormAbstractFactory',
         ],
     ]
    

    In CustomControllerFactory you would need to update to.

    $formElementManager = $realServiceLocator->get('FormElementManager');
    $customForm = $formElementManager->get('My\Form\CustomForm');
    

    And finally the FormAbstractFactory::createServiceWithName() method will now need $serviceLocator = $serviceLocator->getServiceLocator() at the top.