Routing in Recess! Screencast

Posted Dec 16, 2008 by Kris Jordan | Comments ( 13 ) | Filed in: Routing | | | | |


In the first ever Recess Framework screencast we will be walking through the exciting Routing features of the framework.

What is routing?

Routing is the machinery that takes a requested URL path like “/product/23” and ‘routes’ or dispatches control to some other place your application. In most frameworks, including Recess, this is to a method in a controller. Using a framework that has fast, flexible, RESTful routing is important because URLs are fundamental to how people, search engines, and web services interact with you web application.

Simple Routing Techniques (Screencast 0:20)

Let’s dive right into some code and take a look at how we can set up a route to a method in a controller:

class TestController extends Controller {

    /** !Route GET, /hello/world */
    function aMethod() {
         echo 'Hello PHP Community!'; exit;
    }

}

What’s that funny stuff above the function? It’s a Recess Route annotation.  Recess annotations may look a bit strange but they’re really simple. They are written inside of doccomments, a language construct in PHP which begins with a forward slash and two asterisks. Recess annotations are banging. Literally, they start with an exclamation point, or, BANG! (as opposed to the @-symbol if you’re used to Java style annotations). The Route annotation has two parameters. The first is the HTTP method such as GET, POST, PUT, or DELETE and the second is the URL path.

Parametric Routes

When a part of the route is preceded with a dollar sign it becomes a method parameter. Here is an example:

class TestController extends Controller {

    /** !Route GET, /hello/$first/$last */
    function aMethod($first, $last) {
         echo "Hello $first $last!"; exit;
    }

}

Now if we browse to /hello/PHP/Community the browser will print “Hello PHP Community”. Parametric routes are often used with ID or primary key columns in a database. For example, if I were building a store in Recess I might have a Product Details page that used a route like: !Route GET, /product/$id

Multiple Routes per Method

Controller methods can have multiple routes. For example, we can combine the previous two methods into one:

class TestController extends Controller {

    /** 
     * !Route GET, /hello/world
     * !Route GET, /hello/$first/$last
     * */
    function aMethod($first = "PHP", $last = "Community") {
         echo "Hello $first $last!"; exit;
    }

}


If you accidentally add a route that conflicts with another somewhere else in your app Recess will tell you where the conflict occurred. The Recess Diagnostics error screen shows you where in your code the conflict occurred.

Keeping it DRY

If you’re familiar with Cake or Rails you may be wondering what is the upside to specifying routes in-line with my methods? The long and short of it is, it is more DRY. With a separate routes file you must duplicate the name of a controller and method which a route maps to. So if you refactor your controller code you must remember to go and update your routes file as well. By keeping the two together it’s never a mystery what URL will take you to the controller method you’re working on.

Advanced Routing Techniques (Screencast 3:00)

HTTP METHOD Aware Routes for RESTful Routing

Routing in Recess is HTTP method-aware. To demonstrate this we can have two controller methods mapped to the same URL but differing HTTP METHODs:

class TestController extends Controller {

    /** !Route GET, /same/url */
    function comingFromGet() {
         echo '
'; echo '
'; exit; } /** !Route POST, /same/url */ function comingFromPost() { echo 'POSTed!'; exit; } }

The first method will handle a GET to the url “/same/url” and the second a POST to “/same/url”. For an actual demonstration of this running check out the screen cast at minute 7:00! Having a routing system aware of HTTP methods is one way in which Recess helps simplify RESTful application develop in PHP.

Relative Routes

In Recess, Routes can be relative to their context. What does that even mean? There are three logical levels of organization for the purposes of Routing. The most general level is the application. Recess allows multiple applications to be installed at once a la Django. Within an application there may be multiple controllers and a controller can have many methods.

Implicit routes are different from explicit routes in that they do not begin with a forward slash. Check out the !Route annotation for the world method below. By adding a !RoutesPrefix annotation to a controller we will prepend any relative route in the controller with "hello/" so we can now reach the world method using "hello/world/".

/** !RoutesPrefix hello/ */
class TestController extends Controller {

    /** !Route GET, world */
    function world() {
         echo 'Hello World!'; exit;
    }

}

Implicit Routes

If you’re familiar with Rails or Cake you’re probably wondering why I needed specify routes for the world and universe methods. In many frameworks these routes would be implicit. In Recess they can be implicit too. We can delete the route annotations and still access the methods in the same way. The important difference between the way implicit routes work in Recess and other frameworks is that Recess does not rely on the name of a controller to determine the route, but rather on the routing prefixes of the application and the controller.

/** !RoutesPrefix hello/ */
class TestController extends Controller {

    function world() {
         echo 'Hello World!'; exit;
    }

}

Because we've set up a route prefix for the Tests controller we can reach the world method by using the URL: "hello/world". Implicit routes can have parameters too.

/** !RoutesPrefix hello/ */
class TestController extends Controller {

    function world($first, $last) {
         echo "Hello $first $last!"; exit;
    }

}

Now, "hello/world/Michael/Scott" will rest in the world method being called and printing "Hello Michael Scott!"

Tools (Screencast 8:00)

With routes being sprinkled throughout controllers don’t you lose the ability to look in a single place and get a sense of all of the routes in your application?

Recess! Tools is first class Recess! application that runs in the browser and is designed to help you along in development mode writing apps. With Tools we can see all of the routes explicit and implicit, relative and absolute, for any given application. The table lists the HTTP method and route corresponding which map to a controller class and method. With Recess Tools you can get a global picture of Routes in your application.

Performance (Screencast 9:00)

How in the world can you expect to get any kind of performance out of an app when you have to reflect over every single controller in order to know all routes?

This is a great question.  Routing changes as shown in the screencast take immediate effect. This is because the screencast was taken while Recess was in development mode. By switching to deployment mode the routing computation would only have to happen once because the routes won’t change. On the first request in deployment mode Recess will build up the routing data structure, a tree, and cache it either to disk or memory depending on what your server has available.  Subsequent requests simply unserialize the routing tree and Recess is off to the races. Perf has been a top priority while developing Recess and this technique enables great performance while allowing your code to stay simple, nimble, and DRY.

Wrap-up

So this has been a quick look at routing in Recess! I hope you’ll sign up to be notified when the alpha bits go public and try routing out for yourself. For more info on Recess! and other screen casts subscribe to the RSS feed and/or follow me on Twitter: KrisJordan.  Would love to hear your reactions, thoughts, and questions in the comments!

Comments

Comments are moderated. It may take some time for your comment to appear.

Recess can only be as good as the thoughts that go into it. Let us hear yours...

  1. Posted by Joel Sutherland on Dec 16, 2008
    Great screencast -- It was interesting all the way through. Why do you think other frameworks have a centralized routing system? This seems quicker to implement and just as efficient once caching is enabled.
  2. Posted by Kris Jordan on Dec 17, 2008
    Good question. Routing hasn't always been centralized.

    Back in the golden days entire web applications were built by placing scripts at various places in a web server's document root. For pretty URLs you might use a rule based routing system like mod_rewrite. Note, though, that not even mod_rewrite routes are centralized. They can be specified as close to the director(ies) in which they reroute using .htaccess files.

    When Rails exploded in popularity many of its design decisions were imitated in other frameworks. A quick look at Cake shows it is essentially an attempt to clone Rails in PHP, this holds true for its routing (http://book.cakephp.org/view/341/Routes). Even Microsoft's MVC framework uses a system like Rails' (http://tinyurl.com/55wsn2). Django's is of a different flavor, but even more centralized as it does not rely on conventions.

    Rails' style of routing simply isn't DRY. Take a look at this: screenshot: http://tinyurl.com/5jstgu

    This is a controller generated by Rails in the most recent "Blog in 15 Minutes" screen cast. Notice how each of the methods has comments above it indicating which routes point to that method. These comments are for style/reference only and will be stale as soon as the actual route specified in routes.rb changes.

    When designing routing in Recess! the conclusion was that the most useful place for a route to be is inline with the method it routes to. Though the underlying implementation is a little trickier the resulting user code is cleaner, self-documenting, and more DRY.
  3. Posted by Adam on Dec 17, 2008
    Looks promising. When will you release some code?
  4. Posted by Ivan Stojic on Dec 17, 2008
    Please don't make us use comments to annotate code in a way that is meant to be understood by computers. It's evil and defeats the whole purpose of commenting the code for your fellow humans.

    Java sank into a horrible pit of crap with XDoclet, albeit one could say that something like that was needed to unravel the EJB mess. On the other hand, you are designing something from scratch. Why would you use this type of kludge?

    Until PHP gets annotations (hopefully never), it's best to leave this type of tomfoolery to other languages.
  5. Posted by Kris Jordan on Dec 17, 2008
    @Adam - Preview bits emailed out soon.

    @Ivan - I appreciate your concerns! When the design decision was made to use annotations within doccomments it was a very deliberate one. I expected it would be controversial. This is an interesting enough topic that deserves a full post on it soon.

    Doccomments were specifically chosen because they are a first class language construct in PHP available via reflection. (See: http://tinyurl.com/3zcjud ReflectionClass::getDocComment()) Reflected methods and properties have them too.

    Whereas xdoclet was a preprocessor that parsed your Java files and generated something from it, Recess uses built-in PHP functionality to get this information. The alternative to using declarative meta/macro constructs is to ask the programmer to write more boilerplate.
  6. Posted by David on Dec 17, 2008
    Is there any chance your framework exposes the routing system to the controllers? I've written a MVC framework for a previous employeer and with the Routing by centralized config, I couldn't come up with a good way to dynamically build menu's. But if your system has all of that accessible somewhere, I can't imagine it would be to hard to add in extra checks in the docblock like "!label Pretty Action name".
  7. Posted by Tim on Dec 20, 2008
    I was looking forward your recess framework to integrate it into my current "dev stack", but the use of Doccomments is a no-go for me. I'll stick with my homegrown front-controller for now.

    Good luck with Recess !
  8. Posted by Chucho on Dec 20, 2008
    The idea is good, but unfortunately Reflection is a performance killer in PHP.
  9. Posted by Martin on Dec 21, 2008
    Hello,

    I like this idea of a controller with integrated routing informations.

    When will the source code be available? (cant wait to try it out)

    @Chucho: In the screencast Kris mentioned, that the routes will be generated and cached only once in deployment-mode, so I think (hope) the performance loss will not be a problem.
  10. Posted by Ivan Stojic on Dec 29, 2008
    @Kris
    To say that doccomments are "first class language construct" is a misnomer, because it hides the fact that doccoments are strings, and strings are the first class language construct you are manipulating.

    For operating systems with proper file systems, I actually have a different tack at doing this in my own "framework" which I don't think is too shabby: I'm considering the arrangement of files in the file system as the routing meta-data, and work out the routing from that.

    @Martin
    Call me a skeptic, but I always used PHP when I wanted to, as with any other scripting language, skip the whole "compile/make/deploy/rake/whatever" step in my development loop. I never have (and probably never will) understood the desire to make PHP into a bad copy of Rails by sticking something that needs to be done between saving a file and testing out it's funcionality.
  11. Posted by sohdubom on Mar 11, 2009
    I like the idea of this framework. I've been testing a ruby framework named Sinatra who 'listens' to get/post/put/delete verbs and is controlled by a single main controller (actually sinatra is just a thin layer above another framework ruby developers are using to create frameworks, it's named Rack which is based on Python WSGI) ... about Rails routing I'd say it's a mess because since Rails is relatively old, it started with classic routing, then added named routes and it's restfull routes are based on top of named routes and you have to keep track of your routes in the routes.rb file (not DRY at all) and the great joke is that they (rails) will change it again, since it's now merged with Merb that has a better routing system.

    It would be nice if we could:

    1. class TestController extends Controller {
    2.
    3.
    4. get '/hello/world' {
    5. echo 'Hello PHP Community!'; exit;
    6. }
    7.
    8. }

    or an implicitly unique controller:

    4. get '/hello/world' {
    5. echo 'Hello PHP Community!'; exit;
    6. }

    instead of:

    1. class TestController extends Controller {
    2.
    3. /** !Route GET, /hello/world */
    4. function aMethod() {
    5. echo 'Hello PHP Community!'; exit;
    6. }
    7.
    8. }

    just unsure if we can create a so strong DSL in PHP like that?
  12. Posted by Ding on Mar 31, 2009
    De-centralized routing is good. Does Recess support URL creation based on these rules?
  13. Posted by KevBurnsJr on Apr 8, 2009
    Ding: yes.

    $controller->urlTo('index');
    $controller->urlTo('details', $id);
    $controller->urlTo('PhotoController::details', $photoId);

    I added another url creation syntax to my fork

    $controller->urlTo('details?id='.$id);

    and I wouldn't mind some day being able to write

    $controller->urlTo('photo/details?id='.$photoId);

Comments are disabled for this post.