For your code you should be having trouble creating routes and passing parameters via GET
. Since you're not using object-oriented, things get a bit more annoying to do, and calling direct files can be a big security mistake, although it's a valid file.
O .htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^$ index.php?url=$1 [L]
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>
The file index.php
if (isset($_GET['url']) === false) {
include '400.php';
exit();
}
$url = $_GET['url'];
$routes = array(
'' => array(
'file' => 'principal',
'params' => array()
),
'detalhe/televisao' => array(
'file' => 'detalhe',
'params' => array('cat' => 1)
)
);
if (isset($routes[$url])) {
$actualRoute = $routes[$url];
$file = __dir__ . DR . $actualRoute['file'] . '.php';
if (file_exists($file)) {
foreach ($actualRoute['params'] as $name => $value) {
$_GET[$name] = $value;
}
include $file;
} else {
include '501.php';
}
} else {
include '404.php';
}
The above example makes it necessary for all URLs to be known, so let's modify the code so that we can use keywords to identify certain portions of the requested url.
We will define two keywords :controller
and :params
which will indicate respectively the file that will be loaded and the parameters that will be passed.
For example:
- We created a
:controller/:params
- The URL received is
detalhe/televisao/lcd/lg
-
:controller
will receive detalhe
-
:params
will get the value televisao/lcd/lg
.
Changing our code will look like this:
define('DR', DIRECTORY_SEPARATOR);
// Caso o htaccess não tenha passado a variável 'url', redireiona para
// o erro 404.
if (isset($_GET['url']) === false) {
include '400.php';
exit();
}
// Obetm o caminho desejado e trata retirando a barra no final e barras
// duplicadas.
$url = $_GET['url'];
if ( substr( $url, -1 ) == '/' ) {
$url = substr( $url, 0, -1 );
}
$url = str_replace( '//', '/', $url);
// Define as rotas aceitas pelo sistema.
// Uma rota pode ser definida com um valor exato do caminho ou usar as palavras
// chaves abaixo:
//
// :controller que define o nome do arquivo que será chamado e
// :params que define os parâmetors que serão passados para o arquivo.
$routes = array(
'' => array(
'file' => 'principal',
'params' => array()
),
'detalhe/televisao' => array(
'file' => 'detalhe',
'params' => array('cat' => 1)
),
'detalhe/:params' => array(
'file' => 'detalhe',
'params' => array('cat', 'artigo')
)
);
// Define quais são as palavras chaves
$keywords = array(
'/',
':controller',
':params'
);
// Define a expressão regular para cada palavra chave.
$keywordsPattern = array(
// Apenas para escapar as barras na expressão regular
'\/',
// Aceita letras de "a" a "z" e os simbolos "_" e "-"
'(?P<controller>[\w_\-]+)',
// Obtem tudo a partir de quando for achado a palavra chave ":params"
// Exemplo:
// Url = detalhe/televisao/1
// Rota = 'detalhe/:params'
// Parametros encontrados: 'televisao/1'
'(?P<params>.+)'
);
// Inicia a variável que irá armazenar os valores das palavras chaves
// encontradas.
$matches = array();
// Percorre todas as rotas
foreach ($routes as $route => $config) {
// Troca as palavras chaves por suas respectivas expressões regulares.
// Exemplo:
// Rota: 'detalhe/:params'
// Regex: /^detalhe\/(?P<params>.+)\/?$/i
$map = '/^' . str_replace($keywords, $keywordsPattern, $route) . '\/?$/i';
// Verifica se a url requisitada atende a expressão regular da rota.
if( preg_match( $map, $url, $matches ) === 1) {
// Se foi atendida define a rota atual.
$actualRoute = $config;
// Verifica se foi encontrada a palavra chave :controller e define o
// o nome do arquivo.
if (isset($matches['controller'])) {
$actualRoute['file'] = $matches['controller'];
}
// Verifica se foi encontrada a palavra chave :params
if (isset($matches['params'])) {
// Separa a string encontrada como por exemplo 'televisao/1' pelas
// barras e define os valores dos parâmetros.
// Por exemplo, se nas rotas foi definido
// 'params' => array('cat', 'artigo')
// então
// 'cat' = televisao
// 'artigo' = 1
$params = explode('/', $matches['params']);
foreach ($actualRoute['params'] as $key => $param) {
$actualRoute['params'][$param] = isset($params[$key]) ? $params[$key] : null;
unset($actualRoute['params'][$key]);
}
}
// Se a rota foi encontrada, para de percorrer as rotas.
break;
}
}
// Se não foi encontrada nenhuma rota ainda, verifica se a url requisitada
// atende alguma de forma direta. Um exemplo no nosso caso seria a rota
// 'detalhe/televisao'.
if ($actualRoute !== false && isset($routes[$url])) {
$actualRoute = $routes[$url];
}
// Faz a inclusão dos arquivos.
if ($actualRoute) {
$file = __dir__ . DR . $actualRoute['file'] . '.php';
if (file_exists($file)) {
foreach ($actualRoute['params'] as $name => $value) {
$_GET[$name] = $value;
}
include $file;
} else {
include '501.php';
}
} else {
include '404.php';
}
This is an adapted implementation of the routing capabilities of frameworks such as CakePHP, Symfony, Laravel, etc. As these frameworks call methods, and here we only pass the parameters to the global variable $_GET
it is very important to validate them and always take care to maintain the same order of the set in the array of routes.
What defines variable names as $_GET['cat']
is the params
array of the route, so it is very important that it is defined on all routes. In the frameworks this is not necessary because the variables are the parameters of the methods, for example:
public function detalhe($categoria, $artigo = null) {
}
where $categoria
is required and $artigo
is not.
Remember that error files are very important to pass the header stating that this is an error.
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request', true, 400);
header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found', true, 404);
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
header($_SERVER['SERVER_PROTOCOL'] . ' 501 Not Implemented', true, 501);