Routing and URL generation
Usually, a Yii application processes certain requests with certain handlers. It selects a handler based on request URL. The part of the application that does the job is a router and the process of selecting a handler, instantiating it and running a handler method is routing.
The reverse process of routing is URL generation, which creates a URL from a given named route and the associated query parameters. When you later request the created URL, the routing process can resolve it back into the original route and query parameters.
Routing and URL generation are separate services, but they use a common set of routes for both URL matching and URL generation.
Configuring routes
By configuring routes, you can let your application recognize arbitrary URL formats without modifying your existing application code. You can configure routes in /config/routes.php
. The structure of the file is the following:
<?php
declare(strict_types=1);
use App\Controller\SiteController;
use Yiisoft\Router\Route;
return [
Route::get('/')
->action([SiteController::class, 'index'])
->name('site/index')
];
File returns an array of routes. When defining a route, you start with a method corresponding to a certain HTTP request type:
- get
- post
- put
- delete
- patch
- head
- options
If you need many methods, you can use methods()
:
<?php
declare(strict_types=1);
use App\Controller\SiteController;
use Yiisoft\Http\Method;
use Yiisoft\Router\Route;
return [
Route::methods([Method::GET, Method::POST], '/user/{id}')
->action([SiteController::class, 'user'])
->name('site/user')
];
All these methods accept a route pattern and a handler. The route pattern defines how the router matches the URL when routing and how it generates URL based on route name and parameters. You will learn about the actual syntax later in this guide. You could specify a handler as:
- Middleware class name.
- Handler action (an array of [HandlerClass, handlerMethod]).
- A callable.
In case of a handler action, a class of type HandlerClass
is instantiated and its handlerMethod
is called:
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class HandlerClass
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// ...
}
}
The callable is called as is:
static function (ServerRequestInterface $request, RequestHandlerInterface $next) use ($responseFactory) {
$response = $responseFactory->createResponse();
$response->getBody()->write('You are at homepage.');
return $response;
}
For handler action and callable typed parameters are automatically injected using dependency injection container passed to the route. Current request and handler could be obtained by type-hinting for ServerRequestInterface
and RequestHandlerInterface
.
You could add extra handlers to wrap primary one with middleware()
method:
<?php
declare(strict_types=1);
use Yiisoft\Http\Method;
use Yiisoft\Router\Route;
return [
Route::methods([Method::GET], '/download/{id}')
->action([DownloadController::class, 'download'])
->name('download/id')
->middleware(LimitDownloadRate::class)
];
Check "the middleware" guide to learn more about how to implement middleware.
This is especially useful when grouping routes:
<?php
declare(strict_types=1);
use Yiisoft\Router\Group;
use Yiisoft\Router\Route;
return [
Group::create('/api')
->middleware(ApiDataWrapper::class)
->routes(
Route::get('/info/v2')
->action(ApiInfo::class)
->name('api/info/v2')
->middleware(FormatDataResponseAsJson::class),
Route::get('/user')
->action([ApiUserController::class, 'index'])
->name('api/user/index'),
Route::get('/user/{login}')
->action([ApiUserController::class, 'profile'])
->middleware(FormatDataResponseAsJson::class)
->name('api/user/profile'),
)
];
Router executes ApiDataWrapper
before handling any URL starting with /api
.
You could name a route with a name()
method. It's a good idea to choose a route name based on handler's name.
You can set a default value for a route parameter. For example:
<?php
declare(strict_types=1);
use App\Controller\SiteController;
use Yiisoft\Http\Method;
use Yiisoft\Router\Route;
return [
Route::methods([Method::GET, Method::POST], '/user[/{id}]')
->action([SiteController::class, 'user'])
->name('site/user')
->defaults(['id' => '42'])
];
This configuration would result in a match with both /user
and /user/123
. In both cases CurrentRoute
service will contain id
argument filled. In the first case it will be default 42
and in the second case it will be 123
.
In cause URL should be valid for a single host, you can specify it with host()
.
Routing
Yii routing is flexible, and internally it may use different routing implementations. The actual matching algorithm may vary, but the basic idea stays the same.
Router matches routes defined in config from top to bottom. If there is a match, further matching isn't performed and the router executes route handler to get the response. If there is no match at all, router passes handling to the next middleware in the application middleware set.
Generating URLs
To generate URL based on a route, a route should have a name:
<?php
declare(strict_types=1);
use App\Controller\TestController;
use Yiisoft\Router\Route;
return [
Route::get('/test', [TestController::class, 'index'])
->name('test/index'),
Route::post('/test/submit/{id}', [TestController::class, 'submit'])
->name('test/submit')
];
The generation looks like the following:
<?php
declare(strict_types=1);
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Router\UrlGeneratorInterface;
class TestController extends AbstractController
{
protected function name(): string
{
return 'test';
}
public function index(UrlGeneratorInterface $urlGenerator): ResponseInterface
{
$url = $urlGenerator->generate('test/submit', ['id' => '42']);
// ...
}
}
In the above code we obtain generator instance with the help of automatic dependency injection that works with action handlers. In another service, you can obtain the instance with similar constructor injection. In views URL generator is available as $url
.
Then we use generate()
method to get actual URL. It accepts a route name and an array of named query parameters. The code will return "/test/submit/42". If you need absolute URL, use generateAbsolute()
instead.
Route patterns
Route patterns used depend on the underlying implementation used. Default the implementation is nikic/FastRoute.
Basic patterns are static like /test
. That means they must match exactly in order for a route match.
Named Parameters
A pattern can include one or more named parameters which are specified in the pattern in the format of {ParamName:RegExp}
, where ParamName
specifies the parameter name and RegExp
is an optional regular expression used to match parameter values. If RegExp
isn't specified, it means the parameter value should be a string without any slash.
Note: You can only use regular expressions inside of parameters. The rest of a pattern is considered plain text.
You can't use capturing groups. For example {lang:(en|de)}
isn't a valid placeholder, because ()
is a capturing group. Instead, you can use either {lang:en|de}
or {lang:(?:en|de)}
.
On a route match router fills the associated request attributes with values matching the corresponding parts of the URL. When you use the rule to create a URL, it will take the values of the provided parameters and insert them at the places where the parameters are declared.
Let's use some examples to illustrate how named parameters work. Assume you've declared the following three patterns:
'posts/{year:\d{4}}/{category}
'posts'
'post/{id:\d+}'
/posts
match second pattern;/posts/2014/php
match a first pattern. Parameters are theyear
whose value is 2014 and thecategory
whose value isphp
;/post/100
match a third pattern. Theid
parameter value is 100;/posts/php
doesn't match.
When generating URLs, you should use the following parameters:
echo $url->generate('first', ['year' => '2020', 'category' => 'Virology']);
echo $url->generate('second');
echo $url->generate('third', ['id' => '42']);
Optional parts
You should wrap optional pattern parts with [
and ]
. For example, /posts[/{id}]
pattern would match both http://example.com/posts
and http://example.com/posts/42
. Router would fill id
argument of CurrentRoute
service in the second case only. For this case, you could specify default value:
use \Yiisoft\Router\Route;
Route::get('/posts[/{id}]')->defaults(['id' => '1']);
Optional parts are only supported in a trailing position, not in the middle of a route.