By your code you must be having problems creating routes and passing the parameters via GET
. Since you are not using object orientation, things get a little more boring to do, and calling files directly can be a big security error, although checking if it is a valid file.
The .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 archive 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 we will modify the code so that we can use keywords with the intention of identifying certain snippets 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 route
:controller/:params
- The URL received is
detalhe/televisao/lcd/lg
:controller
will receive the value detalhe
:params
will receive 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 implementation adapted from frameworks routing resources like Cakephp, Symfony, Laravel, etc. As these frameworks call methods, and here we just pass the parameters to the global variable $_GET
it is very important to validate them and always take care to keep the same order as defined in the route array.
What defines the names of variavels as $_GET['cat']
is the array params
of the route, so it is very important that it is defined on all routes. In frameworks this is not necessary pq the variables are the parameters of the methods, for example:
public function detalhe($categoria, $artigo = null) {
}
where the parameter $categoria
is mandatory and $artigo
nay.
Remember that error files is very important to pass the header stating that it 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);
I don’t think you can, just by . htaccess, change something according to a database. You will probably have to change Javascript directly and use . htaccess to pass as parameter (only to not give page error when user access /televisao)
– KhaosDoctor
Dude, you’re in the right direction, where exactly did you get stuck? the second part of the array you have to pass as parameter to your include file. The correct view on the problem would be the detail/televisao url becomes ? url=detail/televisao from then on you mount patterns-based routes.
– Marcelo Bonus
I recommend you use some router to interpret the urls. Easier than implementing one yourself. For example Klein - https://github.com/chriso/klein.php
– Eduardo Lelis