PHPunit Dispatch controller action with POST and form - zend-framework

I have a PHPunit test like this:
public function testUsersCanRegisterWhenUsingValidData()
{
$this->request->setMethod('POST')
->setPost(array(
'username' => 'user123',
'zip_code' => '43215',
'email' => 'me1#something.com',
'password' => 'secret',
'confirm_pswd' => 'secret',
));
$this->dispatch('/account/register');
$this->assertRedirectTo('/account/login');
}
and a User controller action called register like this :
public function registerAction()
{
// Instantiate the registration form model
$form = new Application_Model_FormRegister();
// Has the form been submitted?
if ($this->getRequest()->isPost()) {
// If the form data is valid, process it
if ($form->isValid($this->_request->getPost())) {
// Does an account associated with this username already exist?
$account = $this->em->getRepository('Entities\Account')
->findOneByUsernameOrEmail($form->getValue('username'), $form->getValue('email'));
if (! $account)
{ // do something
.............
..............
} else {
$this->view->errors = array(
array("The desired username {$form->getValue('username')} has already been taken, or
the provided e-mail address is already associated with a registered user.")
);
}
} else {
$this->view->errors = $form->getErrors();
}
}
$this->view->form = $form;
}
I get an error in this line :
$account = $this->em->getRepository('Entities\Account')
->findOneByUsernameOrEmail($form->getValue('username'), $form->getValue('email'));
It's caused by $form->getValue('username') being NULL because the form has not actually been submitted, instead PHPunit has dispatched the action and set up the POST variables.
How can I get this working?

Sorry everyone. I had commented out this line to try and study my problem:
// If the form data is valid, process it
if ($form->isValid($this->_request->getPost())) {
and it turns out that my input test input was not valid and you can't use $form->getValue to get the value of an invalid form.
I didn't get any answers because this line was not commented out in my post and would have worked. Slap head............MODS feel free to delete this post if you think it is no help to anybody.

Related

How to disable filter of a field in Zend Framework 2 in the controller?

Form to add/edit user I get from service manager with already installed filter, which is the test password. But this password is not needed when the user is edited. Can I somehow disabled password field validation in the controller?
In the getServiceConfig function of the module:
// ....
'UserCRUDFilter' => function($sm)
{
return new \Users\Form\UserCRUDFilter();
},
'UserCRUDForm' => function($sm, $param, $param1)
{
$form = new \Users\Form\UserCRUDForm();
$form->setInputFilter($sm->get('UserCRUDFilter'));
return $form;
},
// ....
In the controller I first of all getting a form object from service manager:
$form = $this->getServiceLocator()->get('UserCRUDForm');
Then disable user password validation and requirements, when user is edited and password not specified:
if ($user_id > 0 && $this->request->getPost('password') == '') {
$form->.... // Someway gained access to the filter class and change the password field validation
}
And after this i make a validation:
$form->isValid();
I found it!
// If user is editted - clear password requirement
if ($user_id > 0) {
$form->getInputFilter()->get('password')->setRequired(false);
$form->getInputFilter()->get('confirm_password')->setRequired(false);
}
This lines is disables requirement of input form fields :)
if you like to set all validators by yourself, call inside your form class
$this->setUseInputFilterDefaults(false);
to disable auto element validations/filter added from zend.
if you like to remove filter from elements call in your controller after your form object this
$form->getInputFilter()->remove('InputFilterName');
$form->get('password')->removeValidator('VALIDATOR_NAME'); should do the trick.
Note that you may have to iterate trough the Validatorchain when using Fieldsets.
$inputFilter->add(array(
'name' => 'password',
'required' => true,
'allow_empty' => true,
));
And on ModeleTable: saveModule:
public function saveAlbum(Album $album)
{
$data = array(
'name' => $album->name,
);
if (isset($album->password)){
$data['password'] = $album->password;
}

CakePHP - Model validation does not work

again alot of similar questions out there but none of them really help me.
HTML5 form validation seems to be triggering with messages "Please fill in this field" instead of the model validation messages which should be "Please enter the model"
I have a form to add Computers to the database.
Here is my form:
echo $this->Form->create('Computer');
echo $this->Form->input('Computer.model', array('label' => 'Model'));
echo $this->Form->input('Computer.memory', array('label' => 'memory'));
echo $this->Form->input('Computer.hdd', array('label' => 'hdd'));
echo $this->Form->input('Computer.price', array('label' => 'price'));
echo $this->Form->end('Save Computer');
Here is the full controller code with index and add actions
<?php
class ComputersController extends AppController {
public $helpers = array('Html', 'Form', 'Session');
public $components = array('Session');
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('add');
}
public function index() {
$this->set('computers', $this->Computer->find('all'));
}
public function add() {
if ($this->request->is('post')) {
if (!empty($this->request->data)) {
$this->Computer->save($this->request->data);
$this->Session->setFlash(__('Your Computer has been saved, or so it seems.....'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('Not sure why we got here 1.'));
} else {
$this->Session->setFlash(__('By right, this should be the index page'));
}
}
}
?>
Here's the model
<?php
class Computer extends AppModel {
public $validate = array(
'model' => array(
'Please enter model name'=> array(
'rule'=>'notEmpty',
'message'=>'Please enter model'
)
)
);
}
?>
I read from other forms that triggering the model save function, which I do, will automatically trigger the model validation. How can i get the model validation to work?
Thanks
Kevin
As you were saying, if you have the notEmpty validation in the model, CakePHP adds required="required" on the input attributes. This is handled by the browser, so you see the default Please enter this field message when you try to submit an empty value. An advantage is that if you are using the browser in a different language, the message will be displayed in that language.
If you want to change that message, you can try a solution like the ones from this question. (this is probably not what you want)
If you want to remove that client-side message, you can disable it using novalidate
echo $this->Form->create('Computer', array('novalidate' => 'novalidate'));
This way, the HTML5 required property will be ignored, and you will get the message from the model.
I am not sure if there is a way to tell Cake to use the server-side value on the client.
$this->{Model}->save() returns false if the validation fails, but in your case you're redirecting with a flash message after save function. so first check the form is saving perfectly or not, if perfectly saving then redirect to listing page other wise render your view file with a flash message where you can view the validation messages.
if ($this->Computer->save($this->request->data)) {
$this->Session->setFlash(__('Your Computer has been saved, or so it seems.....'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('Unable to save form'));
}
Note: To disable html validation just do
$this->Form->inputDefaults(array(
'required' => false
));
in your view file
Hope this helps you.
Set 'novalidate' => true in options for FormHelper::create()
echo $this->Form->create('Computer', array('novalidate' => true));
For more information, go to http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html

Zend: Redirect from form without validation

I have a form for the creation of new "groups". I now added a small "go back" image with which the user should be able to go back one step. I don't know why, but when I click this new image, the controller and action used for the form which I want to leave (/admin/creategroup) is called again with HTTP POST set. Therefore, the form validation is done, and I'm stuck at this form with the validation errors displayed.
This is a snippet of the code from my form with both image-buttons. I wan't the "go back"-image to redirect me to the specified controller without validating the form:
$this->addElement('image', 'btnBack', array (
'name' => 'btnBack',
'id' => 'btnBack',
'label' => '',
'title' => 'Go back',
'alt' => 'Go back',
'src' => '/img/undo.png',
'onClick' => "window.location='/admin/groupoverview'"
));
$this->addElement('image', 'btnSave', array (
'name' => 'btnSave',
'id' => 'btnSave',
'label' => '',
'title' => 'Save this new group',
'alt' => 'Save this new group',
'src' => '/img/save.png',
'onClick' => "document.forms[0].submit();"
));
Edit:
I already thought of the possibility to check in /admin/creategroup whether it was called from the 'btnBack'-image or the 'btnSave'-image and skip form validation and redirect correctly if the source was the 'btnBack'-image.
I just think that there should be a nicer solution to directly redirect from the form and circumvent calling /admin/creategroup again.
Edit2:
My view script:
<div id="createGroupMask">
<br/>
Use the form below to create a new group
<?php
$this->form->setAction($this->url());
echo $this->form;
?>
</div>
My action in the controller:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
Now there is something to work with:
The isValid() loop is incorrect, your form will never evaluate as inValid with respect to the elements you've presented, you will never get to the else.
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
/* This is incorrect */
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
My problem is that I'm not sure what is going to be submitted from your form, I'm not really familiar with how your using "onClick" and what I presume is javascript. It looks like element btnBack should redirect on click and element btnSave should POST. However this does not seem to be happening.
I have done this type of thing in PHP and ZF with submit buttons, perhaps the flow of what I did will help:
NOTE: for this type of flow to work you must give the button element a label. The label is used as the submit value.
//psuedoCode
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
//I would probably opt to perform this task with a switch loop
if ($form->getValue('btnBack') === some true value) {
$this->_redirect('new url');
}
if ($form->getValue('btnSave') === some true value) {
//Process and save data
}
} else {
//Display form errors
}
$this->view->form = $form;
}
I think when all is said and done the crux of your problem is that you did not give your button elements a label.
I tried adding labels to my images, but this didn't work.
I also tried to use the isChecked() method on my btnBack-image like this:
if ($form->btnBack->isChecked()) {
// 'Go back' image was clicked so this is no real error, just redirect
}
This didn't work either.
I finally was able to check which image was clicked via the following method as answered in Zend form: image as submit button:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
if (isset($this->_request->btnBack_x)) {
// 'Go back' image was pressed, so this is no error
// -> redirect to group overview page
$this->_redirect('/admin/groupoverview');
}
}
}
$this->view->form = $form;
}
I guess this doesn't thoroughly answer the original question as the validation is still done and I'm only checking for this 'special case' where the 'Go back' image was clicked, but I'll mark it as answered anyways.
Tim Fountain suggested an even cleaner approach in my somewhat related question:
Zend forms: How to surround an image-element with a hyperlink?

How can I replace this _forward() with something that can exit?

I use the following code over and over in my zend framework application. It is used in action() to check if an article exists. If not, the user shall see an error message:
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if (!$article) {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
return $this->_forward('error', null, null, array(
'message' => 'Article not found',
));
}
I was wondering how to factor this out into an own method to reduce the code load in all actions.
I came to something like this:
protected function myAction() {
$article = $this->getArticleIfExists($this->_getParam('url', ''));
if ($article == null) {
return;
}
}
protected function getArticleIfExists($url) {
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if ($article) {
return $article;
} else {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
$this->_forward('error', null, null, array(
'message' => 'Article not found',
));
return nulL;
}
}
I still would like to get rid of the if case in myAction(), but _forward() does not allow to exit the execution (of course, because it still needs to execute the other actions).
Another possibility (I have implemented in some other controllers) is this:
protected function myAction() {
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if (!$article) {
return $this->notFound('Article does not exist');
}
}
protected function notFound($message) {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
return $this->_forward('error', null, null, array(
'message' => $message,
));
}
Again we have this if check in the action. It’s already better than before, but can we make it even better?
How can I circumvent this? Is there a possibility to do it without losing the current URL? With a Redirector I can of course exit, but then I would lose the current URL (/controller/myaction/url/hello -> /error/error/message/Article%20not%20found)
A possible approach would be to throw an Exception. Because of the Zend_Controller_Plugin_ErrorHandler this will automatically redirect you to the ErrorController without any further code being executed.
If you don't want to get to the ErrorController but only to the current controller's error actions, you can simply modify the plugin in the controller's init method:
public function init()
{
$plugin = Zend_Controller_Front::getInstance()->getPlugin('Zend_Controller_Plugin_ErrorHandler');
$plugin->setErrorHandlerModule('default')
->setErrorHandlerController('MyController')
->setErrorHandlerAction('error');
}
But of course you can also write your own ErrorHandler plugin for a more fine grained error handling. This is described in the Zend Framework Manual on Plugins
For something as simple as just displaying a "* does not exist" against a user request I prefer to leave the user in the application and just hit them with a flashmessenger notice and leave them on the page to make another request (if appropriate):
public function indexAction() {
//get form and pass to view
$form = new Form();
$this->view->form = $form;
try {
//get form values from request object
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost())) {
$data = $form->getValues();
// Do some stuff
}
}
} catch (Zend_Exception $e) {
$this->_helper->flashMessenger->addMessage($e->getMessage());//add message to flashmessenger
$this->_redirect($this->getRequest()->getRequestUri());//perform redirect to originating page so the messenger will flash
}
}
This is simple and works well when the possibility for incorrect user input exists.

Validate a set of fields / group of fields in Zend Form

Is there any good solution for the following requirements:
A form with one field for zip code and default validators like number, length, etc.
After submission, the form is checked against a database.
If the zip code is not unique we have to ask for an city.
Examples:
Case 1: Submited zip code is unique in database. Everything is okay. Process form
Case 2: Submited zip code is not unique. Add a second field for city to the form. Go back to form.
We want to handle this in an generic way (not inside an controller). We need this logic for
a lot of forms. First thought was to add it to isValid() to every form or write a
validator with logic to add fields to the form. Subforms are not possible for us, because we need this for different fields (e.g. name and street).
Currently I'm using isValid method inside my forms for an User Form to verify the password and confirm password field. Also, when the form is displayed in a New Action, there are no modifications, but when displayed in an Edit Action, a new field is added to the form.
I think that is a good option work on the isValid method and add the field when the validation return false, and if you want something more maintainable, you should write your own validatator for that purpose.
Take a look at my code:
class Admin_Form_User extends Zf_Form
{
public function __construct($options = NULL)
{
parent::__construct($options);
$this->setName('user');
$id = new Zend_Form_Element_Hidden('id');
$user = new Zend_Form_Element_Text('user');
$user->setLabel('User:')
->addFilter('stripTags')
->addFilter('StringTrim')
->setAllowEmpty(false)
->setRequired(true);
$passwordChange = new Zend_Form_Element_Radio('changePassword');
$passwordChange->setLabel('Would you like to change the password?')
->addMultiOptions(array(1 => 'Sim', 2 => 'Não'))
->setValue(2)
->setSeparator('');
$password = new Zend_Form_Element_Password('password');
$password->setLabel('Password:')
->addFilter('stripTags')
->addFilter('StringTrim')
->setRequired(true);
$confirm_password = new Zend_Form_Element_Password('confirm_password');
$confirm_password->setLabel('Confirm the password:')
->addFilter('stripTags')
->addFilter('StringTrim')
->addValidator('Identical')
->setRequired(true);
$submit = new Zend_Form_Element_Submit('submit');
$submit->setLabel('Save');
$this->addElements(array($id,$name,$lastname,$group,$user,$passwordChange,$password,$confirm_password,$submit));
$this->addDisplayGroup(array('password','confirm_password'),'passwordGroup');
$this->submit->setOrder(8);
$this->setDisplayGroupDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div','id' => 'div-password'))
)
);
$passwordChange->clearDecorators();
}
public function addPasswordOption()
{
$this->changePassword->loadDefaultDecorators();
$this->getDisplayGroup('passwordGroup')
->addDecorators(array(
array('HtmlTag', array('tag' => 'div','id' => 'div-password'))
)
);
$this->password->setRequired(false);
$this->confirm_password->setRequired(false);
}
public function setPasswordRequired($flag = true)
{
$this->password->setRequired($flag);
$this->confirm_password->setRequired($flag);
}
public function isValid($data)
{
$confirm = $this->getElement('confirm_password');
$confirm->getValidator('Identical')->setToken($data['password']);
return parent::isValid($data);
}
}
So, in my controller:
public function newAction()
{
$this->view->title = "New user";
$this->view->headTitle($this->view->title, 'PREPEND');
$form = $this->getForm();
if($this->getRequest()->isPost())
{
$formData = $this->_request->getPost();
if($form->isValid($formData))
{
$Model = $this->getModel();
$id = $Model->insert($formData);
$this->_helper->flashMessenger('The user data has beed updated.');
$this->_helper->redirector('list');
}
}
$this->view->form = $form;
}
public function editAction()
{
$this->view->title = "Edit user";
$this->view->headTitle($this->view->title, 'PREPEND');
$id = $this->getRequest()->getParam('id');
$form = $this->getForm();
// Add yes or no password change option
$form->addPasswordOption();
$Model = $this->getModel();
if($this->getRequest()->isPost())
{
$formData = $this->getRequest()->getPost();
// Change password?
if($formData['changePassword'] == 2) $form->setPasswordRequired(false);
if($form->isValid($formData))
{
$Model->update($formData);
$this->_helper->flashMessenger('The user data has beed updated.');
$this->_helper->redirector('list');
}
}
$data = $Model->getById($id)->toArray();
$form->populate($data);
$this->view->form = $form;
}
You will probably need a Javascript form validator for that. In the submit function perform an AJAX call to check if the zipcode is unique. If not, show an extra city field.
But you still have to perform the validation server side: never trust user input, even if it's validated on the client side.