Validating Route Parameters in Laravel 4 Another Way(?)

Note: as the ‘?’ indicates in the title, I’m not sure if this is the “best” way or even a “good” way. But it’s definitely a different way than Laravel’s regular expression route constraints.

I realized recently that I wasn’t doing a good job of validating the route parameters in my Laravel application in my RESTful URIs, so I started working on a solution.

First Attempt

I started by creating a helper function called isValidId() with the idea that for controller actions that passed in a route parameter, I’d call isValidId() on the param(s):

#Controller Action
public function show($id)
{
if(!isValidId) App::abort(404);
$video = Video::find($id);
...

For the function itself, I looked at what I could use to validate whether or not the param was, in fact, a numeric ID.

is_int(): This would work fine except that the route parameters are actually strings.

is_numeric(): This one doesn’t care if it’s a string or not, but it will also return true for decimals, among other non-whole numbers.

ctype_digit(): Never used this  before, but it does the trick. Only returns true for whole numbers passed in as strings.

I know I could also use regular expressions here, but it just seemed like overkill.

function isValidId($id)
{
$id = (string) $id; //you can use this on ints to by casting them to a string first.
return ctype_digit($id);
}

So now I’m looking all through my controllers for actions that pass in route parameters and pasting in my isValidId() function. Then it dawned on me that this wasn’t very efficient.

Second Attempt

I realized a better way to do this was to utilize Laravel’s filters. So in app/filters.php, I created a “validateId” filter that uses my isValidId() function:

Route::filter('validateId', function($route)
{
$params = $route->getParameters();
foreach ($params as $key => $value)
{
if (!isValidId($value)) App::abort(404);
}
});

Now, all I had to do was create or edit constructors in all of the controllers that passed in route parameters and use “only” to choose the actions that actually accepted parameters.

$this->beforeFilter('validateId', array('only' => array('edit','update','destroy')));

Then I decided to see what would happen if I just applied the filters to entire controllers (index, etc.) to see if it would break something and it didn’t because those actions don’t return router parameters to be validated. A-ha!

Final Attempt

I quickly realized (this doesn’t happen very often) that my application does not use any route parameters that aren’t numeric IDs, so I can make this even DRYer. I removed all the validateId filter references in the constructors and simple applied it as a filter in a global route group.

Route::group(array('before' => array('forceSSL|validateId')), function()
{
...

Why not just use Regular Expression Route Constraints?

The problem (for me) with using regular expression route constraints for this is:

  • I don’t know how to redirect to some other place than a 404 if I need to. Is this possible?
  • It doesn’t seem as DRY to me when I have to add the following to the end of every route declaration:
    ->where('id', '[0-9]+')

Thoughts? Am I stupid for doing it this way?

  • blessing

    I think there’s a way to define your bindings once: Route::bind(param, function(value, route));

    Check out the section of documentation right above this: http://laravel.com/docs/routing#throwing-404-errors