URLs Amigáveis com PHP, .htaccess (Apache) e Nginx Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 2 anos, 10 meses, 5 dias atrás.

Embora muito usadas em frameworks modernos (e até os não tão modernos), URLs amigáveis ja foram uma revolução na internet, mas ainda hoje muitos tentam aplicar nos projetos com base em parâmetros GET, o que leva complexibilidade ao seu .htaccess a medida em que mais formatos de URLs aparecem, mas e que tal levar esta complexibilidade para o PHP resolver e podermos montar a URL amigável que quisermos rapidamente? Um pouco de orientação a objetos pode ajudar muito!

A proposta

A ideia é que eu possa criar um arquivo ".htaccess padrão" e o PHP cuidar de identificar qualquer formato de URL amigável que eu configurar, por exemplo:

http://site.com.br/categoria/1/camisetas-esportivas
http://site.com.br/categoria/1/paginas
http://site.com.br/categorias
http://site.com.br/admin/categoria/edit/1
http://site.com.br/contato

E o PHP seria mais os menos assim:

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

//configuro os formatos de URL que eu quero
$route->add('/{prefix}/{modulo}{acao}/{id}');
$route->add('/{modulo}/{id}/{titulo}');
$route->add('/{titulo}');

//pego as informações da URL
$params = $route->params($url);

if (isset($params['prefix']) and $params['prefix'] == 'admin') {
    //logica para carregar a administração do site
    //posso usar includes ou autoload, ou o que quiser
    include 'carrega_admin.php';
    die();
}

$modulos_disponiveis = [
    'categorias',
    'paginas',
    'usuarios'
];

if (isset($params['modulo']) and in_array($params['modulo'], $modulos)) {
    //logica para carregar um modulo do site
    //(listagem de categorias por exemplo)
    include 'carrega_modulo.php';
    die();
}

if (isset($params['titulo'])) {
    //logica para carregar paginas do site
    //(página de contato por exemplo)
    include 'carrega_pagina.php';
    die();
}

echo 'Página não encontrada';

Este seria um arquivo "bootstrap" do site, ou seja, iria carregar a aplicação, cada item entre chaves ({}) nas rotas se torna um nó no array $params, assim na linha abaixo:

$route->add('/{prefix}/{modulo}{acao}/{id}');

Teriamos um array neste formato:

$params['prefix'];
$params['modulo'];
$params['acao'];
$params['id'];

Muito mais simples de trabalhar não?

O $SERVER['PATHINFO']

Pra encontrar a URL vamos usar $_SERVER['PATH_INFO'] em vez de GET, no exemplo acima teriamos:

$url = '/';
if (isset($_SERVER['PATH_INFO']))
    $url = $_SERVER['PATH_INFO'];

//configuro os formatos de URL que eu quero
$route->add('/{prefix}/{modulo}{acao}/{id}');
$route->add('/{modulo}/{id}/{titulo}');
$route->add('/{titulo}');


//pego as informações da URL
$params = $route->params($url);

//restante do código

A variável $_SERVER['PATH_INFO'] retorna todo valor digitado usando '/' após um arquivo PHP, por exemplo:

www.seusite.com.br/index.php/categorias/1

Neste caso teriamos /categorias/1 como valor de $_SERVER['PATH_INFO'], acontece que se nada for passado a variável não existe, por isso passei um valor padrão $url = '/'; e verifiquei a existência com isset() logo na sequência.

Mas e essa classe $route, como ela funciona?

Trabalhando com as rotas

Eu criei um arquivo em src/Router/Router.php com o seguinte conteúdo:

<?php

namespace WebDevBr\Router;

class Router
{
    private $routes = [];

    /**
     * Adiciona uma nova rota
     *
     * @param String $route
     */
    public function add($route)
    {
        preg_match_all('{\{[a-z][a-zA-Z0-9_]*}', $route, $variable);
        preg_match_all('{[^\{][a-z][a-zA-Z0-9_]*}', $route, $static);

        $variable[0] = $this->trimArray('{', $variable[0]);
        $static[0] = $this->trimArray('/', $static[0]);

        $this->routes[$route]['variable'] = $variable[0];
        $this->routes[$route]['static']['order'] = $static[0];
        $this->routes[$route]['static']['values'] = array_diff($static[0], $variable[0]);
    }

    /**
     * Retorna os parâmetros configurados da rota
     * Ou null caso não seja encontrada uma rota válida
     *
     * @param  String $url [description]
     */
    public function params($url)
    {
        $result = [];
        $clean = ltrim ($url, '/');
        $params = explode('/', $clean);

        foreach ($this->routes  as $route) {
            $params = array_diff($params, $route['static']['values']);
            if (count($params) == count($route['variable'])) {
                return array_combine($route['variable'], $params);
            }
        }
    }

    /**
     * Remove um ou mais caracteres de strings em um array
     * em um ambiente ideal isso seria um trait ou arquivo
     * de funções
     */
    protected function trimArray($str, $data)
    {
        $array_fill = array_fill(0, count($data), $str);

        return array_map(
            function($v, $str) {return ltrim($v, $str);},
            $data,
            $array_fill
        );
    }
}

Tentei manter o código o mais limpo possível, Nosso exemplo completo com o carregamento do nosso novo objeto.

//Carrego minha classe Router
include __DIR__.'src/Router/Router.php';
$route = new WebDevBr\Router\Router;

$url = '/';
if (isset($_SERVER['PATH_INFO']))
    $url = $_SERVER['PATH_INFO'];

//configuro os formatos de URL que eu quero
$route->add('/{prefix}/{modulo}{acao}/{id}');
$route->add('/{modulo}/{id}/{titulo}');
$route->add('/{titulo}');


//pego as informações da URL
$params = $route->params($url);

//restante do código

Prontinho, nosso exemplo agora deve funcionar.

Removendo a index.php da URL

Com Apache e Htaccess:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Com Nginx:

location / {
    try_files $uri $uri/ /index.php?$args;
}

O PHP Built-in Server não precisa de nenhuma configuração especial, ele já trabalha corretamente se omitirmos a index.php da rota.

Indo além

Este foi um exemplo simples sobre rotas e URLs amigáveis, existem bibliotecas muito mais completas e igualmente simples de usar, vale a penas dar uma conferida:

Quando você usa um roteamento aumenta a chance de aplicar a design pattern Front Controller, ou seja, todas as entradas e saídas da aplicação passando por um único arquivo.

Conclusão

O assunto ainda vai além, mas o importante é que conseguimos facilitar a utilização das URLs amigáveis, note que as URLs continuam funcionando mesmo sem suporte ao mod_rewrite, neste caso você usa os links com index.php, e ainda continua igualmente simples e amigável suas rotas!


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!