Se tem algo que não tem muito material na internet é sobre um roteamento com banco de dados no CakePHP, não que já não seja flexível o suficiente, mas sempre da pra melhorar, e é ai que entra o routeClass.
O que é routeClass
RouteClass é um recurso do CakePHP que permite passarmos a url para uma classe PHP analisar e responder com um array que indique qual Controller, Action e parâmetros devemos usar, caso retorne false ele vai ignorar e passar para a próxima regra do routes.php (app/Config/routes.php).
Como o routes trabalha
Para você entender melhor o que eu disse acima vou dar um exemplo, imagine que você tem a seguintes rotas no routes.php:
Gostou deste artigo?
Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/empresa', array('controller' => 'pages', 'action' => 'display', 'empresa'));
Router::connect('/contato', array('controller' => 'pages', 'action' => 'display', 'contato'));
Quando o CakePHP le as rotas ele começa no primeiro item (que no exemplo é a home do site) se a url passada conferir com '/' ele deverá retornar o array indicando qual Controller, Action e parâmetros devem ser usados, a checagem para aqui e não lê a demais rotas, agora se não conferir ele retorna false e passa para o próximo item, isso quer dizer que os itens superiores tem a prioridade. No exemplo abaixo, se eu visitar a página inicial da aplicação o segundo parâmetro não teria serventia alguma.
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/', array('controller' => 'paginainicial', 'action' => 'display'));
Em outras palavras o controller "paginainicial" nunca seria requisitado, já que o CakePHP pararia de pesquisar a rota um item antes.
Construindo uma classe de Roteamento
Claro que vou passar a classe pronta pra usar no fim do artigo, então essa parte é opcional.
Quando começamos a estudar CakePHP logo de cara já conhecemos as pastas Config, Controller, Model e View, algumas outras também são bem conhecidas, mas essas 4 formam o básico do básico, e mesmo depois de algum tempo trabalhando com o Cake você pode vir a notar que não usa a pasta Lib, pois bem, quando você quer incrementar o Core do seu CakePHP é aqui que o arquivo fica, então é isso que vamos fazer aqui, pra começar crie uma estrutura de diretórios:
- Routing
- Route
E dentro da pasta Route o seu arquivo DbRoute.php, a convenção do CakePHP nos diz que os arquivos de roteamento tem que ser prefixados pelo nome Route e estender a classe CakeRoute, neste caso minha classe se chamaria Db, então o nome final é DbRoute. Dentro do arquivo adicione a classe DbRoute e estenda a CakeRoute:
class DbRoute extends CakeRoute
{
}
Também vou usar o utility ClassRegistry para carregar o Model (afinal vamos trabalhar com banco de dados, então preciso de um model pra isso), carregue ele antes de abrir a classe, assim:
App::uses('ClassRegistry', 'Utility');
class DbRoute extends CakeRoute
{
}
Toda classe de roteamento tem dois métodos (funções) já pré-definidas e você precisa usar pelo menos um dos dois (ou os dois), eles são:
- parse: Usado para analisar as requisições, é ele quem retorna o array da rota ou o false
- match: Usado para tratar o roteamento reverso
Nós vamos usar o parse apenas, vou aproveitar e criar mais dois métodos:
- returnSlug: Vai verificar se a url veio vazia, e neste caso é a home da aplicação, então ele vai setar o '/' como url
- getPass: Vai retornar os parâmetros para o controller, o CakePHP vai usar a chave de array "pass" para pegar este argumento, então por isso o nome getPass
Para informar qual controller, action e parâmetros vamos passar para o CakePHP vamos usar um array com a seguinte estrutura:
Array(
'controller'=>'Controller',
'action'=>'Action',
'pass'=>Array(
0=>'Parâmetro 1',
1=>'Parâmetro 2',
2=>'Parâmetro 3',
)
)
Vamos ao código completo da nossa nossa classe:
<?php
App::uses('ClassRegistry', 'Utility');
class DbRoute extends CakeRoute
{
protected function returnSlug($slug)
{
if (empty($slug)) {
return '/';
}
return $slug;
}
protected function getPass($slug)
{
if (preg_match('/^(/).{1,}/',$slug)) {
$slug = substr($slug,1);
}
if (preg_match('/.{1,}(/)$/',$slug)) {
$slug = substr($slug,0,-1);
}
return explode('/',$slug);
}
public function parse($slug)
{
$slug = $this->returnSlug($slug);
$route = ClassRegistry::init('Route');
$url=$route->find('first',array('conditions'=>array('Route.slug'=>$slug)));
if(empty($url)) {
return false;
} else {
$parse['controller']=$url['Route']['controller'];
$parse['action']=$url['Route']['action'];
$parse['pass']=$this->getPass($url['Route']['pass']);
if (empty($parse['pass'])) {
unset($parse['pass']);
}
return $parse;
}
}
}
Então $slug é nossa url.
No método returnSlug() está simples, se estiver vazio é '/', e retorna o url.
O método getPass() verifica se existe "/" no começo e no fim da string e remove para evitar que gere uma entrada vazia no começo do array e outra no fim e depois transforma a string em array e retorna esse array.
E por fim o método parse() que faz a mágica toda então vou comentar linha a linha:
public function parse($slug)
{
//Usa o returnSlug() para retornar a url correta mesmo que esteja vazio
$slug = $this->returnSlug($slug);
//Inicia o Model Route na variavel $route
$route = ClassRegistry::init('Route');
//Executa um find para buscar no banco o controller e action dessa url e guarda em $url
$url=$route->find('first',array('conditions'=>array('Route.slug'=>$slug)));
//se não encontrar nada (neste caso a variável $url vem vazia)
if(empty($url)) {
//retornar false, para avisar que é pra continuar a checar as outras rotas do routes.php
return false;
} else {
//se encontrar algum resultado no campo
//organizo tudo na variável que será repassada ao Cake (falamos dela acima)
$parse['controller']=$url['Route']['controller'];
$parse['action']=$url['Route']['action'];
//aqui eu pego o retorno da getPass() e armazeno na variável também
$parse['pass']=$this->getPass($url['Route']['pass']);
//se a ['pass'] estiver vazia eu removo do array
if (empty($parse['pass'])) {
unset($parse['pass']);
}
//retorno a variável para o Cake
return $parse;
}
}
Pra fechar essa etapa, faltou apenas o banco de dados:
CREATE TABLE IF NOT EXISTS `routes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`slug` varchar(255) NOT NULL,
`controller` varchar(20) NOT NULL,
`action` varchar(20) NOT NULL,
`pass` varchar(55) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Como usar o DbRoute no meu route.php
É muito fácil, primeiro você precisa carregar o DbRoute e em seguida usá-lo:
App::uses('DbRoute', 'Routing/Route');
Router::connect(':slug', array(),array('routeClass' => 'DbRoute'));
Lembrando que você pode colocar o DbRoute primeiro ou em último lugar no routes.php, depende da sua lógica.
Como criar novas rotas no banco de dados.
É muito fácil, é só você usar relacionamentos, neste caso vai depender de como você quer, vou usar de exemplo um cadastro de Páginas:
- Muitas Páginas tem muitas URLs: HABTM
- Uma página tem muitas URLS: HasMany
- Uma página tem uma URL: HasOne
No caso de páginas, o mais indicado seria o HasOne e na hora de salvar você criar a url a partir do título da página direto no beforeSave(), mas ai é com você.
Como baixar o DbRoute
Eu subi o DbRoute no GitHub pra vocês, ajudem lá.
O link: https://github.com/erikfig/DbRouteCakePHP
Do "ladinho" tem um botão chamado Download ZIP pra quem não quer usar o GitHub.
Bom proveito.