Roteamento e geração de URLs
Normalmente, uma aplicação Yii processa certas requisições com certos handlers. Ela escolhe o handler com base na URL da requisição. A parte da aplicação que faz esse trabalho é o router, e o processo de escolher um handler, instanciá-lo e executar um método dele é o roteamento.
O processo inverso do roteamento é a geração de URLs, que cria uma URL a partir de uma rota nomeada, de argumentos de rota e de parâmetros de query. Quando você requisita depois a URL criada, o roteamento consegue convertê-la de volta nos argumentos de rota e parâmetros de query originais.
Roteamento e geração de URLs são serviços separados, mas usam o mesmo conjunto de rotas tanto para casar URLs quanto para gerá-las.
Configurando rotas
Configurando rotas, você faz a aplicação reconhecer formatos arbitrários de URL sem modificar o código existente. Você pode configurar as rotas em /config/routes.php. A estrutura do arquivo é a seguinte:
<?php
declare(strict_types=1);
use App\Controller\SiteController;
use Yiisoft\Router\Route;
return [
Route::get('/')
->action([SiteController::class, 'index'])
->name('site/index')
];O arquivo retorna um array de rotas. Ao definir uma rota, você começa com um método que corresponde a um tipo de requisição HTTP:
- get
- post
- put
- delete
- patch
- head
- options
Se você precisar de vários métodos, pode usar 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')
];Todos esses métodos aceitam um padrão de rota. O padrão de rota define como o router casa a URL durante o roteamento e como gera a URL a partir do nome da rota e dos parâmetros. Você vai conhecer a sintaxe mais adiante neste guia. Defina o handler com action() como:
- Nome de uma classe de middleware.
- Uma action de handler (um array no formato [HandlerClass, handlerMethod]).
- Um callable.
No caso de uma action de handler, uma classe do tipo HandlerClass é instanciada e o seu método handlerMethod é chamado:
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
final readonly class HandlerClass
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// ...
}
}O callable é chamado como está:
static function (ServerRequestInterface $request, RequestHandlerInterface $next) use ($responseFactory) {
$response = $responseFactory->createResponse();
$response->getBody()->write('You are at homepage.');
return $response;
}Para actions de handler e callables, os parâmetros tipados são injetados automaticamente pelo container de injeção de dependência passado para a rota.
Obtenha a requisição e o handler atuais usando os type hints ServerRequestInterface e RequestHandlerInterface.
Argumentos de rota nas actions
Os parâmetros nomeados da rota ficam guardados em CurrentRoute. Para passar um parâmetro de rota para um parâmetro do método da action, use o atributo RouteArgument:
<?php
declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Router\HydratorAttribute\RouteArgument;
use Yiisoft\Router\Route;
return [
Route::get('/post/{id:\d+}')
->action([PostController::class, 'view'])
->name('post/view')
];
final readonly class PostController
{
public function view(#[RouteArgument('id')] int $id): ResponseInterface
{
// $id contains the value from the {id} route parameter.
}
}Se o parâmetro tiver o mesmo nome do parâmetro da action, você pode omitir o nome no atributo:
public function view(#[RouteArgument] int $id): ResponseInterface
{
// ...
}Você também pode injetar CurrentRoute em uma action ou em um serviço quando precisar de vários argumentos ou de metadados da rota:
<?php
declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Router\CurrentRoute;
final readonly class PostController
{
public function view(CurrentRoute $currentRoute): ResponseInterface
{
$id = $currentRoute->getArgument('id');
$routeName = $currentRoute->getName();
// ...
}
}Argumentos de rota não são atributos da requisição. Leia-os de CurrentRoute ou de parâmetros da action marcados com RouteArgument.
Para parâmetros de rota opcionais, informe um valor padrão na rota com defaults() ou na assinatura do método da action.
Você pode adicionar handlers extras em volta do principal com o método middleware():
<?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)
];Veja o guia sobre "middleware" para saber mais sobre como implementar middlewares.
Isso é especialmente útil ao agrupar rotas:
<?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(JsonDataResponseMiddleware::class),
Route::get('/user')
->action([ApiUserController::class, 'index'])
->name('api/user/index'),
Route::get('/user/{login}')
->action([ApiUserController::class, 'profile'])
->middleware(JsonDataResponseMiddleware::class)
->name('api/user/profile'),
)
];O router executa ApiDataWrapper antes de tratar qualquer URL que comece com /api.
Você pode dar um nome a uma rota com o método name(). É uma boa ideia escolher o nome da rota com base no nome do handler.
Você pode definir um valor padrão para um parâmetro de rota. Por exemplo:
<?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'])
];Essa configuração casa tanto com /user quanto com /user/123. Nos dois casos, o serviço CurrentRoute vai ter o argumento id preenchido. No primeiro caso, ele será o padrão 42 e, no segundo, 123.
Se a URL só deve valer para um host, você pode defini-lo com host().
Roteamento
O roteamento do Yii é flexível e, internamente, pode usar diferentes implementações. O algoritmo de casamento pode variar, mas a ideia básica é a mesma.
O router casa as rotas definidas na configuração de cima para baixo. Se houver um casamento, ele para de procurar e executa o handler da rota para obter a resposta. Se nenhuma rota casar, o router passa o tratamento para o próximo middleware do conjunto de middlewares da aplicação.
Respostas OPTIONS automáticas
Quando o caminho da requisição casa com uma rota, mas o método HTTP não, o router retorna uma resposta 405 Method Not Allowed com o header Allow:
HTTP/1.1 405 Method Not Allowed
Allow: GETPara uma requisição OPTIONS no mesmo caminho, o router retorna uma resposta vazia 204 No Content com o mesmo header Allow:
HTTP/1.1 204 No Content
Allow: GETEssa resposta é gerada a partir das rotas configuradas, antes de a action da rota ser executada. Se um endpoint de API precisar de headers CORS, adicione um middleware de CORS a um grupo de rotas:
<?php
declare(strict_types=1);
use App\Api\CorsMiddleware;
use App\Api\PostController;
use Yiisoft\Http\Method;
use Yiisoft\Router\Group;
use Yiisoft\Router\Route;
return [
Group::create('/api')
->withCors(CorsMiddleware::class)
->routes(
Route::methods([Method::GET, Method::POST], '/posts')
->action([PostController::class, 'index'])
->name('api/posts')
)
];Gerando URLs
Para gerar uma URL a partir de uma rota, a rota precisa ter um nome:
<?php
declare(strict_types=1);
use App\Controller\TestController;
use Yiisoft\Router\Route;
return [
Route::get('/test')
->action([TestController::class, 'index'])
->name('test/index'),
Route::post('/test/submit/{id}')
->action([TestController::class, 'submit'])
->name('test/submit')
];A geração fica assim:
<?php
declare(strict_types=1);
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Router\UrlGeneratorInterface;
final readonly class TestController extends AbstractController
{
protected function name(): string
{
return 'test';
}
public function index(UrlGeneratorInterface $urlGenerator): ResponseInterface
{
$url = $urlGenerator->generate('test/submit', ['id' => '42']);
// ...
}
}No código acima, obtemos uma instância do gerador com a ajuda da injeção de dependência automática, que funciona com handlers de action. Em outro serviço, você pode obter a instância com uma injeção no construtor parecida. Nas views, o gerador de URLs está disponível como $url.
Depois usamos o método generate() para obter a URL. Ele aceita o nome da rota, argumentos de rota, parâmetros de query e um fragmento (hash). O código retorna /test/submit/42.
Os argumentos de rota preenchem os placeholders do padrão da rota. Os argumentos que não aparecem no padrão são adicionados à query string, desde que o array de parâmetros de query ainda não tenha o mesmo nome:
$url = $urlGenerator->generate(
'test/submit',
['id' => '42', 'utm' => 'newsletter'],
['page' => '1']
);
// $url is "/test/submit/42?page=1&utm=newsletter".Se você precisar de uma URL absoluta, use generateAbsolute().
Gerando a URL a partir da rota atual
Use generateFromCurrent() quando um link deve manter a rota atual e a maior parte dos seus argumentos. Por exemplo, uma página de detalhes pode apontar para outro número de página mantendo o nome da rota atual e o argumento id:
$url = $urlGenerator->generateFromCurrent(['page' => '2']);O método também mantém os parâmetros de query da requisição atual. Passe parâmetros de query explícitos quando o link precisar substituir ou adicionar valores na query string.
URLs com locale
Quando o locale faz parte do caminho da URL, defina-o como um argumento de rota. Em uma aplicação Yii com as rotas em config/common/routes.php, coloque as rotas localizadas em um grupo com um parâmetro de locale:
<?php
declare(strict_types=1);
use App\Web\HomePage\Action as HomePageAction;
use App\Web\Post\ViewAction as PostViewAction;
use Yiisoft\Router\Group;
use Yiisoft\Router\Route;
return [
Group::create('/{_locale:en-US|de}')
->routes(
Route::get('/')
->action(HomePageAction::class)
->name('home'),
Route::get('/posts/{slug}')
->action(PostViewAction::class)
->name('post/view')
)
];A rota /de/posts/welcome casa com a rota post/view e guarda _locale como argumento de rota com o valor de.
No middleware que escolhe o locale da requisição, leia esse argumento de CurrentRoute:
<?php
declare(strict_types=1);
namespace App\Web\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Router\CurrentRoute;
use Yiisoft\Router\UrlGeneratorInterface;
use Yiisoft\Translator\TranslatorInterface;
use Yiisoft\View\WebView;
final readonly class LocaleMiddleware implements MiddlewareInterface
{
private const DEFAULT_LOCALE = 'en-US';
public function __construct(
private CurrentRoute $currentRoute,
private TranslatorInterface $translator,
private UrlGeneratorInterface $urlGenerator,
private WebView $view,
) {}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$locale = $this->currentRoute->getArgument('_locale', self::DEFAULT_LOCALE);
$this->translator->setLocale($locale);
$this->view->setLocale($locale);
$this->urlGenerator->setDefaultArgument('_locale', $locale);
return $handler->handle($request);
}
}Registre esse middleware no grupo de rotas localizadas, antes da action da rota:
use App\Web\HomePage\Action as HomePageAction;
use App\Web\Middleware\LocaleMiddleware;
use Yiisoft\Router\Group;
use Yiisoft\Router\Route;
return [
Group::create('/{_locale:en-US|de}')
->middleware(LocaleMiddleware::class)
->routes(
Route::get('/')
->action(HomePageAction::class)
->name('home')
)
];Depois que o middleware define o argumento padrão _locale, a geração de URLs pode omiti-lo:
$url = $urlGenerator->generate('post/view', ['slug' => 'welcome']);
// On a German page, $url is "/de/posts/welcome".Para um seletor de idioma, use generateFromCurrent() e substitua apenas o argumento de locale:
$url = $urlGenerator->generateFromCurrent(
['_locale' => 'en-US'],
fallbackRouteName: 'home'
);Padrões de rota
Os padrões de rota dependem da implementação usada por baixo. A implementação padrão é o nikic/FastRoute.
Os padrões básicos são estáticos, como /test. Isso significa que eles precisam casar exatamente para a rota ser escolhida.
Parâmetros nomeados
Um padrão pode ter um ou mais parâmetros nomeados, escritos no formato {ParamName:RegExp}, onde ParamName é o nome do parâmetro e RegExp é uma expressão regular opcional usada para casar o valor. Se RegExp não for informada, o valor do parâmetro deve ser uma string sem barras.
NOTE
Você só pode usar expressões regulares dentro dos parâmetros. O resto do padrão é tratado como texto puro.
Você não pode usar grupos de captura. Por exemplo, {lang:(en|de)} não é um placeholder válido, porque () é um grupo de captura. Em vez disso, use {lang:en|de} ou {lang:(?:en|de)}.
Quando uma rota casa, o router preenche os argumentos de CurrentRoute com os valores das partes correspondentes da URL. Quando você usa a regra para criar uma URL, ela pega os valores dos parâmetros informados e os insere nas posições onde os parâmetros foram declarados.
Vamos usar alguns exemplos para mostrar como os parâmetros nomeados funcionam. Suponha que você declarou os três padrões a seguir:
'posts/{year:\d{4}}/{category}'posts''post/{id:\d+}'
/postscasa com o segundo padrão;/posts/2014/phpcasa com o primeiro padrão. Os parâmetros sãoyear, com valor 2014, ecategory, com valorphp;/post/100casa com o terceiro padrão. O valor do parâmetroidé 100;/posts/phpnão casa.
Ao gerar URLs, você deve usar os seguintes parâmetros:
echo $url->generate('first', ['year' => '2020', 'category' => 'Virology']);
echo $url->generate('second');
echo $url->generate('third', ['id' => '42']);Partes opcionais
Coloque as partes opcionais do padrão entre [ e ]. Por exemplo, o padrão /posts[/{id}] casa tanto com http://example.com/posts quanto com http://example.com/posts/42. O router só preenche o argumento id do serviço CurrentRoute no segundo caso. Nesse caso, você pode definir o valor padrão:
use \Yiisoft\Router\Route;
Route::get('/posts[/{id}]')->defaults(['id' => '1']);Partes opcionais só são suportadas no final da rota, não no meio dela.