I have following calss to validate my input elements.
class Validator_FormValidator {
public function genValid($value){
$chainValid = new Zend_Validate();
$strLenV = new Zend_Validate_StringLength(array('min' => 4, 'max' => 10));
$strLenV->setMessage("You have entered a Invalid value","stringLengthInvalid");
$chainValid->addValidator($strLenV);
return $result;
}
}
and then in the index controller
public function indexAction()
{
$this->view->form = new Form_LoginForm();
$this->view->registrationForm = new Form_RegistrationForm();
if($this->getRequest()->isPost()) {
$validatorObj = new Validator_FormValidator();
$valid = new Zend_Validate_Callback(array($validatorObj,'genValid'));
$username = $this->_getParam('username');
if ($valid->isValid($username)) { echo 'success'; }
else {
echo 'failure';
foreach ($valid->getMessages() as $message) {
echo "$message\n";
}
}
}
}
Now I am trying to add customized error messages in my Validator_FormValidator class which are not display in my controller failure condition.
Could any of you plz guide how to do this? Is this what I am trying to do(createing a separate validator class and put all validation function in it) is a good practice?
Usual way of creating your own, custom validates is by extending Zend_Validate_Abstract or any class of already existing validator, e.g. Zend_Validate_StringLength. This way you could customize validation process as well as its all properties, e.g. messages.
Related
I need a clean solution to set data after submit a page from being populated by :
$form->loadDataFrom( $Page );
There is my code :
public function FormUpdate() {
$error="Required";
$fields = new FieldList(
TextField::create('Title', 'Title')->setCustomValidationMessage($error),
TextField::create('Description', 'Description')->setCustomValidationMessage($error),
TextField::create('Subject', 'Description')->setCustomValidationMessage($error),
);
$actions = new FieldList(
FormAction::create("FormUpdateSubmit")->setTitle('Update')
);
$Page=Versioned::get_by_stage('Page', 'Live')->filter( array('SecureCode' => $_REQUEST['id'] ))->First();
$fields->push( HiddenField::create('id','SecureCode', $Page->SecureCode ));
$fields->push( CheckboxField::create('Approbation', "Approbation")->setCustomValidationMessage($error) ); ),
$required = new RequiredFields(array(
'Title','Subject','Description'
));
$form = new Form($this, 'FormModifier', $fields, $actions, $required);
$form->loadDataFrom( $Page );
$form->setAttribute('novalidate', 'novalidate');
return $form;
}
The problem... If I change Title and Description and I empty Subject field, i'm redirected back to the form page with the error message below Subject but, All fields are reloaded from $form->loadDataFrom($Page); That wasn't good. I must prevent that data to be reloaded. In this case, datas posted must replace $Page. What I have missing?
I generally use loadDataFrom on the action that called the form (rather than inside the form function). So for example:
...
public function index()
{
$form =$this->Form();
$form->loadDataFrom($this);
$this->customise(array("Form" => $form));
return $this->renderWith("Page");
}
...
That way the function only returns the base form and you alter it as and when required.
Your form will be called once when adding it in the template, and once via request. Since all actions on a controller get the request as parameter, you can modify your form function like so:
public function FormUpdate($request = null) {
Then inside your function, only populate the form if it's not called via a request, eg.
if (!$request) {
$form->loadDataFrom($Page);
}
I created a form request class and defined a bunch of rules. Now I would like to test these rules to see if the behaviour meets our expectations.
How could I write a test to accomplish that?
Many thanks in advance for your answers!
Update: more precisely, I would like to write a unit test that would check e.g. if a badly formatted email passes validation or not. The problem is that I don't know how to create a new instance of the Request with fake input in it.
The accepted answer tests both authorization and validation simultaneously. If you want to test these function separately then you can do this:
test rules():
$attributes = ['aa' => 'asd'];
$request = new MyRequest();
$rules = $request->rules();
$validator = Validator::make($attributes, $rules);
$fails = $validator->fails();
$this->assertEquals(false, $fails);
test authorize():
$user = factory(User::class)->create();
$this->actingAs($user);
$request = new MyRequest();
$request->setContainer($this->app);
$attributes = ['aa' => 'asd'];
$request->initialize([], $attributes);
$this->app->instance('request', $request);
$authorized = $request->authorize();
$this->assertEquals(true, $authorized);
You should create some helper methods in base class to keep the tests DRY.
You need to have your form request class in the controller function, for example
public function store(MyRequest $request)
Now create HTML form and try to fill it with different values. If validation fails then you will get messages in session, if it succeeds then you get into the controller function.
When Unit testing then call the url and add the values for testing as array. Laravel doc says it can be done as
$response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content);
Here's a full example of testing validation:
use App\Http\Requests\PostRequest;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
class PostRequestTest extends TestCase
{
protected function newTestRequest($data = [])
{
$request = new PostRequest();
$request->initialize($data);
return $request
->setContainer($this->app)
->setRedirector($this->app->make(Redirector::class));
}
public function testValidationFailsWhenEmptyTitleIsGiven()
{
$this->expectException(ValidationException::class);
$this->newTestRequest(['title' => ''])->validateWhenResolved();
}
}
I'm new in Laravel and some times it's hard to undestand waht's happening.
I have a code for validate a form. If I use it in a function it works right, but dosn't work the Redirect:back() if a call it from a other function. I want to reuse the code for all forms, but can't refactor.
This is the code (not working, does't redirect, continue executing code if validation fails)
private function formValidation($data ,$rules ,$error_name = 'user', $message = 'Hay errores en el formulario')
{
$validator = Validator::make($data, $rules);
if ($validator->fails())
{
Session::Flash('message' , $message);
return Redirect::back()->withErrors($validator,$error_name)->withInput();
}
else
return $validator;
}
public function loginPost()
{
$this->formValidation(Input::all(),User::$rules_login);
..........
This code works fine, it redirects if validation fails:
public function loginPost()
{
$validator = Validator::make(Input::all(),User::$rules_login);
if ($validator->fails())
{
Session::Flash('message' , 'Hay errores en el formulario');
return Redirect::back()->withErrors($validator,'user')->withInput();
}
.............
Can anybody help me plese? Really I want to call it from another class, but after see that does not work I call fron a private function.
I try calling Redirect:to('login', $data, $rules...) and with Redirect::route too, still not working.
Tanks.
This works, so I can refactor ;)
private function formValidation($data ,$rules , $message = 'Hay errores en el formulario')
{
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
MyHelpers::sendSessionFlashMessages(array('message' => $message)); // Send a Session::flash with css information
return Redirect::back()->withErrors($validator, 'user')->withInput();
}
return $validator;
}
public function loginPost()
{
if (get_class($response = $this->formValidation(Input::all(),User::$rules_login)){ === 'Illuminate\Http\RedirectResponse')
return $response;
}
...................... rest of the function
Laravel uses the return value of the loginPost() method to determine what the response to the browser is. However, that method doesn't return the redirect - formValidation() does. So what happens is that formValidation returns a redirect, and then it gets ignored completely. If you put the redirect in the loginPost() method, it should work straight away.
I had a small test done in PHP for a Controller I had written in Symfony2:
class DepositControllerTest extends WebTestCase {
public function testDepositSucceeds() {
$this->crawler = self::$client->request(
'POST',
'/deposit',
array( "amount" => 23),
array(),
array()
);
$this->assertEquals(
"Deposit Confirmation",
$this->crawler->filter("title")->text());
}
}
Up to here, everything was great. Problem started when I realized I wanted to disable possible re-submissions while refreshing the page. So I added a small mechanism to send nonce on every submission.
It works something like this:
class ReplayManager {
public function getNonce() {
$uid = $this->getRandomUID();
$this->session->set("nonce", $uid);
return $uid;
}
public function checkNonce($cnonce) {
$nonce = $this->session->get("nonce");
if ($cnonce !== $nonce)
return false;
$this->session->set("nonce", null);
return true;
}
}
So I had to mofidy the controller to get the nonce when displaying the form, and consume it when submitting.
But now this introduces a problem. I cant make a request to POST /deposit because I dont know what nonce to send. I thought to requesting first GET /deposit to render the form, and setting one, to use it in the POST, but I suspect Symfony2 sessions are not working in PHPUnit.
How could I solve this issue? I would not want to go to Selenium tests, since they are significant slower, not to mention that I would have to rewrite A LOT of tests.
UPDATE: I add a very simplified version of the controller code by request.
class DepositController extends Controller{
public function formAction(Request $request){
$this->replayManager = $this->getReplayManager();
$context["nonce"] = $this->replayManager->getNonce();
return $this->renderTemplate("form.twig", $context);
}
protected function depositAction(){
$this->replayManager = $this->getReplayManager();
$nonce = $_POST["nonce"];
if (!$this->replayManager->checkNonce($nonce))
return $this->renderErrorTemplate("Nonce expired!");
deposit($_POST["amount"]);
return $this->renderTemplate('confirmation.twig');
}
protected function getSession() {
$session = $this->get('session');
$session->start();
return $session;
}
protected function getReplayManager() {
return new ReplayManager($this->getSession());
}
}
I'm not sure what ReplayManager does, but it looks to me as if it is not the right class to handle the 'nonce'. As the 'nonce' is ultimately stored in and retrieved from the session it should either be handled by the controller or abstracted out into its own class which is then passed in as a dependency. This will allow you to mock the nonce (sounds like a sitcom!) for testing.
In my experience problems in testing are actually problems with code design and should be considered a smell. In this case your problem stems from handling the nonce in the wrong place. A quick refactoring session should solve your testing problems.
It is possible to access the Symfony2 session from PHPUnit via the WebTestCase client. I think something like this should work:
public function testDepositSucceeds() {
$this->crawler = self::$client->request(
'GET',
'/deposit',
);
$session = $this->client->getContainer()->get('session');
$nonce = $session->get('nonce');
$this->crawler = self::$client->request(
'POST',
'/deposit',
array("amount" => 23, "nonce" => $nonce),
array(),
array()
);
$this->assertEquals(
"Deposit Confirmation",
$this->crawler->filter("title")->text());
}
EDIT:
Alternatively, if there is a problem getting the nonce value from the session, you could try replacing the two lines between the GET and POST requests above with:
$form = $crawler->selectButton('submit');
$nonce = $form->get('nonce')->getValue(); // replace 'nonce' with the actual name of the element
I have a few modules, one is an API. I want to set a different ErrorHandler for this API module.
Because when the default ErrorHandler is fired it uses the default module with the default layout, which contains HTML. Which is something I do not want my API to be returning.
I have looked on here and at the Zend Docs and have not come up with anything that allows me to do this.
Thanks for any help.
Here is the one I use:
<?php
class My_Controller_Plugin_Modular_ErrorController extends Zend_Controller_Plugin_Abstract
{
public function routeShutdown (Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
// Front controller has error handler plugin
// if the request is an error request.
// If the error handler plugin is not registered,
// we will be unable which MCA to run, so do not continue.
$errorHandlerClass = 'Zend_Controller_Plugin_ErrorHandler';
if (!$front->hasPlugin($errorHandlerClass)) {
return false;
}
// Determine new error controller module_ErrorController_ErrorAction
$plugin = $front->getPlugin($errorHandlerClass);
$errorController = $plugin->getErrorHandlerController();
$errorAaction = $plugin->getErrorHandlerAction();
$module = $request->getModuleName();
// Create test request module_ErrorController_ErrorAction...
$testRequest = new Zend_Controller_Request_Http();
$testRequest->setModuleName($module)
->setControllerName($errorController)
->setActionName($errorAaction);
// Set new error controller if available
if ($front->getDispatcher()->isDispatchable($testRequest)) {
$plugin->setErrorHandlerModule($module);
} else {
return false;
}
return true;
}
}
(BTW, This issue already was discussed here, look carefully.)
#takeshin: Thank you for sharing your plugin, this is great! And just the thing I was looking for on the google machine.
I made some changes, respectfully, to the logic that determines the request to be an "error request," since I found that the full plugin callback was running on every request, regardless of whether or not an error had occured.
I just changed the plugin hook to "postDispatch" and tested that an exception had actually occurred during dispatch. The rest of the code functions exactly the same as yours.
Now, you can put a die statement in the middle of the plugin, and you will only see it after an exception has occurred during the request.
<?php
class Rm_Controller_Plugin_Modular_ErrorController
extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
// true if response has any exception
$isError = $front->getResponse()->hasExceptionOfType('Exception');
// if there was no error during dispatch
if (!$isError) {
return false;
}
// standard error handler plugin class name
$errorHandlerClass = 'Zend_Controller_Plugin_ErrorHandler';
// if the error handler plugin is not registered, do not continue.
if (!$front->hasPlugin($errorHandlerClass)) {
return false;
}
$plugin = $front->getPlugin($errorHandlerClass);
// the module that was requested and threw error
$module = $request->getModuleName();
// the controller & action name that error handler will dispatch
$errorController = $plugin->getErrorHandlerController();
$errorAction = $plugin->getErrorHandlerAction();
// create a dummy request to test for dispatchablility
$testRequest = new Zend_Controller_Request_Http();
$testRequest->setModuleName($module)
->setControllerName($errorController)
->setActionName($errorAction);
// verify that the current module has defined an ErrorController::errorAction
if ($front->getDispatcher()->isDispatchable($testRequest)) {
// tell error controller plugin to use the module's error controller
$plugin->setErrorHandlerModule($module);
} else {
return false;
}
return true;
}
}
Just off the top of my head, you could store a copy of the original request object in the registry from a controller plugin. In the preDispatch() of your controller you could then forward to another error controller based on the requested module.
// Plugin
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$clonedRequest = clone $request;
Zend_Registry::set('originalRequest', $clonedRequest);
}
// ErrorController
public function preDispatch()
{
$request = Zend_Registry::get('originalRequest');
$this->_forward('error', 'error', $request->getModuleName());
}