6. Request and Routing

(Request and routing)

6.1. Request

request parameters and attributes, attribute namespaces, errors, cookies.

6.1.1. User Input

This chapter focuses on how to use user input in Agavi actions and views. We don't go into input validation as the validation in Agavi works in such a way that you can turn it on later without touching the code in actions or views. Just remember that in real life you should never use any un-validated user input.

All the input from the user like $_GET and $_POST arrays in the web context is stored into AgaviRequestDataholder (in the web context AgaviWebRequestDataHolder but you don't have to worry about the actual implementation) object that's given to the action's execute method as an argument. Depending on your validation settings this object has either all input or just the input that has passed validation (see chapter Validation to learn more about different validation set-ups). For added security PHP's superglobals like $_GET and $_POST are emptied entirely. This is to keep developers from accidentally using insecure data. Agavi also removes so-called magic quotes from the input should that feature be enabled in your PHP set-up.

class Products_EditAction extends WebShopBaseAction
{
  // route pattern /product/(id:\d+)/edit
  // eg. www.example.com/product/123/edit to edit prd no 123

  public function executeWrite(AgaviRequestDataHolder $rd)
  {
    $prdId = $rd->getParameter('prdid');

    $product = $this->getContext()->getModel('Product', 'Products', array('id'=>'$prdId));
    $product->setName($rd->getParameter('prdname');
    $product->save();

    return 'Success';
  }

This simple example shows us two important type of input - input coming directly from the user (submitted via an HTML form perhaps) and implicit parameters from the route (URL). Both input types are accessed with getParameter(name, default) of the AgaviRequestDataHolder.

In web context cookies, HTTP headers and uploaded files are accessed just as easily with their respective access methods getCookie, getHeader and getFile.

6.1.2. Passing Data Inside the System

6.1.2.1. From Action to View/Template

{Action::setAttribute, View::getAttribute, $template array / extracted vars}

To pass data from the action to the view or template, you would use the action's setAttribute -method, which takes two parameters: a name-parameter identifying the data, and a mixed parameter of actual data. The data can be anything you can use as a variable in PHP itself, for instance strings, arrays or objects.

class Products_ViewAction extends WebShopBaseAction
{
  // route pattern /prouct/(id:\d+)
  // eg. www.example.com/product/123 to view the product with id 123
  public function executeRead(AgaviRequestDataHolder $rd)
  {
    $prdId = $rd->getParameter('prdid');

    $product = $this->getContext()->getModel('Product', 'Products', array('id' => $prdId));
    $this->setAttribute('product', $product);

    return 'Success';
  }

}

Now the attribute "product" is available in the view as an attribute which you can access via the view's getAttribute -method, and in the template as a template variable. Attributes may also be set in separate namespaces, for more information see @@attribute namespaces@@.

All the request parameter that the action recieved are also available via the AgaviRequestDataHolder -object in the view. This is also true for the template, if the request data auto-assignment is enabled in output_types.xml.

There are several variables automatically assigned in the template if set up in output_types.xml. These are the @@link:routing object@@, the request object (which is not the same as the request data object), the @@link:controller@@, the @@link:user@@ , the @@link:translation manager@@ and the request data -object. The variable name of these objects is naturally configurable to your own taste.

6.1.2.2. From Component to Another

Core messages (matched_routes, org.agavi.controller.forwards.*, fpf)

6.2. Routing

(routing functionality, getting info from the routing, generating urls)

Normally when you do HTTP GET requests, parameters are specified using a format like index.php?param1=val1&param2=val2. Routing can help you get rid of this ugly format that search engines do not particularly like and make the URI prettier for the users. Routing is basically a method to map URIs to module/action -pairs and provide information for them, such as the parameters. Think of it as replacing mod_rewrite for Apache or ISAPI_Rewrite for Microsoft IIS but with a lot more power and features. Routes are defined in the file %core.app_dir%/config/routing.xml, and to enable routing you must set the "use_routing" option to true in settings.xml.

Let's look at an example route:

<route name="viewproduct" pattern="^/products/(id:\d+)$" module="Shop" action="ViewProduct" />

A request to the URL http://host.com/products/157 would run the "ViewProduct" action in module "Shop", with a request parameter id set to "157".

Most of the "magic" is achieved using regular expressions (@linkto PCRE@). In the above example, the id parameter must consist of digits, otherwise, the pattern, and thus this whole route, would not match.

Note

The name:expression syntax is really only a convenient form for named subpatterns, which look something like (?P<name>pattern)

The example pattern is anchored at the beginning and end. This is very important, because if the pattern above wasn't anchored at the end, a URL like /products/157/YOUR-SITE-SUCKS etc would match, too. So you always have to pay attention to anchor patterns properly. There are cases when you don't want that to happen,

Routes can run for either read- or write-requests. You tell a route which requests to be applied to by using the method -attribute. For read-methods, you would use method="read", and for write-requests you would use method="write". Routes are matched for both request methods by default, and are always matched in the order they are configured.

It is worth to note that if the route has the module and action attributes set, they will overwrite route parameters named "module" and "action". If the attributes aren't set, you can use module and action parameters, and they would do what you expect -- set the module and the action of the request.

6.2.1. Optional Parts

If we take the previous Shop-example in section 6.2, we notice that we have to specify the id-parameter every time, otherwise the route will not match. So how do we add optional parts to the route?

Because the route pattern is a regular expression, this is done the same way optional parts are done in regular expressions: with the ?-quantifier.

<route name="viewproduct" pattern="^/products(/{id:\d+})?$" module="Shop" action="ViewProduct" />

The difference between this route and the previous one is that now the slash and the id -parameters are optional. That is, the request http://host.com/products would also run the Shop.ViewProduct action, but the id-parameter would be undefined. Your action can then check if the id request parameter is null, and if so show a listing of, say, all products.

6.2.2. Defaults

The default-element specifies values that should be used if a parameter is not supplied. This also applies for generating the route even if the parameter is not optional. For instance, we have a route like this:

<route name="viewproduct" pattern="^/products(/{id:\d+})(/{lang:[a-z]{2})?$" module="Shop" action="ViewProduct">
  <defaults>
    <default for="lang">en</default>
  </defaults>
</route>

indicates that if we do not specify a value for the lang-parameter (specified with the for="lang" -attribute), the default will be set to "en". Likewise, if we would remove the ?-quantifier from the end of the pattern and generate a route to viewproduct but do not specify the lang-parameter, it will use the default "en".

6.2.3. Pre- and Postfixes for Parameters

When using @@optional parts@@, you almost always need additional characters before and after the actual parameters. For that, you can use pre- and postfixes.

To use them, you enclose the name:expression part in curly braces, and outside of these, you define the pre- and postfixes that should not be included in a matched value:

^/uri(/pre-part-{varname:regexp}-post-part)$

Let's look at a more complicated example from the Sample Application:

<route name="search_engine_spam" pattern="^/products(/buy-cheap-{name:[\S\s]+}-at-agavi-dot-org)?/(id:\d+)" module="Default" action="SearchEngineSpam">
  <defaults>
    <default for="name">/buy-cheap-{chainsaws}-at-agavi-dot-org</default>
  </defaults>
</route>

This would match the request http://host.com/products/37 and http://host.com/products/buy-cheap-whatever-at-agavi-dot-org/37. The inner part is optional, and for Search Engine Optimization (or, in this case, stupid spamming) only.

The <default> here indicates that if we do not supply the "/buy-cheap-whatever-at-agavi-dot-org", it will set it to "/buy-cheap-chainsaws-at-agavi-dot-org", thus setting the name-parameter to "chainsaws", and id to null. (see @@previous section@@)

Tip

Pre- and Postfixes may also be regular expressions themselves. You must, however, define static @@default values@@ so these routes can be @@generated@@

6.2.4. Ignores

The ignores-section specifies parameters that affect the routing or output types, but are not set as request parameters. From the sample application:

<route pattern="^/({locale:[a-z]{2}(_[A-Z]{2})?})" stop="false" imply="true" cut="true" locale="${locale}@currency=GBP" callback="AgaviSampleAppLanguageRoutingCallback">
  <ignores>
    <ignore>locale</ignore>
  </ignores>
</route>

If the locale-parameter is set here, we use the route to set the application locale to the value specified. The ignore-section means that the parameter specified will not be available as a request parameter later on.

6.2.5. Routing callbacks

Routing callbacks are user-defined classes that run when Agavi tests the route for matches. The class has to implement methods that Agavi call, and these methods would return true or false depending on if the route matching is to succeed or fail.

onMatched is called when the route matches. This method can then return either true to indicate that it did indeed match, or false to indicate that even though the pattern itself matches, the route should fail.

onNotMatched is similarly called for routes that do not match the pattern, and likewise can return true to indicate that the pattern should match nevertheless, or false to indicate that ithe route should fail.

onGenerate is called when the routing engine generates an URI for the route, and can modify parameters and return true or false indicating wether this fragment should be in the route at all.

Note

For in-depth information on how to write your custom routing callbacks, please see the @@cookbook@@

6.2.6. Subroutes

Imagine you have a big application with tens or hundreds of actions. Imagine adding all those into routing.xml one by one in the <routes> -section. It would be very ineffective to parse through all those routes in order to find the correct route. Introducing... subroutes!

Subroutes are routes that are defined under other routes. Let us explain through an example:

<route name="blog" pattern="^/blog" module="Default" action="Blog">
  <route name=".index" pattern="^/$" action=".Index" />
  <route name=".article" pattern="^/(id:\d+)(-{title:\S+})?$" action=".ShowArticle" />
</route> 

This would not run the action Blog in the module Default for the request http://host.com/blog. Why not? Because the request cannot be anchored at the end of the pattern, as the pattern has children. That is, to run the Blog.Index -action, you would have to use the request http://host.com/blog/ (note the slash), and to run the Blog.ShowArticle with the id and title -parameters set, you would have to use the request http://host.com/blog/151-Agavi_rocks. This way you only have to define "top level" routes for your main actions, and you can use subroutes to define routes to actions that are conceptually below the "top level" actions, making parsing of the route much faster.

6.2.7. Other Things to Match

Routing can do more -- like match against other sources, or do things other than setting module and action. An example:

Because this is a MVC-framework, the data produced in the model should be view-independent. That is, the same data could be rendered as XHTML or a PDF or whatever you can imagine. You can define the output type by setting the output_type -attribute for the route:

<route name="rss" pattern="/rss$" output_type="rss" stop="false" cut="true" />

As you can see, the pattern is only anchored at the end. On a match, routing execution will not stop, and the matched portion ("/rss") will be cut from the route for the following tests against the remaining routes.

Now let's say your application also has fancy AJAX features, and you return the data in JSON format. You can use the exact same URLs for "normal" and AJAX requests, since there is a difference: most AJAX frameworks send special headers along with the request. We can use that to automatically set the output type for these to JSON:

<route pattern="text/javascript" source="_SERVER[HTTP_ACCEPT]" output_type="json" stop="false" />

Here, if the HTTP Accept header contains text/javascript (which it does if you make an XMLHTTPRequest using Prototype or Mootools or Dojo), then the output type will be set to "json". We are operating on a different source, $_SERVER, that allows to access it's members directly.

Note

There are other match sources available as well. Please refer to the cookbook for more information on matching against other sources and information on how to create your own sources.

6.2.8. Generating routes

To generate routes in templates or actions, you use the AgaviRouting-class which returns the URI or route as a string. You must use the name -attribute for the routes in order for the AgaviRouting-class to know which route to generate, unless you are generating a route to the same route the user currently is at. The way to generate a route is

$ro->gen(routename[, array parameters[, array options]])

So if we take our blog-route from section 6.2.6 and want to generate a route to /blog/151/Agavi_rocks, we would use

$ro->gen("blog.article", array('id' => 151, 'title' => 'Agavi_rocks'));

This would generate a relative URI, thus won't include the protocol (http/https/whatever) nor the host name. To generate a complete URI to the document with the same protocol that the user used, you set the "relative" option to false:

$ro->gen("blog.article", array('id' => 151, 'title' => 'Agavi_rocks'), array('relative' => false));

To generate a route to the same page the user is currently at, use null as the route name.

If the user has accessed the page with the HTTP-protocol and you need to generate a route to a super-secure location using HTTPS, you set the "protocol" option to "HTTPS". To generate a URI to the same route that the user is on but add an anchor (#foobar), use

$ro->gen(null, array(), array('fragment' => 'foobar'));