Zend Framework 2 : Extending Zend\Form to add Select, Multicheckbox, Email,Date, Textarea, and Radio element
By extending Zend\Form, we can add elements into form on the fly instead of manually instantiate the element(s).
For example, we want a form like this :

I will show you the way to do that. In Zend Framework 2, it’s very easy.
Let’s code :
namespace SampleModule\Form;
use Zend\Form\Form;
class SampleForm extends Form
{
public function __construct($name = null)
{
parent::__construct("Sample Form");
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));
$this->add(array(
'name' => 'name',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => 'Name',
),
));
$this->add(array(
'type' => 'Zend\Form\Element\Select',
'name' => 'gender',
'options' => array(
'label' => 'Gender',
'value_options' => array(
'1' => 'Select your gender',
'2' => 'Female',
'3' => 'Male'
),
),
'attributes' => array(
'value' => '1' //set selected to '1'
)
));
$this->add(array(
'type' => 'Zend\Form\Element\MultiCheckbox',
'name' => 'hobby',
'options' => array(
'label' => 'Please choose one/more of the hobbies',
'value_options' => array(
'1' =>'Cooking',
'2'=>'Writing',
'3'=>'Others'
),
),
'attributes' => array(
'value' => '1' //set checked to '1'
)
));
$this->add(array(
'type' => 'Zend\Form\Element\Email',
'name' => 'email',
'options' => array(
'label' => 'Email'
),
'attributes' => array(
'placeholder' => 'you@domain.com'
)
));
$this->add(array(
'type' => 'Zend\Form\Element\Date',
'name' => 'birth',
'options' => array(
'label' => 'Birth'
)
));
$this->add(array(
'name' => 'address',
'attributes'=>array(
'type'=>'textarea'
),
'options' => array(
'label' => 'Address',
),
));
$this->add(array(
'type' => 'Zend\Form\Element\Radio',
'name' => 'direction',
'options' => array(
'label' => 'Please choose one of the directions',
'value_options' => array(
'1' => 'Programming',
'2' => 'Design',
),
),
'attributes' => array(
'value' => '1' //set checked to '1'
)
));
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Go',
'id' => 'submitbutton',
),
));
}
//IF YOU WILL WORK WITH DATABASE
//AND NEED bind() FORM FOR EDIT DATA, YOU NEED OVERRIDE
//populateValues() FUNC LIKE THIS
public function populateValues($data)
{
foreach($data as $key=>$row)
{
if (is_array(@json_decode($row))){
$data[$key] = new \ArrayObject(\Zend\Json\Json::decode($row), \ArrayObject::ARRAY_AS_PROPS);
}
}
parent::populateValues($data);
}
}
And remember, if you work with database, beside override populateValues() func in Form Class, don’t forget to encode array of hobby[] in Table Class by :
//common code to describe namespace, use here...
class SampleTable extends AbstractTableGateway
{
protected $table = 'sampletable';
public function __construct(Adapter $adapter){ /* common code here */ }
public function saveSample(Sample $sample)
{
$data = array(
'name' => $sample->name,
'gender' => $sample->gender,
'hobby' => \Zend\Json\Json::encode($sample->hobby),
'email' => $sample->email,
'birth' => $sample->birth,
'address' => $sample->address,
'direction' => $sample->direction
);
$id = (int)$sample->id;
if ($id == 0) {
$this->insert($data);
} else {
if ($this->getSampleById($id)) {
$this->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}
}
before save/update-ing the row, and value will be like : [1,2,3] etc.
Next step,add filter and validator. They should be in other class.
namespace SampleModule\Model;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Sample implements InputFilterAwareInterface
{
public $id;
public $name;
public $gender;
public $hobby;
public $email;
public $birth;
public $address;
public $direction;
protected $inputFilter;
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->name = (isset($data['name'])) ? $data['name'] : null;
$this->gender = (isset($data['gender'])) ? $data['gender'] : null;
$this->hobby = (isset($data['hobby'])) ? $data['hobby'] : null;
$this->email = (isset($data['email'])) ? $data['email'] : null;
$this->birth = (isset($data['birth'])) ? $data['birth'] : null;
$this->address = (isset($data['address'])) ? $data['address'] : null;
$this->direction = (isset($data['direction'])) ? $data['direction'] : null;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'name',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 5,
'max' => 255,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'gender',
'validators' => array(
array(
'name' => 'InArray',
'options' => array(
'haystack' => array(2,3),
'messages' => array(
'notInArray' => 'Please select your gender !'
),
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'hobby',
'required' => true
)));
$inputFilter->add($factory->createInput(array(
'name' => 'email',
'validators' => array(
array(
'name' => 'EmailAddress'
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'birth',
'validators' => array(
array(
'name' => 'Between',
'options' => array(
'min' => '1970-01-01',
'max' => date('Y-m-d')
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'address',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 5,
'max' => 255,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'direction',
'required' => true
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
Now, we can call it from controller :
namespace SampleModule\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use SampleModule\Form\SampleForm;
use SampleModule\Model\Sample;
class TablesampleController extends AbstractActionController
{
public function addAction()
{
$form = new SampleForm();
$request = $this->getRequest();
if ($request->isPost()) {
$sample = new Sample();
$form->setInputFilter($sample->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
//SAVE TO DATABASE...
}
}
return array(
'form' => $form
);
}
}
In the view, we can call :
$form = $this->form;
$form->setAttribute('action', $this->url(
'SampleModule/default',
array(
'action' => 'add'
)
));
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('name'));
echo $this->formRow($form->get('gender'));
echo $this->formRow($form->get('hobby'));
echo $this->formRow($form->get('email'));
echo $this->formRow($form->get('birth'));
echo $this->formRow($form->get('address'));
echo $this->formRow($form->get('direction'));
echo $this->form()->closeTag();
OR, if we’re a lazy programmer, we can use like this :
$form = $this->form;
$form->setAttribute('action', $this->url(
'SampleModule/default',
array(
'action' => 'add'
)
));
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formCollection($form);
echo $this->form()->closeTag();
Done !.
[UPDATE]
As Zend Framework version 2.0.2, for multicheckbox and radio, we must to call formElementErrors() in the view to show error if data form posted is not valid like this :
echo $this->formElementErrors($form->get('hobby')); //multicheckbox
echo $this->formElementErrors($form->get('direction')); //radio
[UPDATE] ( Zend Framework 2.0.3 )
The above code ( formElementErrors ) in multicheckbox and radio is unnecessary.
References :
1. http://stackoverflow.com/questions/12563181/zend-framework-2-standalone-forms
2. http://framework.zend.com/apidoc/2.0/classes/Zend.Form.Element.Select.html
[...] http://samsonasik.wordpress.com/2012/10/01/zend-framework-2-extending-zendform-to-add-select-multich… [...]
How About using Ajax with Zend Forms,nice article by the way and also is this the end of Decorators
I hope i can blog about using zend\form with ajax soon, thanks for the comment
Very nice example. But what if I wanted to background-color the form and display the labels in white, assuming background-color was dark. What would be the best way to do this?
First, prepare your css id/class, for example :
.withcolor{
color:red;
}
#sampleform{
background-color:blue;
}
Then, You can create CUSTOM form view helper, for ex :
namespace SampleModule\View\Helper; use Zend\Form\ElementInterface; use Zend\Form\View\Helper\FormLabel as ParentLabel; class FormLabel extends ParentLabel { public function __invoke(ElementInterface $element, $labelContent = null, $position = null) { if (!$element) { return $this; } $openTag = '<label class="withcolor">'; $label = ''; if ($labelContent === null || $position !== null) { $label = $element->getLabel(); if (empty($label)) { throw new Exception\DomainException(sprintf( '%s expects either label content as the second argument, ' . 'or that the element provided has a label attribute; neither found', __METHOD__ )); } if (null !== ($translator = $this->getTranslator())) { $label = $translator->translate( $label, $this->getTranslatorTextDomain() ); } } if ($label && $labelContent) { switch ($position) { case self::APPEND: $labelContent .= $label; break; case self::PREPEND: default: $labelContent = $label . $labelContent; break; } } if ($label && null === $labelContent) { $labelContent = $label; } return $openTag . $labelContent . $this->closeTag(); } }And…, register in module.config.php
'view_helpers' => array( 'invokables' => array( 'formlabel' => 'SampleModule\View\Helper\FormLabel', ), ),Last but not least, for Form background, you can add id or class into form by :
$this->setAttribute('id', 'sampleform');In the view :
echo $this->formLabel($form->get('name')); echo $this->formElement($form->get('name'));for echo-ing label and element manually
instead of
echo $this->formRow($form->get('name');for automatically
thanks for tutorial.
Can you blog other tutorial about select element which load from database.
Thank you ^^
You’re welcome. It’s easy, just populate array of rows into array, and add to the value_options of the select element. I hope i can blog soon.
Thank you very much for all your tutorials. It’s really very useful for me.
As per your advice in last comment i succeeded to load select elements from DATABASE.
// this is in Model
public function fetchLookup($table,$name)
{
$sql = new Sql($this->adapter);
$select = $sql->select($table);
$statement = $sql->prepareStatementForSqlObject($select);
$rowset = $statement->execute();
$myArray = array();
$myArray[''] = ”;
foreach($rowset as $key => $value)
{
$myArray[$value['id']] = $value[$name];
}
// \Zend\Debug\Debug::dump($myArray);
return $myArray;
}
// I am calling this model from controller like the below code
$form = new ProformaForm();
$form->get(‘Customers’)
->setOptions(
array(
‘value_options’ => $this->getLookupTable()->fetchLookup(‘Lkp.Customers’,'CustomerName’)
)
);
I don’t know is this professional way to achieve this!?
But by using this option, i can able to call only from controller. I have to call the function during ADD and EDIT Actions(so there is a code redundancy). Is there any way to /* load select elements from DATABASE*/ during form creation. Please advise me.
You can pass a ServiceManager into form that registered by factories, that can call dbadapter and do Sql operation(s). Call form in controller by serviceLocator.
Thanks for your prompt response. Let me try.
newbie here…. Can you blog it please???
You’re welcome
Hi, Great tutorial, but I have a question:
Can be separated into another file, “filter Class” of the “model class”.
For example:
# SampleModule \ Model \ Sample
SampleModule namespace \ Model;
Class Sample {
public $ id;
public $ name;
…
}
# SampleModule \ Form \ SampleFilterForm
Class Sample implements Sample {class InputFilterAwareInterface
….
getInputFilter public function () {
if ($ this-> inputFilter) {
$ inputFilter InputFilter = new ();
InputFactory $ factory = new ();
$ inputFilter-> add ($ factory-> createInput (array (
‘name’ => ‘id’,
‘required’ => true,
‘filters’ => array (
array (‘name’ => ‘Int’),
)
)));
…
}
Do you think this is possible without using the services?
Greetings and sorry for my English
Sorry, filter class name is “Class SampleFilterForm implements InputFilterAwareInterface”
model layer can contain business logic and data access logic, as long as it’s a business logic or data access logic, you can define as your desire.
Hi samsonasik,
Thanks for the great tutorial.can you send me some sample code for populate drop down list with the database?
TQs
Hey samsonasik,
But there is a big problem during sending MultiCheckbox Data via AJAX. I build a MultiCheckbox element and subit via normal Framework Post. This works perfect. But try to send via AJAX, the form is not valid if the last checkbox in the list isn’t checked. I tested lots of posibillities, but only if the last echeckbox is checked, the form is valid. I can’t found the solution for the problem. Perhaps the problem is in the name of the Input element. the last array is evertime empty (only an idea):
Very good tutorials, i re-code the most of them locally for better understanding
RED
BLUE
But Sending via Framework POST works. Hope you can help me with this little problem.
best regards and very good work with your blog
mludewig
i think it’s your javascript problem. before send, please not submit, but check if the checkbox values are getted.
ohhh yes, U R right
the javascript don’t submit the checkbox array if last element is not selected … strange, but i think so solution is near. Thanks for your hint.
I have to thanks you I need to add some help for people if you r using fieldset you have to override the populateValues within fieldset the same way
I was just thinking how this can been handeled within database table search
if I need to search the json encoded code if u have any vision please share
———-
one more thing do u know Arabic ?
You can encode the array of chekbox search.
$hobby = array('1','3'); $where = \Zend\Json\Json::encode($hobby); $resultSet = $this->select(function (Select $select) use ($where){ $select->columns(array('id', 'name', 'gender')); $select->where(array('hobby'=>$where)); $select->order(array('id asc')); });I know a little English. I don’t know arabic language.
thanx
but what about orwhere
say:
select * where hoppy in(1,2,3,4)
i think if the field will be used in query condition it should be separated to another table
if u have an idea to do the above query please let me know
i think you should use separate table ( master, transaction, detail, etc) to do that. for full sql example, clone this : https://github.com/ralphschindler/Zend_Db-Examples
Hey Abdul,
I have similar elements with the same name & label attribute. How would I change the attributes without needing to create another element with hard coded attributes in controller?
I have the following
In Controller;
$name = new Element(‘name’);
$name->setLabel(‘First name’);
$name->setAttributes(array(
‘type’ => ‘text’
));
$form->add($name);
In View:
First Name
formRow($form->get(‘name’)); ?>
Last Name
formRow($form->get(‘name’)); ?>
Thanks
I don’t understand what you mean. but you can change the attribute via :
$form->get('elementname')->setAttribute(array('attrname'=>'attrvalue'));You did cause you answered my question. However this is what worked for me $form->get(‘elementname’)->setAttribute(‘attrname’,'attrvalue’);
Thank You
Oops, you’re right. You’re welcome.
I have a problem with dynamic changed select box options via ajax. (Category Tree). I get the error “The input was not found in the haystack.” This appears propably, because the options are not the same ones as they should be in the validator. So how can i deactivate the validiator for the Selectbox. I tried ‘inarrayvalidator’ => false but it does not seem to work.
can someone help me out?
thanks
don’t try to deactive the default validator. re-values the select options values before setData.
$form->get('state_id') ->setOptions( array('value_options'=> $Newstatecollection)) ;\Zend\Validator\Between does not validate dates. Between only does string validation. Trying to use this method with another format like ‘m/d/Y’ will not correctly limit the date.
just work for me
Hi man
I have a question to you? I want to create a form N-photos (where N could be any number). I want to be able to add description to any number of photos that are already in my database. I want also be able to select which photos are at the moment active on web-page or one that i could delete. But i don’t wanna be able to add or select each time only one photo. I would like add some description, active or delete any number of photos and update those data only once sending those information to database.
So my question is how in this case i should prepare my Model and Form files, since each time number of photos is different? What would you suggest?
Assalamualaikum, first of all thanks to you for providing such a good tutorial for a newbie in Zend Framework.
I am having a doubt with haystack. I want to pass database entries into haystack. I am not getting how to do it…
So it would be nice to if you give some guidelines regarding it…
I am waiting for your reply….
for easy setup, you can add function to set Data to the value_options like this :
public function setData($data) { $this->datavalueoptions = $data; }so, you can pass the data via factories :
'factories'=>array( 'yourform' => function($sm) { $form = new \YourModule\Form\YourForm(); $form->setData($sm->get('YourTableModel')->getData()); return $form; }, ),Thanks for your reply,
I have done the above steps but getting exception error ‘haystack option is mandatory’.
Regards.
your datavalue should be pass into ‘value_options’ key.
for example :
foreach($this->datavalueoptions as $row) { $datavalueoptions[$row->country_id] = $row->country_name; } $this->add(array( 'type' => 'Zend\Form\Element\Select', 'name' => 'country_id', 'options' => array( 'label' => 'Country :', 'value_options' => $datavalueoptions ), 'attributes' => array( 'class' => 'country' ), ));make sure your $datavalueoptions is array like :
array( '0' => 'Indonesia', '1' => 'Japan' )to make sure, you should print_r the data that you get.
I tried to do that in FORM, to made a Dynamic SELECT with database values. But it first load FORM INPUT on CONTRUCT, after that it load values.
I can’t load that dyanmica value in form, can you help ?
Thanks it work’s…..
you’re welcome
Hi
I want to make seprate module admin login and logout so currently my folder name is manager i want to login with username and password i need suggestion. please suggest.
How to work with aith library for admin login
thank s
vinu
while your app authenticate the username/login, your app should check the role who logged in, and pass to what he can do with the role. if his role is admin, he will be passed to admin module, etc.
samsonik,saya ada masalah mengenai “Zend\Form\Element\Date” yang tampil hanya sebagai textbox biasa bukan date, mohon bantuannya. terima kasih
coba pakai chrome browser atau opera.
thanks berhasil. sebelumnya sudah coba pakai firefox dan IE tdk berhasil, tidak terpikir klo hanya karena browser
sip
samsonasik jika masalah sebelumnya pada date element, sy menemui hal yg sama untuk datetime element. sebagai informasi element “date” yg berhasil dirender oleh browser hanya berupa textbox dengan masked “mm/dd/yyyy” bukan datepicker, apakah memang seperti itu. thx
biasanya sih tergantung browser sih, heuheu. coba buat custom element aja kalau ribet
Hi,
Great post again! Could you please tell us in short manner what is the difference or advantage to use a class implementing InputFilterAwareInterface or a class extending InputFilter? I saw a Rob Allen’s webinar about Zend Form, in there Rob uses InputFilter derived class.
Thank you!
akrabat make it model and filters separated. you can choose what you like.
Perfect, thank you!
you’re welcome
Salam,
How do you get the last insert id for student in the saveStudent your model?
read the docs : http://zf2.readthedocs.org/en/latest/modules/zend.db.table-gateway.html?highlight=lastinsertvalue#basic-usage
sorry but it returns me an exception :
Invalid magic property access in Zend\Db\TableGateway\AbstractTableGateway::__get()
in the model constuctor I put just this :
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
$this->initialize();
}
in the namespace section I put just this :
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\DriverInterface;
use Zend\Db\ResultSet\ResultSet;
use Front\Model\NewStudent; //this is the student class with form annotations
I Wonder if I forgot something else.
thnx.
if you extends AbstractTableGateway, so you can just call via $this directly
or if you call via instantiated model, you can call $this->instantiatedModel->lastInsertValue, for ex :
read the latest doc : http://zf2.readthedocs.org/ there is a better way using tableGateway instead of extends AbstractTableGateway.
It works now, I found a little var spelling error in my code
Thanks a lot,
you’re welcome
Hi!
When I submit my form for adding values, my url is of this type “http://www.mydomain.com/album/add/ID.
If my form is not valid (ex. empty field) refreshing page I lost ID in the url.
Do you help me?
Stupid bug. Solved:
$form->setAttribute(‘action’, $this->url(
‘album’,
array(
‘action’ => ‘add-album’,
‘id’ => $this->id,
)));