Criando seu próprio framework PHP

O componente Routing

Rodrigo Vieira
OperacionalTI
4 min readMar 4, 2018

--

/**
* Este é o quarto de uma série de artigos traduzidos e adaptados
* a partir dos originais: "Create your own PHP Framework -
* http://symfony.com/doc/current/create_framework/index.html
* by Fabien Potencier.
*/

Antes de mergulharmos no componente de rotas, vamos refatorar nosso framework. Uma pequena alteração para deixar nossos templates ainda mais legíveis:

https://gist.github.com/rodrigoSyscop/7d82051bfce3e314ea239533fc3598f9

Com isso não precisaremos nos preocupar em extrair os dados do objeto $request em nossos templates.

Agora estamos preparados para as novas funcionalidades.

Um dos aspectos mais importantes do nosso site é o formato das suas URLs. Graças ao URL map, atualmente um simples array, nós somos capazes de desacoplar uma URL do seus respectivo código que gera a resposta HTTP associada a esta URL. Mas isso ainda não é suficiente.

E se quisermos suportar PATHs dinâmicos (ou URLs dinâmicas) permitindo-nos embutir dados diretamente na URL (/hello/Fabien) ao invés de recair em query strings (/hello?name=Fabien)?

Para suportar essa funcionalidade, adicione o componente symfony/routing como dependência:

composer require symfony/routing

Ao invés de utilizar um array para o URL map, o novo componente routing precisa de um objeto do tipo RoutingCollection:

use Symfony\Component\Routing\RouteCollection;

$routes = new RouteCollection();

Vamos adicionar uma rota que representa a URL /hello/SOMETHING e outra para um simples /bye:

use Symfony\Component\Routing\Route;

$routes->add(
'hello', new Route(
'/hello/{name}', array('name' => 'World')
)
);
$routes->add('bye', new Route('/bye'));

Cada entrada nessa coleção (collection) de rotas é formada por um nome hello e uma instância de Route, a qual é definida por um padrão hello/{name} e um array de valores default para os atributos da rota array('name' => 'World').

Resumindo, o novo componente routing depende de uma coleção de rotas RouteCollection que por sua vez é composto de diversos objetos do tipo Route.

Nota:
Leia a documentação do componente Routing para aprender mais sobre suas funcionalidades, como geração de URL, requisitos de atributos, implementação dos métodos HTTP, loaders para arquivos YAML ou XML, dumpers para arquivos PHP ou Apache rewrite rules para melhorar a performance e muito mais.

Baseado nas informações armazenadas na instância do RouteCollection, uma instância de UrlMatcher pode casar os paths de uma URL:

use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;

$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);

$attributes = $matcher->match($request->getPathInfo());

O método match() recebe um request path e retorna um array de atributos (note que a rota casada é armazenada automaticamente no atributo _route):

print_r($matcher->match('/bye'));
/* Gives:
array (
'_route' => 'bye',
);
*/

print_r($matcher->match('/hello/Fabien'));
/* Gives:
array (
'name' => 'Fabien',
'_route' => 'hello',
);
*/

print_r($matcher->match('/hello'));
/* Gives:
array (
'name' => 'World',
'_route' => 'hello',
);
*/

Nota:
O request context não é estritamente necessário em nossos exemplos, ele é usado em aplicações reais para garantir os requisitos dos métodos e mais.

O UrlMatcher lança uma excessão quando nenhuma de suas rotas casa com o path da requisição:

$matcher->match('/not-found');

// throws a Symfony\Component\Routing\Exception\ResourceNotFoundException

Agora que já temos uma visão geral de como esses novos componentes se relacionam, vamos escrever a nova versão do nosso framework:

https://gist.github.com/rodrigoSyscop/e0f9d0cc33a79928753ff1400c8900f3

Há algumas novidades nesse código:

  • Nomes das rotas são usados como os nomes dos templates.
  • Erros 500 são gerenciados corretamente.
  • Atributos da requisição são extraídos para manter nossos templates simples.
  • Configuração das rotas foi movida para seu próprio arquivo:
https://gist.github.com/rodrigoSyscop/38149e4937da92db784990b457930b69

Agora nós temos uma clara separação entre configuração (tudo que é específico à nossa aplicação em app.php) e o framework (código genérico que dá poder a nossa aplicação em front.php).

Com menos de 50 linhas de código nós temos um framework mais poderoso e mais flexível do que o anterior. Aproveite!

Usar o componente Routing ainda nos dá mais uma grande vantagem: a habilidade de gerar URLs através das definições de Route. Quando estiver usando ambos os componentes Url matching e URL generation, mudar os padrões de URL não deverá ter nenhum impacto. Quer saber como usar o Url generator? Muito simples:

use Symfony\Component\Routing;

$generator = new Routing\Generator\UrlGenerator($routes, $context);

echo $generator->generate('hello', array('name' => 'Fabien'));
// outputs /hello/Fabien

O código deve ser auto-explicativo e graças ao context, você pode até gerar URLs absolutas:

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

echo $generator->generate(
'hello',
array('name' => 'Fabien'),
UrlGeneratorInterface::ABSOLUTE_URL
);
// outputs something like http://example.com/somewhere/hello/Fabien

Dica:
Está preocupado com a performance? Baseado nas suas definições de rotas, crie um uma classe UrlMatcher altamente otimizada que pode substituir a UrlMatcher padrão:

$dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);

echo $dumper->dump();

Você também pode acompanhar o desenvolvimento através deste repositório no GitHub.

--

--