EDIT : My main question has now become 'How do I get the ServiceManager with the doctrine entity manager into the hands of my form, element, and input classes in some clean way?' Read on to see the full post.
I'm going to try and ask by example here so bear with me. Let me know where I'm going wrong/right or where I could improve
I'm trying to create a registration form. I could use ZfcUser module but I want to do this on my own. I'm using ZF2 with Doctrine2 as well so that leads me away from that module a bit.
My strategy was this,
Create a form class called registration form
Create separate 'element' classes for each element where each element will have an input specification
Since each element is a separate class from the form I can unit test each one separately.
All seemed fine until I wanted to add a validator to my username element that would check that the username is NOT is use yet.
Here is the code thus far
namepsace My\Form;
use Zend\Form\Form,
Zend\Form\Element,
Zend\InputFilter\Input,
Zend\InputFilter\InputFilter,
/**
* Class name : Registration
*/
class Registration
extends Form
{
const USERNAME = 'username';
const EMAIL = 'email';
const PASSWORD = 'password';
const PASS_CONFIRM = 'passwordConfirm';
const GENDER = 'gender';
const CAPTCHA = 'captcha';
const CSRF = 'csrf';
const SUBMIT = 'submit';
private $captcha = 'dumb';
public function prepareForm()
{
$this->setName( 'registration' );
$this->setAttributes( array(
'method' => 'post'
) );
$this->add( array(
'name' => self::USERNAME,
'type' => '\My\Form\Element\UsernameElement',
'attributes' => array(
'label' => 'Username',
'autofocus' => 'autofocus'
)
)
);
$this->add( array(
'name' => self::SUBMIT,
'type' => '\Zend\Form\Element\Submit',
'attributes' => array(
'value' => 'Submit'
)
) );
}
}
I removed a lot that I think isn't necessary. Here is my username element below.
namespace My\Form\Registration;
use My\Validator\UsernameNotInUse;
use Zend\Form\Element\Text,
Zend\InputFilter\InputProviderInterface,
Zend\Validator\StringLength,
Zend\Validator\NotEmpty,
Zend\I18n\Validator\Alnum;
/**
*
*/
class UsernameElement
extends Text
implements InputProviderInterface
{
private $minLength = 3;
private $maxLength = 128;
public function getInputSpecification()
{
return array(
'name' => $this->getName(),
'required' => true,
'filters' => array(
array( 'name' => 'StringTrim' )
),
'validators' =>
array(
new NotEmpty(
array( 'mesages' =>
array(
NotEmpty::IS_EMPTY => 'The username you provided is blank.'
)
)
),
new AlNum( array(
'messages' => array( Alnum::STRING_EMPTY => 'The username can only contain letters and numbers.' )
)
),
new StringLength(
array(
'min' => $this->getMinLength(),
'max' => $this->getMaxLength(),
'messages' =>
array(
StringLength::TOO_LONG => 'The username is too long. It cannot be longer than ' . $this->getMaxLength() . ' characters.',
StringLength::TOO_SHORT => 'The username is too short. It cannot be shorter than ' . $this->getMinLength() . ' characters.',
StringLength::INVALID => 'The username is not valid.. It has to be between ' . $this->getMinLength() . ' and ' . $this->getMaxLength() . ' characters long.',
)
)
),
array(
'name' => '\My\Validator\UsernameNotInUse',
'options' => array(
'messages' => array(
UsernameNotInUse::ERROR_USERNAME_IN_USE => 'The usarname %value% is already being used by another user.'
)
)
)
)
);
}
}
Now here is my validator
namespace My\Validator;
use My\Entity\Helper\User as UserHelper,
My\EntityRepository\User as UserRepository;
use Zend\Validator\AbstractValidator,
Zend\ServiceManager\ServiceManagerAwareInterface,
Zend\ServiceManager\ServiceLocatorAwareInterface,
Zend\ServiceManager\ServiceManager;
/**
*
*/
class UsernameNotInUse
extends AbstractValidator
implements ServiceManagerAwareInterface
{
const ERROR_USERNAME_IN_USE = 'usernameUsed';
private $serviceManager;
/**
*
* #var UserHelper
*/
private $userHelper;
protected $messageTemplates = array(
UsernameNotInUse::ERROR_USERNAME_IN_USE => 'The username you specified is being used already.'
);
public function isValid( $value )
{
$inUse = $this->getUserHelper()->isUsernameInUse( $value );
if( $inUse )
{
$this->error( UsernameNotInUse::ERROR_USERNAME_IN_USE, $value );
}
return !$inUse;
}
public function setUserHelper( UserHelper $mapper )
{
$this->userHelper = $mapper;
return $this;
}
/**
* #return My\EntityRepository\User
*/
public function getUserHelper()
{
if( $this->userHelper == null )
{
$this->setUserHelper( $this->getServiceManager()->get( 'doctrine.entitymanager.orm_default' )->getObjectRepository( 'My\Entity\User') );
}
return $this->userHelper;
}
public function setServiceManager( ServiceManager $serviceManager )
{
echo get_class( $serviceManager );
echo var_dump( $serviceManager );
$this->serviceManager = $serviceManager;
return $this;
}
/**
*
* #return ServiceManager
*/
public function getServiceManager( )
{
return $this->serviceManager;
}
}
Why did this seem like a good idea to me?
It seemed like a good testability/re-use choice to make since I could re-use the elements separately across my application if need be.
I could unit test each Input generated by each element to make sure it correctly accepts/rejects input.
This is the example of my unit test for the element
public function testFactoryCreation()
{
$fac = new Factory();
$element = $fac->createElement( array(
'type' => '\My\Form\Registration\UsernameElement'
) );
/* #var $element \My\Form\Registration\UsernameElement */
$this->assertInstanceOf( '\My\Form\Registration\UsernameElement',
$element );
$input = $fac->getInputFilterFactory()->createInput( $element->getInputSpecification() );
$validators = $input->getValidatorChain()->getValidators();
/* #var $validators \Zend\Validator\ValidatorChain */
$expectedValidators = array(
'Zend\Validator\StringLength',
'Zend\Validator\NotEmpty',
'Zend\I18n\Validator\Alnum',
'My\Validator\UsernameNotInUse'
);
foreach( $validators as $validator )
{
$actualClass = get_class( $validator['instance'] );
$this->assertContains( $actualClass, $expectedValidators );
switch( $actualClass )
{
case 'My\Validator\UsernameNotInUse':
$helper = $validator['instance']->getUserHelper();
//HAVING A PROBLEM HERE
$this->assertNotNull( $helper );
break;
default:
break;
}
}
}
The problem I'm having is that the validator can't fetch the UserHelper properly, which is really a UserRepository from doctrine. The reason this is happening is because the validators only get access to the ValidatorPluginManager as a ServiceManager rather than having access to the application wide ServiceManager.
I get this error for the Validator portion, although if I call the same get method on the general service manager it works with no problems.
1) Test\My\Form\Registration\UsernameElementTest::testFactoryCreation
Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for doctrine.entitymanager.orm_default
The var_dump( $serviceManager ) in validator shows me it is of the class ValidatorPluginManager.
I tried putting a factory in the service_manager entry like so
'service_manager' => array(
'factories' => array(
'My\Validator\UsernameNotInUse' => function( $sm )
{
$validator = new \My\Validator\UsernameNotInUse();
$em = $serviceManager->get( 'doctrine.entitymanager.orm_default' );
/* #var $em \Doctrine\ORM\EntityManager */
$validator->setUserHelper( $em->getRepository( '\My\Entity\User' ) );
return $validator;
}
)
but that didn't work because it's not consulting the application level service manager.
So, overall, here are my questions :
Is this strategy of separating the form and elements a good one? Should I keep going this way? What are alternatives? ( I'm for breaking stuff up for the sake of testability ) I was going to test ONLY the form itself originally with a combination of ALL the inputs but it seemed like I'd be trying to do too much.
How do I resolve the issue I have above?
Should I be using the Form/Element/Input parts of Zend in some other way that I'm not seeing?
this is my validator, using a static method to inject the entityManager and working with any doctine entity.
<?php
namespace Base\Validator;
use Traversable;
use Zend\Stdlib\ArrayUtils;
use Zend\Validator\AbstractValidator;
use Doctrine\ORM\EntityManager;
class EntityUnique extends AbstractValidator
{
const EXISTS = 'exists';
protected $messageTemplates = array(
self::EXISTS => "A %entity% record already exists with %attribute% %value%",
);
protected $messageVariables = array(
'entity' => '_entity',
'attribute' => '_attribute',
);
protected $_entity;
protected $_attribute;
protected $_exclude;
protected static $_entityManager;
public static function setEntityManager(EntityManager $em) {
self::$_entityManager = $em;
}
public function getEntityManager() {
if (!self::$_entityManager) {
throw new \Exception('No entitymanager present');
}
return self::$_entityManager;
}
public function __construct($options = null)
{
if ($options instanceof Traversable) {
$options = ArrayUtils::iteratorToArray($token);
}
if (is_array($options)) {
if (array_key_exists('entity', $options)) {
$this->_entity = $options['entity'];
}
if (array_key_exists('attribute', $options)) {
$this->_attribute = $options['attribute'];
}
if (array_key_exists('exclude', $options)) {
if (!is_array($options['exclude']) ||
!array_key_exists('attribute', $options['exclude']) ||
!array_key_exists('value', $options['exclude'])) {
throw new \Exception('exclude option must contain attribute and value keys');
}
$this->_exclude = $options['exclude'];
}
}
parent::__construct(is_array($options) ? $options : null);
}
public function isValid($value, $context = null)
{
$this->setValue($value);
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->from($this->_entity, 'e')
->select('COUNT(e)')
->where('e.'. $this->_attribute . ' = :value')
->setParameter('value', $this->getValue());
if ($this->_exclude) {
$queryBuilder = $queryBuilder->andWhere('e.'. $this->_exclude['attribute'] . ' != :exclude')
->setParameter('exclude', $this->_exclude['value']);
}
$query = $queryBuilder->getQuery();
if ((integer)$query->getSingleScalarResult() !== 0) {
$this->error(self::EXISTS);
return false;
}
return true;
}
}
ie. i'm using it for theese form elements which are also tested and working fine:
<?php
namespace User\Form\Element;
use Zend\Form\Element\Text;
use Zend\InputFilter\InputProviderInterface;
class Username extends Text implements InputProviderInterface
{
public function __construct() {
parent::__construct('username');
$this->setLabel('Benutzername');
$this->setAttribute('id', 'username');
}
public function getInputSpecification() {
return array(
'name' => $this->getName(),
'required' => true,
'filters' => array(
array(
'name' => 'StringTrim'
),
),
'validators' => array(
array(
'name' => 'NotEmpty',
'break_chain_on_failure' => true,
'options' => array(
'messages' => array(
'isEmpty' => 'Bitte geben Sie einen Benutzernamen ein.',
),
),
),
),
);
}
}
When creating a new user
<?php
namespace User\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use User\Form\Element\Username;
class CreateUsername extends Username implements InputProviderInterface
{
public function getInputSpecification() {
$spec = parent::getInputSpecification();
$spec['validators'][] = array(
'name' => 'Base\Validator\EntityUnique',
'options' => array(
'message' => 'Der name %value% ist bereits vergeben.',
'entity' => 'User\Entity\User',
'attribute' => 'username',
),
);
return $spec;
}
}
when editin an existing user
<?php
namespace User\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use User\Form\Element\Username;
class EditUsername extends Username implements InputProviderInterface
{
protected $_userId;
public function __construct($userId) {
parent::__construct();
$this->_userId = (integer)$userId;
}
public function getInputSpecification() {
$spec = parent::getInputSpecification();
$spec['validators'][] = array(
'name' => 'Base\Validator\EntityUnique',
'options' => array(
'message' => 'Der name %value% ist bereits vergeben.',
'entity' => 'User\Entity\User',
'attribute' => 'username',
'exclude' => array(
'attribute' => 'id',
'value' => $this->_userId,
),
),
);
return $spec;
}
}
Related
I'm trying to have a verification process after registration (by a randomly generated verification code), but after I verify one code, it will not verify another one even though I am using the code that is stored in the database upon registration. For instance:
verify/c42557235936ed755d3305e2f7305aa3
...works fine, but when I try and use another code (like /verify/3bc056ff48fec352702652cfa4850ac4), it generates the default layout for the application and does nothing. I don't know what is causing it.
Here is the code I have for this:
VerifyController -
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class VerifyController extends AbstractActionController
{
public $verify;
public function indexAction()
{
$code = $this->params()->fromRoute('code');
if ($this->getVerifyInstance()->authenticateCode($code) !== false) {
$this->flashMessenger()->addSuccessMessage("Verification Successful, you can now login.");
return $this->redirect()->toRoute('verify', array('action' => 'success'));
} else {
$this->flashMessenger()->addErrorMessage("Oops! Something went wrong while attempting to verify your account, please try again.");
return $this->redirect()->toRoute('verify', array('action' => 'failure'));
}
}
public function successAction()
{
}
public function failureAction()
{
}
public function getVerifyInstance()
{
if (!$this->verify) {
$sm = $this->getServiceLocator();
$this->verify = $sm->get('Application\Model\VerifyModel');
}
return $this->verify;
}
}
VerifyModel -
namespace Application\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Insert;
use Zend\Db\Adapter\Adapter;
class VerifyModel
{
/**
* #var TableGateway
*/
protected $table_gateway;
/**
* #var mixed
*/
protected $code;
/**
* Constructor method for VerifyModel class
* #param TableGateway $gateway
*/
public function __construct(TableGateway $gateway)
{
// check if $gateway was passed an instance of TableGateway
// if so, assign $this->table_gateway the value of $gateway
// if not, make it null
$gateway instanceof TableGateway ? $this->table_gateway = $gateway : $this->table_gateway = null;
}
public function authenticateCode($code)
{
// authenticate the verification code in the url against the one in the pending_users table
$this->code = !empty($code) ? $code : null;
$select = $this->table_gateway->select(array('pending_code' => $this->code));
$row = $select->current();
if (!$row) {
throw new \RuntimeException(sprintf('Invalid registration code %s', $this->code));
} else {
// verification code was found
// proceed to remove the user from the pending_users table
// and insert into the members table
$data = array(
'username' => $row['username'],
'password' => $row['password'],
);
$sql = new Sql($this->table_gateway->getAdapter());
$adapter = $this->table_gateway->getAdapter();
$insert = new Insert('members');
$insert->columns(array(
'username',
'password'
))->values(array(
'username' => $data['username'],
'password' => $data['password'],
));
$execute = $adapter->query(
$sql->buildSqlString($insert),
Adapter::QUERY_MODE_EXECUTE
);
if (count($execute) > 0) {
// remove the entry now
$delete = $this->table_gateway->delete(array('pending_code' => $this->code));
if ($delete > 0) {
return true;
}
}
}
}
}
the route:
'verify' => array(
'type' => 'Segment',
'options' => array(
'route' => 'verify/:code',
'constraints' => array(
'code' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'controller' => 'Application\Controller\Verify',
'action' => 'index',
),
),
),
and the layout configurer in Module.php:
public function init(ModuleManager $manager)
{
$events = $manager->getEventManager();
$shared_events = $events->getSharedManager();
$shared_events->attach(__NAMESPACE__, 'dispatch', function ($e) {
$controller = $e->getTarget();
if (get_class($controller) == 'Application\Controller\SetupController') {
$controller->layout('layout/setup');
} else if (get_class($controller) == 'Application\Controller\MemberLoginController' || get_class($controller) == 'Application\Controller\AdminLoginController') {
$controller->layout('layout/login');
} else if (get_class($controller) == 'Application\Controller\RegisterController') {
$controller->layout('layout/register');
} else if (get_class($controller) == 'Application\Controller\VerifyController') {
$controller->layout('layout/verify');
}
}, 100);
}
Your route is defined
'options' => array(
'route' => 'verify/:code',
'constraints' => array(
'code' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
So, it should start with a letter (upper or lower case), and be followed by any (even none) number of characters (letters, numbers, underscores, and dashes).
So, valid routes:
verify/c42557235936ed755d3305e2f7305aa3 (the one you where trying)
verify/abcde
verify/N123-123
verify/Z
verify/X-1
etc.
Any of those should work. But the other code you provide in your question:
/verify/3bc056ff48fec352702652cfa4850ac4
starts with a number, so it wont be caught by your router. You need to either change how you generate your codes so they match your route, or change your route so it matches your codes. E.g.:
'options' => array(
'route' => 'verify/:code',
'constraints' => array(
'code' => '[a-zA-Z0-9][a-zA-Z0-9_-]{28,32}',
),
Can implement dynamic validation on element level? I used this example to implement validation of one element dependent on the value of the other element. But for this purpose I'll need to implement this for every single form where I use this element (comment) with this validation. I have many forms like that. Is there way to do the following:
to take this filter/validation logic to the element level using some kind of "data-comment-for" attribute and retrieving the value of the element on which it depends from the parent form.
This is my current code (but I need to have it for every form now. It does not look elegant at all) :
class CompetencyAdvanceHumanRightsAndJusticeFormFilter extends InputFilter
{
public function isValid($context = null)
{
$figradeCommentName = 'applJusticeFIGrade'.'Comment';
$forGrade = $this->get('applJusticeFIGrade');
$gradeComment = $this->get($figradeCommentName);
$applJusticeFIGradeRawValue = $forGrade->getRawValue('applJusticeFIGrade');
if(is_numeric($applJusticeFIGradeRawValue)){
$gradeValue = intval($applJusticeFIGradeRawValue);
}else{
$gradeValue = $applJusticeFIGradeRawValue;
}
if ($gradeValue != 'na' && $gradeValue > 0) {
$gradeComment->setRequired(true);
$validatorChain = new Validator\ValidatorChain();
$validatorChain->attach(
new Validator\NotEmpty(),
true
);
$gradeComment->setValidatorChain($validatorChain);
}
return parent::isValid($context);
}
public function __construct(){
$this->add(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
$this->add(array(
'name' => 'studEvalId',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
}
}
EDIT:
I added code for the custom element to the question. There are some "leftovers" of my attempts to place this logic to the element level.
Comment Element
class Comment extends Element implements InputProviderInterface
{
/**
* #var ValidatorInterface
*/
protected $validator;
// set its type
protected $attributes = array(
'type' => 'comment'
);
public function init()
{
if (null === $this->validator) {
$validator = new StringLength();
$validator->setMax(10);
$validator->setMessage('The comment should not exceed 1000 letters!', StringLength::INVALID);
$this->validator = $validator;
}
}
/**
* Get a validator if none has been set.
*
* #return ValidatorInterface
*/
public function getValidator()
{
return $this->validator;
}
/**
* #param ValidatorInterface $validator
* #return $this
*/
public function setValidator(ValidatorInterface $validator)
{
$this->validator = $validator;
return $this;
}
/**
* remove require and validator defaults because we have none
*
* #return array
*/
public function getInputSpecification()
{
// return array(
// 'name' => $this->getName(),
// 'required' => false,
// 'validators' => array(
// $this->getValidator(),
// ),
// 'filters' => array(
// new FIGradeCommentDynamicBufferFilter()
// ),
// );
return array(
'name' => $this->getName(),
'required' => false,
'filters' => array(
array('name' => 'Zend\Filter\StringTrim'),
),
'validators' => array(
$this->getValidator(),
),
);
}
// tell it where to find its view helper, so formRow and the like work correctly
public function getViewHelperConfig()
{
return array('type' => '\OnlineFieldEvaluation\View\Helper\FormComment');
}
}
You could make a base abstract input-filter class and an interface and make all your form filters extend the base class that implements the interface with the methods you expect inside your form classes to make the thing work correctly.
Make an interface with the methods:
interface GradeCommentFormFilterInterface()
{
protected function getGradeInput();
protected function getCommentInput();
}
Then you move the common code to your base class:
abstract class BaseGradeCommentFormFilter extends InputFilter implements GradeCommentFormFilterInterface
{
protected function getGradeInput()
{
return $this->get(static::GRADE_NAME);
}
protected function getCommentInput()
{
return $this->get(static::GRADE_NAME . 'Comment');
}
public function isValid($context = null)
{
$gradeInput = $this->getGradeInput();
$commentInput = $this->getCommentInput();
$rawValue = $this->getRawValue($gradeInput);
if(is_numeric($rawValue))
{
$gradeValue = intval($rawValue);
}
else
$gradeValue = $rawValue;
if ($gradeValue != 'na' && $gradeValue > 0) {
$commentInput->setRequired(true);
$validatorChain = new Validator\ValidatorChain();
$validatorChain->attach(
new Validator\NotEmpty(),
true
);
$commentInput->setValidatorChain($validatorChain);
}
return parent::isValid($context);
}
}
Now you can use your abstract class like this:
class CompetencyAdvanceHumanRightsAndJusticeFormFilter extends BaseGradeCommentFormFilter
{
const GRADE_NAME = 'applJusticeFIGrade';
//... other code
}
I quickly tried to make it work for your case, but this isn't tested, and probably there are ways to optimize this, but it gives you an idea of what you can do.
I have a form that was working until I added two dropdown menus. Since then, upon the submission of the form I get the error:
haystack option is mandatory
This is my ProjectForm.php
<?php
namespace Project\Form;
use Zend\Form\Form;
use Zend\Form\Element\Text;
use Zend\Form\Fieldset;
use Zend\Form\Element\Select;
use Zend\Form\Element\Checkbox;
use Zend\Form\Element\Date;
use Zend\Config\Factory;
use Zend\Form\Element\Button;
use Zend\Form\Element\Textarea;
use Zend\Validator\Callback;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Form\Element\File;
class ProjectForm extends Form implements InputFilterProviderInterface {
const KEY_PROJECT_NAME = "project_name";
const KEY_PROJECT_DUE_DTTM = "project_due_dttm";
const KEY_PROJECT_DESCRIPTION = "project_description";
const KEY_PROJECT_CONFIG = "project_config";
const KEY_PROJECT_TYPES = "project_types";
const KEY_PROJECT_WORKFLOW_CONFIG = "project_workflow_config";
const KEY_PROJECT_WORKFLOW_TYPES = "project_workflow_types";
const KEY_PROJECT_FILE = "project_file";
const KEY_SAVE_BTN = "project_save_btn";
const KEY_CANCEL_BTN = "project_cancel_btn";
public function __construct($name = null, $options = array()) {
parent::__construct($name);
$this->setAttribute('method', 'post');
$this->setAttribute('class', 'form-horizontal');
$name = new Text(self::KEY_PROJECT_NAME);
$name->setAttribute("id", self::KEY_PROJECT_NAME);
$name->setLabel("Name");
$duedttm = new Text(self::KEY_PROJECT_DUE_DTTM);
$duedttm->setAttribute("id", self::KEY_PROJECT_DUE_DTTM);
$duedttm->setLabel("Due Date");
$description = new Textarea(self::KEY_PROJECT_DESCRIPTION);
$description->setAttribute("id", self::KEY_PROJECT_DESCRIPTION);
$description->setLabel("Description");
$config = new Textarea(self::KEY_PROJECT_CONFIG);
$config->setAttribute("id", self::KEY_PROJECT_CONFIG);
$config->setLabel("Configuration");
$projectTypeDropDown = new Select(self::KEY_PROJECT_TYPES);
$projectTypeDropDown->setLabel('Project Type:');
$projectTypeDropDown->setValueOptions($options["project"]);
$wfConfig = new Textarea(self::KEY_PROJECT_WORKFLOW_CONFIG);
$wfConfig->setAttribute("id", self::KEY_PROJECT_WORKFLOW_CONFIG);
$wfConfig->setLabel("Workflow Configuration");
$projectWorkflowDropDown = new Select(self::KEY_PROJECT_WORKFLOW_TYPES);
$projectWorkflowDropDown->setLabel('Workflow Type:');
$projectWorkflowDropDown->setValueOptions(array());
$file = new File(self::KEY_PROJECT_FILE);
$file->setAttribute("id", self::KEY_PROJECT_FILE);
$file->setAttribute("multiple", true);
$file->setLabel("File");
$save = new Button(self::KEY_SAVE_BTN);
$save->setAttributes(array("id", self::KEY_SAVE_BTN));
$save->setLabel("Save");
$save->setValue("Save");
$cancel = new Button(self::KEY_CANCEL_BTN);
$cancel->setAttributes(array("id", self::KEY_CANCEL_BTN));
$cancel->setLabel("Cancel");
$this->add($name);
$this->add($duedttm);
$this->add($description);
$this->add($config);
$this->add($projectTypeDropDown);
$this->add($wfConfig);
$this->add($projectWorkflowDropDown);
$this->add($file);
$this->add($save);
$this->add($cancel);
}
public function isValidJSON($value, $options) {
try {
JSON::decode($value);
return true;
} catch (JSONException $e) {
return false;
}
}
public function isValidDateTime($value, $options) {
if (false === date_create($value)) {
return false;
}
return true;
}
/**
* (non-PHPdoc)
* #see \Zend\InputFilter\InputFilterProviderInterface::getInputFilterSpecification()
*/
public function getInputFilterSpecification() {
return array(
ProjectForm::KEY_PROJECT_CONFIG => array(
'required' => true,
'filters' => array(
array(
'name' => 'Zend\Filter\StringTrim'
),
),
'validators' => array(
new Callback(
array(
$this,
'isValidJSON'
)),
),
),
ProjectForm::KEY_PROJECT_WORKFLOW_CONFIG => array(
'required' => false,
'filters' => array(
array(
'name' => 'Zend\Filter\StringTrim'
),
),
'validators' => array(
new Callback(
array(
$this,
'isValidJSON'
)),
),
),
ProjectForm::KEY_PROJECT_DUE_DTTM => array(
'required' => false,
'filters' => array(
array(
'name' => 'Zend\Filter\StringTrim'
),
),
'validators' => array(
new Callback(
array(
$this,
'isValidDateTime'
)),
),
),
);
}
}
I think I need to disable the inarray_validator by adding disable_inarray_validator' => true but I'm not sure where to add this.
Because of the version of Zend that I am using, I had to add this to the controller before validating the form:
// Make certain to merge the files info!
$post = array_merge_recursive($request->getPost()->toArray(), $request->getFiles()->toArray());
//Deals with validation of dynamic form
$projectType = $projectTypes[$post["project_types"]];
$form->get('project_workflow_types')->setValueOptions($workflowTypes[$projectType]);
// set the form instance's data
$form->setData($post);
I suggest you pass an empty string as a unique valueOption. In your javascript you flush the <option> tags of the select to get rid of this empty option.
Also you will have to disable the default validator of the Select element by adding :
$projectWorkflowDropDown->setOptions(array('disable_inarray_validator' => true));
Or
$projectWorkflowDropDown->setDisableInArrayValidator(true);
If you don't add this option, when the form will be submitted, Zend Form will tell you the value send is not in the haystack provided when the form was built. This is a safety process which is very helpful in many cases, just not in your.
I created a Validator for one of my form fields. To do that, I need the ServiceLocator so I would like to use a factory ...
Edit :
Here is my factory :
namespace Maintenance\Factory\Validator;
/* Zend */
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/* Controller */
use Maintenance\Validator\Echeancedebut;
class EcheancedebutFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$maiContratService = $realServiceLocator->get(
'Maintenance\Service\Model\FMaiContratService'
);
return new Echeancedebut($maiContratService);
}
}
My Validator :
class Echeancedebut extends AbstractValidator
{
const ERROR_DATEDEB = 'ERROR_DATEDEB';
protected $maiContratService;
protected $messageTemplates = array(
self::ERROR_DATEDEB => "Saisie inférieure à la date de début du contrat"
);
public function __construct($maiContratService) {
$this->maiContratService = $maiContratService;
}
public function isValid($value){
$this->setValue($value);
if (!$this->validatedate($value)) {
$this->error(self::ERROR_DATEDEB);
return false;
}
return true;
}
private function validatedate($date) {
return false;
}
}
Edit :
My InputFilter:
public function getInputFilter()
{
if (! $this->inputFilter) {
$inputFilter = new InputFilter();
$this->inputFilter = $inputFilter;
}
$inputFilter->add(
array(
'name' => 'dateDeb',
'required' => true,
'allow_empty' => false,
'validators' => array(
array(
'name' => 'Date',
'locale' => 'FR_fr',
'options' => array(
'format' => 'd/m/Y',
),
),
array(
'name' => 'Maintenance\Validator\Echeancedebut',
'options' => array(
'contratId' => $this->iMaiContratId,
)
);
return $this->inputFilter;
}
The problem is that it doesn't retrieve my error message, why ?
The name of your factory and the name you registered in your config are not corresponding:
Maintenance\Factory\Validator\EcheancedebutFactory (inside your config)
Maintenance\Factory\Validator\EcheancedebFactory (the full class name)
Echeancedebut vs Echeancedeb.
Change that and I think it should work.
Okay so injecting Sl in a validator is a bad idea. I will pass my options as arguments via the inputFilter, called by my controller. So the problem is solved. Thanks for your answers.
Hey there, I'm trying to validate a form with Zend_Validate and Zend_Form.
My element:
$this->addElement('text', 'username', array(
'validators' => array(
array(
'validator' => 'Db_NoRecordExists',
'options' => array('user','username')
)
)
));
For I use Doctrine to handle my database, Zend_Validate misses a DbAdapter. I could pass an adapter in the options, but how do I combine Zend_Db_Adapter_Abstract and Doctrine?
Is there maybe an easyer way to get this done?
Thanks!
Solved it with an own Validator:
<?php
class Validator_NoRecordExists extends Zend_Validate_Abstract
{
private $_table;
private $_field;
const OK = '';
protected $_messageTemplates = array(
self::OK => "'%value%' ist bereits in der Datenbank"
);
public function __construct($table, $field) {
if(is_null(Doctrine::getTable($table)))
return null;
if(!Doctrine::getTable($table)->hasColumn($field))
return null;
$this->_table = Doctrine::getTable($table);
$this->_field = $field;
}
public function isValid($value)
{
$this->_setValue($value);
$funcName = 'findBy' . $this->_field;
if(count($this->_table->$funcName($value))>0) {
$this->_error();
return false;
}
return true;
}
}
Used like that:
$this->addElement('text', 'username', array(
'validators' => array(
array(
'validator' => new Validator_NoRecordExists('User','username')
)
)
));