dynamic friendly url

Asked

Viewed 3,382 times

6

as I do for my url that and so: detalhe.php?cat=1 look like this detail/television mine .htaccess :

RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$  index.php?url=$1 [QSA] [L]

and my index linking to the pages:

$url = (isset($_GET['url'])) ? $_GET['url'] : 'home';
$url = array_filter( explode('/', $url));

$file = $url[0].'.php';
if(is_file($file))
    include $file;
else
    include '404.php';

how can I do this with my code?

  • 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)

  • 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.

  • I recommend you use some router to interpret the urls. Easier than implementing one yourself. For example Klein - https://github.com/chriso/klein.php

1 answer

1

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);
  • Guy Voce could explain me the part of $Routes =array by which type this would not receive dynamically the value. by what I intendi I will have q fill the array manually and always know the id that will enter I’m sure about it?

  • Yes that’s right, but you can create these routes by searching from the database the value prepared for url and mount the routes. For example in the category table vc has "sports cars" in a field already has the prepared value "sports cars" and vc mounts its url. There is also the possibility to add keywords to routes like :controller, :action, :params. But then you would have to use regular expression. If you need I can edit the answer with the implementation of this

  • Guy sincerely got lost trying to mean it now if you can give an Edit for me try to visualize it differently

  • The logic seems to me you understood, in case I made an implementation where you should know all the Urls. As you want something more flexible, when I get home I add the possibility to use keywords

  • yes obg the logica ta of good what ta confused me and that msm of using the keywords and to leave more dynamic

Browser other questions tagged

You are not signed in. Login or sign up in order to post.