Redirecionamento de Erros: descubra os 404 e diminua a insatisfação do usuário

Uma das coisas mais frustrantes da navegação na internet é quando você vê um link que te interessa e quando clica é redirecionado pra um erro.
O 404 é o mais comum deles, isso porque ele é muito fácil de acontecer. Se alguém citou uma página do seu site que não existe mais, ou algum erro no script constroi uma url errada dinamicamente o 404 é invevitável você não vai percebê-lo rapidamente.
O CakePHP através do método link do helper Html já previne alguns problemas porém ainda não é possível administrar os erros de maneira fácil.
Pensando nisso, eu desenvolvi um sistema para informar quando os 404 acontecem e criar redirecionamentos para que eles não se repitam. Isto não é difícil de ser feito já que o CakePHP tem boas maneiras de controlar este erro.
A primeira coisa a se fazer é criar a tabela onde ficaram armazenados estes erros.

CREATE TABLE `redirects` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`num_errors` int(10) UNSIGNED NOT NULL,
`page_from` varchar(255) NOT NULL,
`page_to` varchar(255),
`num_redirects` int(10) UNSIGNED,
PRIMARY KEY (`id`)
)

Após a tabela criamos o modelo com métodos para contar e manipular os erros e os redirecionamentos: (/models/redirect.php)

<?php
class Redirect extends AppModel {
var $name = 'Redirect';
public function manageError($url){
$redirects = $this->find(
'first',
array(
'conditions' => array('Redirect.page_from' => $url)
)
);
if(!$redirects){
$redirects['Redirect']['num_errors'] = 0;
$redirects['Redirect']['page_from'] = $url;
$redirects['Redirect']['num_redirects'] = 0;
$redirects['Redirect']['page_to'] = null;
}
$redirects['Redirect']['num_errors']++;
$this->set(
$redirects
);
return $this->save();
}
public function manageRedirect($urlfrom, $urlTo){
$redirect = $this->find(
'first',
array(
'conditions' => array(
'Redirect.page_from' => $urlfrom
)
)
);
$redirect['Redirect']['num_redirects']++;
$this->set($redirect);
return $this->save();
}
}
?>

Com isso, já podemos criar o armazenamento, redirecionamento e contagem dos erros. Esta será feita escrevendo a classe AppError que é feita exatamente para manipular estes erros. (/app_error.php)

<?php
class AppError extends ErrorHandler{
var $Error;
public function error404($params){
$this->Redirect = ClassRegistry::init('Redirect');
$page = $this->Redirect->find(
'first',
array(
'conditions' => array(
'page_from' => $params['url']
)
)
);
if($page && $page['Redirect']['page_to']){
$this->Redirect->manageRedirect(
$params['url'],
$page['Redirect']['page_to']
);
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch($page['Redirect']['page_to']);
}
else{
$this->Redirect->manageError($params['url']);
$this->controller->set('url', $params['url']);
$this->_outputMessage('error404', $params);
}
}
}
?>

Agora os erros já estão sendo contados e redirecionados porém sua administração tem que ser feita diretamente no banco de dados. É interessante portanto também criar métodos para gerenciar estes erros e redirecionamentos. ficamos então com o controller: (/controllers/redirects_controller.php)

<?php
class RedirectsController extends AppController {
var $name = 'Redirects';
var $helpers = array('Html', 'Form');
var $paginate = array(
'limit' => 25,
'order' => array(
'num_errors' => 'desc'
)
);
function index() {
$this->Redirect->recursive = 0;
$this->set('redirects', $this->paginate());
}
function create_redirect($id = null) {
if (!$id && empty($this->data)) {
$this->Session->setFlash(__('Redirecionamento Inválido', true));
$this->redirect(array('action'=>'index'));
}
if (!empty($this->data)) {
if ($this->Redirect->save($this->data)) {
$this->Session->setFlash(__('O Redirecionamento foi salvo.', true));
$this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('O Redirecionamento não pode ser salvo. Por favor, tente novamente.', true));
}
}
if (empty($this->data)) {
$this->data = $this->Redirect->read(null, $id);
}
}
function delete($id = null) {
if (!$id) {
$this->Session->setFlash(__('Redirecionamento com id inválido', true));
$this->redirect(array('action'=>'index'));
}
if ($this->Redirect->del($id)) {
$this->Session->setFlash(__('Redirecionamento excluído', true));
$this->redirect(array('action'=>'index'));
}
}
}
?>

Observem que este controller não está utilizando nenhum método de autenticação. Fica em aberto o método a ser utilizado sendo que todas as páginas devem ficar invisíveis ao usuário comum. As views necessárias para este controller são:
create_redirect (/views/redirects/create_redirect.ctp):

<?php echo $form->create('Redirect', array('action' => 'create_redirect'));?>

<?php __('Criar Redirecionamento');?>
<?php
echo $form->input('id');
echo $form->input('page_from', array('label'=>'URL do 404', 'disabled' => true));
echo $form->input('page_to', array('label' => 'Redirecionar para'));
?>

<?php echo $form->end('Criar');?>

  • <?php
    echo $html->link(
    __('Excluir', true),
    array(
    'action' => 'delete',
    $form->value('Redirect.id')
    ),
    null,
    sprintf(
    __('Você tem certeza que deseja excluir # %s?', true),
    $form->value('Redirect.id')
    )
    );
    ?>
  • <?php
    echo $html->link(
    __('Listar 404s', true),
    array('action' => 'index')
    );
    ?>

e index (/views/redirects/index.ctp):

<?php __('Redirecionamentos');?>

<?php echo $paginator->sort('URL do 404', 'page_from');?>
<?php echo $paginator->sort('número de erros', 'num_errors');?>
<?php echo $paginator->sort('URL a redirecionar', 'page_to');?>
<?php echo $paginator->sort('número de redirecionamentos', 'num_redirects');?>
<?php __('Ações');?>

<?php
foreach ($redirects as $redirect):
?>

<?php echo $redirect['Redirect']['page_from']; ?>

<?php echo $redirect['Redirect']['num_errors']; ?>

<?php echo $redirect['Redirect']['page_to']; ?>

<?php echo $redirect['Redirect']['num_redirects']; ?>

<?php
echo $html->link(
__('Criar Redirecionamento', true),
array(
'action' => 'create_redirect',
$redirect['Redirect']['id']
)
);
?>
<?php
echo $html->link(
__('Excluir', true),
array(
'action' => 'delete',
$redirect['Redirect']['id']
)
);
?>

<?php endforeach; ?>

<?php echo $paginator->prev('<< '.__('anterior', true), array(), null, array('class'=>'disabled'));?>
| <?php echo $paginator->numbers();?>
<?php echo $paginator->next(__('próxima', true).' >>', array(), null, array('class' => 'disabled'));?>

Pronto, agora temos uma ferramenta para gerenciar os erros 404 do site. Tomara que algum dia a frustração de pensar que um link solucionará meus problemas e tomar um 404 na lata diminua
Criei um projeto no github pra facilitar a baixar os arquivos. Está tudo neste link