UnionOfRAD/lithium — pull request #878 by jails (2013-04-06 01:09:05+00:00)
Title: New Router Feature

Hi guys I need your opinion on the following feature. Since my first li3 application, I always feel limited with URL & Routes managment in general. I started my reflexion with #416 and today I would like to share what I've done.

### Why enhancing the `Router` class ?

A good practice of nowadays development is the separation of concerns. For example splitting a monolithic application to some dedicated libraries is a way to increase this separation of concerns. If Lithium already own a solid plugins system, The `Router` class still an hurdle in the road since routes can't be separated. Who never had conflicts among a couple of routes (especially between app & admin routes).

So to keep routes "isolated" in an application or between libaries this PR introduce the concept of scopes and attachments. This mean you can group a bunch of routes inside a scope and attach them to **an arbitraty url**.

What awesome would be if I can "mount" a kind of forum library on a 'forum' subdomain or another specific URL ?

### So How does it works ?

Previously you connect routes using the following syntax :

```php
Router::connect('/', 'Pages::view');
Router::connect('/pages/{:args}', 'Pages::view');
```

To take profit of the scopes, you need to use the following scoping notation:

```php
Router::scope('app', function() {
Router::connect('/ambiguous/{:action}');
Router::connect('/{:controller}/{:action}/{:args}');
});

Router::scope('test', function() {
Router::connect('/ambiguous/{:action}');
Router::connect('/test/{:args}', array('controller' => 'lithium\test\Controller'));
Router::connect('/test', array('controller' => 'lithium\test\Controller'));
});
```

Note : the above routes should be in different routes.php files, but it' ok for examples.

### What does it change ?

Well nothing yet. Everything works and the only difference is `Router::scope()` will now returns the scope of the dispatched route.

For example with '/controller/action', `Router::scope()` will return `'app'`,
with '/test/action', `Router::scope()` will return `'test'`, and with '/ambiguous/index' it'll return `'app'`.

Indeed, since the app location is not defined right now, `Router::process()` can't resolve the ambiguity (i.e same behavior as before)

### So what can i do with that ?

So the next step is to attach scopes to kind of "mount points". For example the following example will use a prefix for the 'app' scope:

```php
Router::attach('app', array('prefix' => 'myprefix'));
```

This mean this routes will only match on urls like `http://www.domain.com/prefix`.

But you can set the mount point based on subdomain using absolute location:

```php
Router::attach('app', array(
'absolute' => true,
'host' => 'mysubdomain.mysite.com',
'scheme' => 'http://'
));
```

You can also use variables for managing subdomains like : `.mysite.com` for example:

```php
Router::attach('app', array(
'absolute' => true,
'host' => '{:subdomain:[a-z]+}.mysite.com',
'scheme' => 'http://'
));
```

### How can i write a link from a location to a different location ?

Since `Router::process()` detect the scope, the links in your templates will be generated according the scope defined with `Router::attach()`.

Suppose you browse '/controller/action' and you need a link to the 'test' scope in your view, you only need to set the `'scope'` attribute :

```php
$this->html->link('test', '/test', array('scope' => 'test'));
```

### Ok but how does the variables in location works ?

Suppose you wan't to manage subdomains like : `.mysite.com` with the following configuration :

```php
Router::attach('app', array(
'absolute' => true,
'host' => '{:subdomain:[a-z]+}.mysite.com',
'scheme' => 'http://'
));
```

with 'http://bob.mysite.com/controller/action', all your links will start with 'http://bob.mysite.com/' but you can override the default scope params with the following syntax :

```php
$this->html->link('Max home', '/home/index', array(
'scope' => array(
'app' => array(
'subdomain' => 'max'
)
)
));
```

Since here max is in current scope you could simply it to :

```php
$this->html->link('Max home', '/home/index', array(
'scope' => array(
'subdomain' => 'max'
)
));
```

### Media class wants in !

Of course separating routes is fine but you can also separate the Medias with the same scoping system :

```php
Media::attach('cdncss', array(
'absolute' => true,
'host' => 'www.cdn.com',
'scheme' => 'http://',
'prefix' => 'web/assets/'
));

Media::attach('cdnimage', array(
'absolute' => true,
'host' => 'www.cdn.com',
'scheme' => 'http://',
'prefix' => 'web/assets/'
));
```

The attachement above create some usable scopes for views.

```php
$this->html->style('style.css', array('scope' => 'cdncss'));
$this->html->image('test.gif', array('scope' => 'cdnimage'));
```

See tests for complete features

## How I can play a bit with it ?

Pretty simple just run the following commands in your document root:

```php
git clone https://github.com/jails/li3_bigbang myapp
cd myapp
composer install
chmod -R 777 atoms/app/resources
chmod -R 777 atoms/admin/resources
```

And open your browser at: `http://localhost/myapp/`

Feel free to comment and to give your opinion on this.

Thank you for reading !