I'm using Laravel 4 and I'm looking for the elegant way of handling validation errors in my controller methods. Everything in the code below works fine (in the real code I actually validate. My example just throws an exception to simulate a failed validation.) Basically, I want to get the try/catch block out of each controller and have it in another method so that I'm not repeating a lot of code that is very similar. The code I have is as follows:
routes.php
Route::resource( 'foo', 'FooController', array('only' => array('create', 'store')));
FooController.php
class FooController extends \BaseController {
public function create()
{
return View::make('foo');
}
public function store(){
try {
// Validation service call goes here. We'll just force an exception
throw new Exception('Invalid email address!');
} catch (Exception $e) {
return Redirect::back()->withErrors($e->getMessage());
}
// Everything validated so lets save the data
}
}
foo.blade.php
<html>
<body>
<h4>Welcome to page Foo!</h4>
@if($errors->any())
<h4>{{$errors->first()}}</h4>
@endif
{{ Form::open(array('url' => '/foo')) }}
{{ Form::label('email', 'E-Mail Address') }}
{{ Form::text('email', 'example@gmail.com') }}
{{ Form::submit('Submit') }}
{{ Form::close() }}
</body>
</html>
the above code works fine. The exception from validation is thrown and we are redirected back to the page with the error message shown.
What I want to do is move the whole try/catch block into a method to clear the clutter from the store() method. Something like this:
class FooController extends \BaseController {
public function create()
{
return View::make('foo');
}
public function store(){
$this->validateMe();
// Everything validated so lets save the data
}
public function validateMe()
{
try {
// Validation service call goes here. We'll just force an exception to
// simulate invalid data the sake of the example
throw new Exception('Invalid email address!');
} catch (Exception $e) {
return Redirect::back()->withErrors($e->getMessage());
}
}
}
This implementation just gives me a blank screen because the redirect isn't making its way back to Laravel. The only way to get redirected back to the form is to do something like below. BUT, that won't work because I can never get to the logic that actually saves the data.
public function store(){
return $this->validateMe();
// Everything validated so lets save the data but we can
// never get here because of the return above. This is bad!
}
Try this
$inputs = Input::all();
$validator = Validator::make(
array(
'field_name' => $inputs['field_name'],
'field_name' => $inputs['field_name']
),
array(
'field_name' => 'required',
'field_name' => 'required|numeric'
)
);
if ($validator->fails())
{
return Redirect::back()->withInput()->withErrors($validator);
}
else
{}
Now what's wrong with your code is that you added:
public function store(){
return $this->validateMe();
// Everything validated so lets save the data
}
So you are always returning from this function, your code doesn't go beyond the return statement.
I would suggest doing the validation / storing inside the Model class, this way you have less code in your controller and it's easier to modify. I made my own model but you can use existing package such as: Laravel-Model-Validation
I still don't know why you want to throw exceptions while validating, what if there is more the one field that's invalid, for instance name, email, you can only throw one exception...
But if you insist, just return an array of everything you need
} catch (Exception $e) {
return ['status' => 'failed',
'validation_object' => $validator,
'exception' => $e,
];
}
//outside of catch, no exceptions occurred,
return ['status' => 'success'];
Then in your controller
public function store(){
$result = $this->validateMe();
if($result['status'] == 'success') {
//code to save in db
return Redirect::back()->with('message', 'saved');
}
else {
return Redirect::back()->withErrors($result['exception']->getMessage());
}
I think what you are trying to do is essentially emulate the $this->validate()
method in Laravel 5 controllers. I think the gist of what happens here is that a global error handler is defined for a custom exception, which they happened to call HttpResponseException.
The default controller has a trait (essentially partial class implementation) which defines a method called validate()
here, which the child controllers can call for validation. You can also use a trait if you want or just add the necessary logic to your base controller. Pretty much every controller in a typical app needs the same thing.
When that validation doesn't pass, the method throws the aforementioned exception here. The main reason I see for this custom exception is that you can capture a bunch of information about the current request which you'll need when you catch the exception on a global level. They just construct a redirect response and save it in the HttpResponseException
instance though.
As described in the docs, all you then need to do is to write an error handler that will catch that particular exception and return the RedirectResponse
, which you bundled into the exception. That Response
is used pretty much as though it had been returned from the controller method directly. Of course you could put all sorts of other logic here to create some other kind of response to return.
I believe in Laravel 5 they do the above here, in the routing code. Going through the error handler approach means you would be catching the exception after it bubbled up a few more levels but the effect is the same.
I know this answer was sparse on code but I think the Laravel 5 code base handles this much more elengantly than anything I could think of off the top of my head and Laravel 4.2 already seems to provide the tools you'd need to replicate that.