PHPUnit ZF2 InputFilter with Custom Validator - zend-framework2

I have the following InputFilter:
<?php
namespace Login\InputFilter;
use Zend\InputFilter\InputFilter;
/**
* Class Login
*
* #package Login\InputFilter
*/
class Login extends InputFilter
{
/**
* Construct
*/
public function __construct()
{
/**
* Password
*/
$this->add(
[
'name' => 'password',
'required' => true,
'filters' => [
[
'name' => 'stringtrim'
]
],
'validators' => [
[
'name' => 'stringlength',
'options' => [
'min' => '5',
'max' => '128'
],
'break_chain_on_failure' => true
],
[
'name' => 'regex',
'options' => [
'pattern' => '/^[^\\\' ]+$/'
],
'break_chain_on_failure' => true
]
]
]
);
}
/**
* Init
*/
public function init()
{
/**
* Employee ID
*/
$this->add(
[
'name' => 'employeeId',
'required' => true,
'filters' => [
[
'name' => 'stringtrim'
]
],
'validators' => [
[
'name' => 'stringlength',
'options' => [
'min' => '1',
'max' => '20'
],
'break_chain_on_failure' => true
],
[
'name' => 'digits',
'break_chain_on_failure' => true
],
[
'name' => 'Login\Validator\EmployeeId',
'break_chain_on_failure' => true
]
]
]
);
}
}
Attached to the employeeId is a custom validator I've created to check if the Employee ID actually exists in a database. It has a constructor for Doctrine Entity Manager. This works fine when testing via the web, so no worries there.
However now I would like to test via PHPUnit and I've created the following test:
<?php
namespace LoginTest\InputFilter;
use Login\InputFilter\Login;
/**
* Class LoginTest
*
* #package LoginTest\InputFilter
*/
class LoginTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Login $inputFilter
*/
protected $inputFilter;
public function setUp()
{
$this->inputFilter = new Login();
$this->inputFilter->init();
parent::setUp();
}
public function testFormHasElements()
{
$inputs = $this->inputFilter->getInputs();
$this->assertArrayHasKey(
'employeeId',
$inputs
);
$this->assertArrayHasKey(
'password',
$inputs
);
}
}
When the test runs the following error is produced:
1) LoginTest\InputFilter\LoginTest::testFormHasElements
Argument 1 passed to Login\Validator\EmployeeId::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /vhosts/admin-application/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php on line 180 and defined
I'm not certain how I can get passed this particular error. I assume I need to use Mockery but I'm not certain.
The validator has a Factory which supplies the Doctrine Entity Manager from the Service Locator.
I am still very new to PHPUnit but I've been trying to do my research before asking here.
Any ideas?

You're getting this error because you directly instantiate you input filter and it isn't then aware of your custom validator factory.
In real application InputFilter is using Zend\Validator\ValidatorPluginManager for getting validators from service manager.
I see two ways how to solve this problem:
1.) You can setup real service manager from application configuration, like it's described in documentation and then pull the input filter from service manager:
$inputFilter = Bootstrap::getServiceManager()->get(\Login\InputFilter\Login::class); // change the service name if you have another
This solution is good if you want to write some kind of integration tests.
2.) You can mock your custom validator and inject into ValidatorPluginManager in setup method:
protected function setUp()
{
$validator = $this->getMockBuilder(\Login\Validator\EmployeeId::class)->getMock();
$inputFilter = new Login();
$inputFilter->getFactory()
->getDefaultValidatorChain()
->getPluginManager()
->setService(\Login\Validator\EmployeeId::class, $validator);
$inputFilter->init();
$this->inputFilter = $inputFilter;
parent::setUp();
}
This solution is good if you want to write unit tests for Login input filter.

Related

How to set selected option on choiceType sonata admin bundle

setting default/ selected option for choice field in symfony3 sonata admin bundle?
For example :
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
* #inheritdoc
*/
public function configureFormFields(FormMapper $formMapper) {
parent::configureFormFields($formMapper);
$formMapper->add('type', ChoiceType::class, [
'label' => 'config.label_type',
'choices' => [
'config.label_permanent' => 'permanent',
'config.label_automatic' => 'automatic',
'config.label_temporary' => 'temporary'
],
'required' => false
]);
}
How to make the _permanent_ as selected value ?
This post doesn't help me out
setting default value in symfony2 sonata admin bundle
You can try with something like this:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
/**
* #inheritdoc
*/
public function configureFormFields(FormMapper $formMapper) {
parent::configureFormFields($formMapper);
$subject = $this->getSubject();
if (null === $subject->getId()) {
$subject->setType('permanent');
}
$formMapper->add('type', ChoiceType::class, [
'label' => 'config.label_type',
'choices' => [
'config.label_permanent' => 'permanent',
'config.label_automatic' => 'automatic',
'config.label_temporary' => 'temporary'
],
'required' => false
]);
}

Zf2 - I am not able to define the form as shared one using its name as well as alias

I am working on ZendFramework 2. I have a form which I want to be used as a shared instance. But the shared key only accepts the actual class rather then the name allocated to it. Sharing some of the code snippet for better understanding of my problem:
SampleForm.php
namespace MyProject\Form;
use Zend\Form\Form;
class Sampleform extends Form
{
public function __construct()
{
parent::__construct('sampelname');
}
/**
* Initialize the form elements
*/
public function init()
{
$this->add(
[
'type' => 'Text',
'name' => 'name',
'options' => [
'label' => 'Enter your name',
]
]
);
}
}
Defining the SampleForm in Module.php:
namespace MyProject;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\FormElementProviderInterface;
use MyProject\Form\SampleForm;
class Module implements ConfigProviderInterface, FormElementProviderInterface
{
/**
* {#inheritDoc}
*/
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
/**
* {#inheritdoc}
*/
public function getFormElementConfig()
{
return [
'invokables' => [
'MyProject\Form\SharedSampleForm' => SampleForm::class,
],
'aliases' => [
'sharedSampleForm' => 'MyProject\Form\SharedSampleForm'
],
'shared' => [
'MyProject\Form\SharedSampleForm' => true
]
];
}
}
It throws me the error like:
Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\Form\FormElementManager\FormElementManagerV2Polyfill::setShared: A service by the name "MyProject\Form\SharedSampleForm" was not found and could not be marked as shared
But it works as expected when I define my getFormElementConfig in Module.php as follows:
public function getFormElementConfig()
{
return [
'invokables' => [
'MyProject\Form\SharedSampleForm' => SampleForm::class,
],
'aliases' => [
'sharedSampleForm' => 'MyProject\Form\SharedSampleForm'
],
'shared' => [
SampleForm::class => true
]
];
}
i.e. In the shared key I provide the reference to the actual Form class name.
If the same definitions are defined under getServiceConfig() then it works as expected without throwing any such error.
Can some one please suggest/help me out how can I be able to use the service name in the shared for forms then providing the actual class reference?
getFormElementConfig() is used for defining Form Element. Not used for defining Form as service. If you wanna define this form as Service, you should define it under getServiceConfig().
Another tips, if you have make an alias, just define the Service name using it's class name.
public function getServiceConfig()
{
return [
'invokables' => [
SampleForm::class => SampleForm::class,
],
'aliases' => [
'sharedSampleForm' => SampleForm::class
],
'shared' => [
SampleForm::class => true
]
];
}
You can call the Form using the alias name like this $this->getServiceLocator()->get('sharedSampleForm');

How to use services for a form fieldset in Zend Framework 2?

I have a form (Zend\Form\Form) with some nested fieldsets (Zend\Form\Fieldset) in it. The construction is pretty similar to that in the Form Collections tutorial.
Storage\Form\MyForm
|_'Storage\Form\Fieldset\FooFieldset'
|_'Storage\Form\Fieldset\BarFieldset'
|_'Storage\Form\Fieldset\BazFieldset'
...
MyForm
class MyForm {
public function __construct()
{
...
$this->add(
[
'type' => 'Storage\Form\Fieldset\FooFieldset',
'options' => [
'use_as_base_fieldset' => true
]
]
);
}
}
FooFieldset
class FooFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct('foo');
$this->setHydrator(new ClassMethodsHydrator())->setObject(new Foo()); // dependencies!
}
}
It works, but the fieldset class has two dependencies in it. I want to inject them. In order to do it, I created a FooFieldsetFactory and extended the /module/MyModule/config/module.config.php by:
'service_manager' => [
'factories' => [
'Storage\Form\Fieldset\FooFieldset' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
The factory is simply being ignored. I guess, the service locator first tries to find the class by namespace and only if nothing is found, takes a look in the invokables and factories. OK. Then I created an alias:
'service_manager' => [
'factories' => [
'Storage\Form\Fieldset\FooFieldset' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
'aliases' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\FooFieldset',
],
... and tried to use it instead of Storage\Form\Fieldset\FooFieldset in my form class. But now I get an exception:
Zend\Form\FormElementManager::get was unable to fetch or create an instance for Storage\Form\Fieldset\Foo
I've also tried this directly:
'service_manager' => [
'factories' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
No effect, the same error.
And this also didn't work (the same error):
'form_elements' => [
'factories' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
So the referencing a service for a fieldset seems not to work. Or am I doing something wrong?
How to use services for form fieldsets?
UPDATE
With some debuggin I found out, that my Foo fieldset factory cannot be found, because it has not be added to the factories list of the Zend\Form\FormElementManager. Here is the place in the Zend\Form\Factory:
So my config
'form_elements' => [
'factories' => [
'Storage\Form\Fieldset\Foo' => 'Storage\Form\Fieldset\Factory\FooFieldsetFactory',
],
],
is ignored. How to fix this?
UPDATE Additional information, how I'm creating my Form object.
/module/Foo/config/module.config.php
return [
'controllers' => [
'factories' => [
'Foo\Controller\My' => 'Foo\Controller\Factory\MyControllerFactory'
]
],
'service_manager' => [
'factories' => [
'Foo\Form\MyForm' => 'Foo\Form\Factory\MyFormFactory',
],
],
];
/module/Foo/src/Foo/Form/Factory/MyFormFactory.php
namespace Foo\Form\Factory;
use ...;
class MyFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new MyForm();
$form->setAttribute('method', 'post')
->setHydrator(new ClassMethods())
->setInputFilter(new InputFilter());
return $form;
}
}
/module/Foo/src/Foo/Controller/Factory/MyControllerFactory.php
namespace Foo\Controller\Factory;
use ...;
class MyControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$fooPrototype = new Foo();
$realServiceLocator = $serviceLocator->getServiceLocator();
// $myForm = $realServiceLocator->get('Foo\Form\MyForm'); <-- This doesn't work correctly for this case. The FormElementManager should be used instead.
$formElementManager = $realServiceLocator->get('FormElementManager');
$myForm = $formElementManager->get('Foo\Form\MyForm');
return new MyController($myForm, $fooPrototype);
}
}
This issue is because you are adding your form elements in the forms __construct() method rather than init() as suggested in the documentation.
You can use a factory instead of an invokable in order to handle dependencies in your elements/fieldsets/forms.
And now comes the first catch.
If you are creating your form class by extending Zend\Form\Form, you must not add the custom element in the __construct-or (as we have done in the previous example where we used the custom element’s FQCN), but rather in the init() method:
The reason is that the new form's factory (which is used to create new elements using add()) must have the application's form element manager injected after the form's constructor has been called This form element manager instance contains all the references to your custom forms elements which are registered under the form_elements configuration key.
By calling add() in the form __construct the form factory will lazy load a new instance of the form element manager; which will be able to create all default form elements but will not have any knowledge of your custom form element.

How to validate if 'email' exists Zend Framework 2

I want to use NoRocordExists to validate if email exists before insert the information inside the mysql DB but i don't get how can i call $dbapater.
This is my code of my inputfilter class
$norecord_exists = new NoRecordExists(
array(
'table' => 'users',
'field' => 'email',
'adapter' => $dbadapter
)
);
$norecord_exists->setMessage('Email already exists !', 'recordFound');
$this->add(array(
'name' => 'email',
'required' => true,
'filters' => array(
array('name' => 'StringTrim'),
),
'validators' => array(
$norecord_exists,
array(
'name'=>'EmailAddress',
'options'=> array(
'allowWhiteSpace'=>true,
'messages' => array(
\Zend\Validator\EmailAddress::INVALID_HOSTNAME=>'Email incorrecto',
),
),
),
)
));
With ZF2, I advise you to use FactoryInterface like this :
UserFormFactory.php
<?php
namespace User\Form\Service;
use User\Form\UserForm;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class UserFormFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return UserForm
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/* #var ServiceLocatorInterface $sl */
$sl = $serviceLocator->getServiceLocator();
$form = new UserForm();
$form->setDbAdapter($sl->get('Zend\Db\Adapter\Adapter'));
return $form;
}
}
UserForm.php
<?php
namespace User\Form;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
class UserForm extends Form implements InputFilterProviderInterface
{
/**
* #var AdapterInterface
*/
protected $dbAdapter;
/**
* Initialisation
*/
public function init()
{
$this->add([
'name' => 'email',
'type' => 'Email',
'options' => [
'label' => 'Email',
],
'attributes' => [
'class' => 'form-control',
'required' => 'required',
],
]);
// ...
$this->add([
'name' => 'submit',
'type' => 'Submit',
'attributes' => [
'value' => 'Connexion',
'class' => 'btn btn-default',
],
]);
}
/**
* InputFilter
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'email' => [
'required' => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
['name' => 'StringToLower'],
],
'validators' => [
[
'name' => 'EmailAddress',
], [
'name' => 'Db\NoRecordExists',
'options' => [
'table' => 'user',
'field' => 'email',
'adapter' => $this->getDbAdapter(),
],
],
],
],
// ...
];
}
/**
* #return AdapterInterface
*/
public function getDbAdapter()
{
return $this->dbAdapter;
}
/**
* #param AdapterInterface $dbAdapter
* #return UserForm
*/
public function setDbAdapter(AdapterInterface $dbAdapter)
{
$this->dbAdapter = $dbAdapter;
return $this;
}
}
module.config.php
return [
'form_elements' => [
'factories' => [
'UserForm' => 'User\Form\Service\UserFormFactory',
],
],
];
Finally, in your controller
$form = $this->getServiceLocator('FormElementManager')->get('UserForm');
//..
if ($form->isValid()) // ...
You need to move this code
$norecord_exists = new NoRecordExists(
array(
'table' => 'users',
'field' => 'email',
'adapter' => $dbadapter
)
);
$norecord_exists->isValid(EMAIL_FROM_THE_FORM_FIELD) {
return false; //email exists
}
return true; // email doen't exists
in your Controller or in a separate service/factory. $dbadapter usually holds the instance to your Zend\Db\Adaptr\Adapter or any other configuration you have.

\Zend\Form\Element\Collection validation as a whole

I have form with collection. And i attach validation to whole collecion - i just want to check the existence of certain relations between elements of the collection.
And it works great. in the case of wrong data - form does not pass the "isValid()" test.
But there is one problem. formElementErrors / getMessages didnt return anything.
What i do wrong?
My form:
class Form implements InputFilterProviderInterface {
/**
* #return array
*/
public function getInputFilterSpecification()
{
return [
[
'name' => 'legend',
'required' => true,
'allowEmpty' => false,
'validators' => [
['name' => 'Callback', 'options' => [
'messages' => [
\Zend\Validator\Callback::INVALID_VALUE => 'Wrong',
],
'callback' => function ($values, $context=[]) {
return false;
},
]],
]
],
];
}
public function init()
{
$this->add(
[
'name' => 'legend',
'type' => 'Zend\Form\Element\Collection',
'options' => [
'label' => 'Legenda',
'count' => 2,
'should_create_template' => true,
'allow_add' => true,
'template_placeholder' => '__placeholder__',
'target_element' => [
'type' => 'Narzedzie\Form\Legenda\LegendyOpcjeFieldset',
],
],
]
);
}
}
And view:
$element = $NarzedzieForm->get('legend');
var_dump($element->getMessages()); // in case of error - empty array!
echo $this->formElementErrors($element); // in case of error - empty string
echo $this->formColleciton($element);
Maybe you need to add both messages?
'messages' => [
\Zend\Validator\Callback::INVALID_VALUE => 'Wrong VALUE',
\Zend\Validator\Callback::INVALID_CALLBACK => 'Wrong CALLBACK',
],
as perhaps the invalid callback message is being suppressed as you are only supplying one? I would hope it would fall back to the default. But then all this validator message stuff seems a bit stupid to me the way it is done.
Looks like you have an error in your callback, which might be throwing an exception and is being caught in the validator in the try catch statement maybe?
should be?
function ($values, $context=[]) {
foreach ($values as $value) {
if ($value['el'] == '1') return false;
}
return true;
},
as in $values not $value for the array in the foreach? Probably want to check that key is set too, with an isset($value['el'])?

Resources