I'm using Zend Framework 1.12 on a project. I having some problems with the Zend_Form. Some fields are generated dynamically on execution time, but the Zend_Form is static, a element predefined at creation.
So, when the form is sent, the validation doesn't work because new fields were added and the sent form doesn't match the form created.
How do that adaptation?
You should try, following solution: after sending the form, get the $_POST array, then check which fields/values do you have and create/modify form Object with this fields/validation.
I would have done this way :
class MyForm extends Zend_Form
{
public function init()
{
//... Create here the basic elements
}
public function initFromPostValue( $post )
{
if( array_key_exists( 'dynamicsField', $post ) ) {
$el = $this->createElement( 'select', 'dynamicsField' )
->setValidators( array( ... PUT your validators here ) );
$this->addElement( $el );
}
}
}
In the validation action :
public function validationAction()
{
$form = new MyForm();
$form->initFromPostValue( $_POST );
if( $form->isValid( $_POST ) ) {
// Form is valid
} else {
// Form is invalid
}
}
Related
Is it possible to fire a FormAction button from within CMSFields? I'm currently using the following code but the button click doesn't want to fire the form action. Does this need to be called through a custom entwine function or is there something already built in that I'm missing?
class NotesPage extends Page {
function getCMSFields() {
$fields = parent::getCMSFields();
// Page Sections
$fields->addFieldsToTab('Root.Notes', array(
// Send for approval
LiteralField::create('', '<h3>Notify Admin Of Content To Approve</h3>'),
FormAction::create('NotifyAdmin', 'Notify Admin'),
));
return $fields;
}
}
class NotesPageExtension extends LeftAndMainExtension {
private static $allowed_actions = array(
'NotifyAdmin'
);
// Email the admin about content to moderate
public function NotifyAdmin($data, $form){
$className = $this->owner->stat('tree_class');
$SQL_id = Convert::raw2sql($data['ID']);
$record = DataObject::get_by_id($className, $SQL_id);
if(!$record || !$record->ID){
throw new SS_HTTPResponse_Exception(
"Bad record ID #" . (int)$data['ID'], 404);
}
// at this point you have a $record,
// which is your page you can work with!
// this generates a message that will show up in the CMS
$this->owner->response->addHeader(
'X-Status',
rawurlencode('Notifying admin')
);
return $this->owner->getResponseNegotiator()->respond($this->owner->request);
}
}
I am trying to use Symfony forms to handle REST requests.
I was facing issue with binding request data in symfony form. While going through Symfony's code, I found it uses
HttpFoundationRequestHandler->handleRequest
In that, for POST,
if ('' === $name) {
$params = $request->request->all();
$files = $request->files->all();
} elseif ($request->request->has($name) || $request->files->has($name)) {
$default = $form->getConfig()->getCompound() ? array() : null;
$params = $request->request->get($name, $default);
$files = $request->files->get($name, $default);
} else {
// Don't submit the form if it is not present in the request
return;
}
If you see above code, if there is form name in Our form type class, it tries to get values from request as
$request->request->get($name) // $name is form name.
This may work if we render form as HTML but if we post request through REST, it doesnt find data.
To make it work we either have to namespace it like below in request (I haven't tested personally this but have read it somewhere)
{
'form_name': {
'field1' => 'value1'
}
}
Only below requst doesnt't bind data
{
'field1' => 'value1'
}
or we will have to return form name as null from getName from form type so that it use root namespace.
public function getName()
{
return null;
}
If we return null, then it creates other problem, if there are any extra parameters in request other than form fields it gives validation errors.
"This form should not contain extra fields."
Ideally form should just fetch the feild values from request which are configured in form and should ignore any other request parameters which are unknowne to it.
e.g. if this is the form
class SampleFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('field1', 'text', array('constraints' => new NotBlank()))
->add('field2', 'password', array('constraints' => new NotBlank()));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
));
}
public function getName()
{
return null;
}
}
but in request if I am sending
{
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3'
}
It gives above validation errors. It should ideally simply ignore field3 and fetch feild1 & field2 from request.
If we compare code in NativeRequestHandler & HttpFoundationRequestHandler,
NativeRequestHandler seem to have handled it properly
if ('' === $name) {
$params = $_POST;
$files = $fixedFiles;
} elseif (array_key_exists($name, $_POST) || array_key_exists($name, $fixedFiles)) {
$default = $form->getConfig()->getCompound() ? array() : null;
$params = array_key_exists($name, $_POST) ? $_POST[$name] : $default;
$files = array_key_exists($name, $fixedFiles) ? $fixedFiles[$name] : $default;
} else {
// Don't submit the form if it is not present in the request
return;
}
where as HttpFoundationRequestHandler doesn't
if ('' === $name) {
$params = $request->request->all();
$files = $request->files->all();
} elseif ($request->request->has($name) || $request->files->has($name)) {
$default = $form->getConfig()->getCompound() ? array() : null;
$params = $request->request->get($name, $default);
$files = $request->files->get($name, $default);
} else {
// Don't submit the form if it is not present in the request
return;
}
So either we need to be able to use NativeRequestHandler or we need fix in HttpFoundationRequestHandler ?
I see below works though only in case of content-type text request (doesnt work with application/json),
$form->submit(array('field1' => $request->request->get('field1'), 'field2' => $request->request->get('field2')))
But using $form->handleRequest($request) looks more simpler and abstract.
If there is any other better and simpler solution please do suggest.
I am using Zend Framework1.11. In my Zend Form I have two zend sub form, I have added these two sub form using addSubForm function.
Now when I call this zend form in controller then isValid function is not working. I have called it as follow..
public function registeredAction(){
$form = new Application_Form_RegisteredForm();
$form->setAction('registered');
$formData = $this->_request->getPost();
if($form->isValid($formData)){
// save into database using model class.
} else {
$form->populate($formData);
}
$this->view->form = $form;
}
In following code isValid is not working, while I print_r the $fotmData requested array, it print array like:-
Array(
[personal] => Array
(
[firstname] => 'Example',
[lastname] => 'Solution'
)
[MAX_FILE_SIZE] => 8388608
[address] => Array
(
[country] => 'IND',
[state] => 'RAJ'
)
);
I have also used the setData() function but it is not working, it's give exceptional error "Message: Method setData does not exist", I have used php array_merge function but return array is not working with isValid().
Can anyone help me to solve this problem. so I can easily store form data into database.
Thanks!
Take a look at array_merge
http://php.net/manual/de/function.array-merge.php
$newFormData=array_merge($formData["personal"],$formData["address"]);
My solution is to create new base form with new method getSubFormsValues(), i.e.:
class My_Form extends Zend_Form
{
public function getSubFormsValues()
{
$values = array();
foreach ($this->getSubForms() as $form) {
$name = $form->getName();
$value = $form->getValues();
$values = array_merge($value[$name], $values);
}
return $values;
}
}
When you can call $my_form_obj->getSubFormValues() in your code.
I'm trying to test valid form data in one of my Zend_Forms however it is failing due to it having a hash element that is generated randomly and I cannot access the generated hash to put it back into the assertion data. E.g.
$form = new MyForm();
$data = array('username'=>'test');
$this->assertTrue($form->isValid($data));
This fails as it doesn't contain the hash element value.
I had same problem when my form had captcha and I wanted to test it. Two possible solutions that I cant think about:
First render the form (hash will be generated then), then take that element, take value and use it to test form.
Just remove hash element for testing.
Thanks singles. Rendering the form before the tests worked perfectly for my problem. I wouldn't be too happy about removing the hash element for testing as:
You'll be adding a certain amount of
pointless code to remove these
elements during testing.
Security features need to be tested too.
Here's a quick example of how I did it:
public function testLoginSetsSession()
{
// must render the form first to generate the CSRF hash
$form = new Form_Login();
$form->render();
$this->request
->setMethod('POST')
->setPost(array(
'email' => 'test#test.co.uk',
'password' => 'password',
'hash' => $form->hash->getValue()
));
$this->dispatch('/login');
$this->assertTrue(Zend_Auth::getInstance()->hasIdentity());
}
I recently found a great way of testing forms with hash elements. This will use a mock object to stub away the hash element and you won't have to worry about it. You won't even have to do a session_start or anything this way. You won't have to 'prerender' the form either.
First create a 'stub' class like so
class My_Form_Element_HashStub extends Zend_Form_Element_Hash
{
public function __construct(){}
}
Then, add the following to the form somewhere.
class MyForm extends Zend_Form{
protected $_hashElement;
public function setHashElement( Zend_Form_Hash_Element $hash )
{
$this->_hashElement = $hash;
return $this;
}
protected function _getHashElement( $name = 'hashElement' )
{
if( !isset( $this->_hashElement )
{
if( isset( $name ) )
{
$element = new Zend_Form_Element_Hash( $name,
array( 'id' => $name ) );
}
else
{
$element = new Zend_Form_Element_Hash( 'hashElement',
array( 'id' => 'hashElement' ) );
}
$this->setHashElement( $element );
return $this->_hashElement;
}
}
/**
*
* In your init method you can now add the hash element like below
*
*
*
*/
public function init()
{
//other code
$this->addElement( $this->_getHashElement( 'myotherhashelementname' );
//other code
}
The set method is there just for testing purposes really. You probably won't use it at all during real use but now in phpunit you can right the following.
class My_Form_LoginTest
extends PHPUnit_Framework_TestCase
{
/**
*
* #var My_Form_Login
*/
protected $_form;
/**
*
* #var PHPUnit_Framework_MockObject_MockObject
*/
protected $_hash;
public function setUp()
{
parent::setUp();
$this->_hash = $this->getMock( 'My_Form_Element_HashStub' );
$this->_form = new My_Form_Login( array(
'action' => '/',
'hashElement' => $this->_hash
}
public function testTrue()
{
//The hash element will now always validate to true
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
//OR if you need it to validate to false
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
}
You HAVE to create your own stub. You can't just call the phpunit method getMockObject because that will directly extend the hash element and the normal hash element does 'evil' stuff in its constructor.
With this method you don't even need to be connected to a database to test your forms! It took me a while to think of this.
If you want, you can push the setHashElement method ( along with the variable and the get method ) into some FormAbstract base class.
REMEMBER, in phpunit you HAVE to pass the hash element during form construction. If you don't, your init method will get called before your stub hash can be set with the set method and you'll end up using the regular hash element. You'll know you're using the regular hash element because you'll probably get some session error if you're NOT connected to a database.
Let me know if you find this helpful or if you use it.
I have created this file at My/View/Helper/FormElement.php
<?php
abstract class My_View_Helper_FormElement extends Zend_View_Helper_FormElement
{
protected function _getInfo($name, $value = null, $attribs = null,
$options = null, $listsep = null
) {
$info = parent::_getInfo($name, $value, $attribs, $options, $listsep);
$info['id'] = 'My new ID';
return $info;
}
}
How can i get the normal form elements to use this instead?
Why i want this?
Say that i use the same form multiple times on a page, the 'id='-tag of the formelements will apear multiple times, this is not w3c-valid. So initially i want to prefix the id with the id of the form.
Any better ideas or ways to do this is much apreciated.
Update: Just realized it's the same problem with the decorators :( Don't think this is the right path i've taken.
Create new form class extending Zend_Form and in the init() method use variable $ns to add prefix/suffix to all elements. You can set $ns variable through form constructor.
class Form_Test extends Zend_Form
{
protected $ns;
public function init()
{
$this->setAttrib('id', $this->ns . 'testForm');
$name = new Zend_Form_Element_Text('name');
$name->setAttrib('id', $this->ns . 'name');
$name->setLabel('Name: *')->setRequired(true);
$submit = new Zend_Form_Element_Submit('submit');
$submit->setAttrib('id', $this->ns . 'submitbutton');
$submit->setLabel('Add')->setIgnore(true);
$this->addElements(array($name, $submit));
}
public function setNs($data)
{
$this->ns = $data;
}
}
In the controller or wherever you are calling this forms specify each form instance:
$form1 = new Form_Test(array('ns' => 'prefix1'));
$this->view->form1 = $form1;
$form2 = new Form_Test(array('ns' => 'prefix2'));
$this->view->form2 = $form2;
// Validation if calling from the controller
if ($form1->isValid()) ...
Using multiple instances of same forms on a page can be validated if used as subform.
SubForms prefix all id's with the name/identifier of the subform.