powered by Slim Framework
Follow @NesbittBrian rss

Slim wildcard routes via route middleware

Jun 18, 2012

As I dig further into the Slim framework I started reviewing some of the community forum questions. A fun way to both learn and help is to answer questions from users. I think its one of the best ways to contribute back as it helps increase the audience of the framework. It also aleviates the main author from being the only one having to do it all the time.

This morning I read the following question about a wildcard catchall route, Can I have a GET request with variable number of parameters in the url? It seems this is something the framework currently doesn't support but is potentially slated for a 1.7 release. In the meantime I thought a simple solution might be to use route conditions in combination with route middleware to parse the incoming parameter into an array for us. Lets see how we can get this done in a generic reusable fashion.

If you don't know what route conditions and route middleware are for the Slim framework, I suggest you go and read about them first.

The GET request the user wants to parse is http://hostname/api/getitems/seafood/fruit/meat. They want to get the seafood, fruit and meat part as an array. Those parameters are also wildcard in length which means there are a variable number of them, so it might be shorter like http://hostname/api/getitems/seafood or longer like http://hostname/api/getitems/seafood/fruit/apples/bananas/chocolate.

We can start by setting up our route and applying a catch all regular expression to the route conditions.


$app->get('/api/getitems/:items', function ($items) use ($app) {
   echo $items;
})->conditions(['items' => '.+']);

When this route is matched the $items parameter will be a string that contains the remainder of the GET request URL. If we were to load the urls from earlier we would get the following output:


seafood/fruit/meat
seafood
seafood/fruit/apples/bananas/chocolate

Since we used a + in our condition regular expression we don't have to worry about handling a blank string as that won't match our route. So no we can use a route middleware to perform the explode on our string. We use PHP's closure support to wrap an anonymous function while passing in the name of the parameter we want parsed. In our example that name is items as seen above.

We need access to the current route that was matched to read it's parameters. This didn't seem to be easily done except after looking at the source for the Slim_Route I saw that the route middleware is called with three parameters. The Slim_Http_Request, Slim_Http_Response and the currently matched Slim_Route.

So here is our middleware callable.


$parseWildcardToArray = function ($param_name) use ($app) {
   return function ($req, $res, $route) use ($param_name, $app) {

      $env = $app->environment();
      $params = $route->getParams();

      $env[$param_name.'_array'] = array();

      //Is there a useful url parameter?
      if (!isset($params[$param_name]))
      {
         return;
      }

      $val = $params[$param_name];

      //Handle  /api/getitems/seafood//fruit////meat
      if (strpos($val, '//') !== false)
      {
         $val = preg_replace("#//+#", "/", $val);
      }

      //Remove the last slash
      if (substr($val, -1) === '/')
      {
         $val = substr($val, 0, strlen($val) - 1);
      }

      //explode or create array depending if there are 1 or many parameters
      if (strpos($val, '/') !== false)
      {
         $values = explode('/', $val);
      }
      else
      {
         $values = array($val);
      }

      $env[$param_name.'_array'] = $values;
   };
};

Its more preventitive code than real code. The comments above indicate the checks in place.

From what I can tell from the Slim source there is no way to inject a new parameter back into the $route->params. So for now this code injects the newly parsed array into an environment variable for the route to read. With this in place we can now augment our route to use the middleware and access the newly created array via the environment.


$app->get('/api/getitems/:items', $parseWildcardToArray('items'), function ($items) use ($app) {
   $env = $app->environment();
   var_dump($env['items_array']);
})->conditions(['items' => '.+']);

This now prints the following arrays for the same three urls as before:


array(3) { [0]=> string(7) "seafood" [1]=> string(5) "fruit" [2]=> string(4) "meat" }
array(1) { [0]=> string(7) "seafood" }
array(5) { [0]=> string(7) "seafood" [1]=> string(5) "fruit" [2]=> string(6) "apples" [3]=> string(7) "bananas" [4]=> string(9) "chocolate" }

Also, if you are using PHP 5.4+ and what to make use of array dereferencing you can change the above route to be one line like so:


$app->get('/api/getitems/:items', $parseWildcardToArray('items'), function ($items) use ($app) {
   var_dump($app->environment()['items_array']);
})->conditions(['items' => '.+']);

As mentioned this could be better if the route middleware was able to inject the new array back into the parameters rather than storing it into the environment. For now this is an easy solution to implement and is reuseable until the feature is added to the framework itself.

Read the follow up to see how it was improved in conjuction with the 1.6.4 release... Slim wildcard routes improved

PHP on a diet : Up and running with Slim  Home Multilingual site using Slim  
blog comments powered by Disqus