Routez vos visiteurs !

Voici un petit bout de code permettant de résoudre un phénomène dû à l’utilisation des routes avec CakePHP. Lors d’une question récemment posée sur IRC, un membre a soulevé une remarque intéressante vis-à-vis du système de routage de Cake : lorsqu’une route existe pour une page, rien n’empêche l’utilisateur d’accéder à la page en utilisant l’url d’origine « /controller/action/param1/param2« .

Ci-après une illustration du problème avec le contrôleur « Pages » livré avec CakePHP, ainsi qu’une proposition de code pour rediriger correctement les utilisateurs. C’est l’occasion idéale pour découvrir quelques méthodes proposées par le Router de CakePHP.

Le problème

Prenons l’exemple du contrôleur « Pages » qui est livré avec CakePHP. Dans une nouvelle installation, la page d’accueil de l’application est la Page « home » correspondant à l’action « display » du contrôleur Pages. Ainsi, lorsque l’on accède à l’url http://monsite.com/ nous voyons en fait la page http://monsite.com/pages/display/home … ceci est dû à la route suivante, déclarée dans « /config/routes.php » :

Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));

Mais rien n’empêche un utilisateur d’accéder à cette même page directement depuis l’url http://monsite.com/pages/display/home. Dans la plupart des cas, c’est assez peu important mais vous pouriez vouloir éviter ceci pour des raisons de référencement (duplicate content, changements d’url) ou autre.

Une solution

La solution suivante consiste à analyser l’url actuelle pour déterminer quelle action est appelée, puis de vérifier si l’url correspond aux routes déclarées. Si l’url ne correspond pas il suffit simplement de faire une redirection 301 vers l’url correcte.

Dans la méthode beforeFilter de votre AppController, ajoutez l’instruction suivante :

/**
 * Before filter callback
 *
 * @return void
 * @access public
 */
 public function beforeFilter() {
    $this->_checkRoute();
 }

Et créez la méthode « _checkRoute » (et une fonction bonus pour détecter les appels avec requestAction()) dans votre AppController, avec l’implémentation suivante :

EDIT: Voici une version encore plus simple et qui fonctionnera mieux! (voir les commentaires, merci Rob2 pour la remarque)

/**
 * Check that the current page has been accessed through the correct url.
 * When current url does not match the correct route, user is redirected to the correct location
 *
 * @return void
 * @access protected
 */
	protected function _checkRoute() {
		$url = Router::url($this->passedArgs);
		if ($this->here != $url && !$this->isRequestedAction() && !$this->RequestHandler->isAjax()) {
			$this->redirect($url, 301);
		}
	}
 
/**
 * Returns true if the action was called with requestAction()
 *
 * @return boolean
 * @access public
 */
	public function isRequestedAction() {
		return array_key_exists('requested', $this->params);
	}

Voici tout de même la version initiale, pour information

 protected function _checkRoute() {
    $urlParts = Router::parse($this->here);
    $url = array_merge($urlParts, $urlParts['pass'], $urlParts['named']);
    unset($url['pass'], $url['named']);
    if ($this->here != Router::url($url)) {
       $this->redirect($url, 301);
    }
 }

… et le tour est joué !

Petite analyse

La première ligne décode l’url actuelle ($this->here) pour récupérer chaque partie dans un tableau. Ensuite, les paramètres nommés et non-nommés sont ajoutés au tableau de base décrivant l’url afin que celui-ci puisse être utilisé pour générer l’url correcte de la page.

Si l’url générée est différente de l’url actuelle l’utilisateur est redirigé au bon endroit.

Pour information, je n’ai jamais utilisé ce code dans une application mais n’ai fait que quelques tests basiques pour vérifier son bon fonctionnement. Si vous l’utilisez et trouvez des problèmes, n’hésitez pas à me les parvenir dans les commentaires ;)

Partagez avec vos amis !
  • del.icio.us
  • Twitter
  • Tumblr
  • Facebook
  • Digg
  • LinkedIn
  • Google Bookmarks
  • email
  • Technorati
  • Wikio FR
  • Netvibes
  • RSS
If you enjoyed this post, make sure you subscribe to my RSS feed!

Cet article a été publié dans En vrac avec les mots-clefs : , . Bookmarker le permalien. Laisser un commentaire ou faire un trackback : URL de trackback.
  • debug($this->here) donne :
    /cakephp/
    debug($url) donne :
    Array
    (
    [controller] => cakephp
    [plugin] =>
    [action] => index
    )
    J'accède à CakePHP avec l'url : http://127.0.0.1/cakephp/ et il me redirige vers http://127.0.0.1/cakephp/cakephp, avec une erreur car le contrôleur cakephp n'existe pas...
  • Ah, je n'ai pas teste ce code depuis un sous-dossier du domaine principal ... cela vient peut-etre de la.
    Que donne un debug($urlParts); ?

    Peux tu confirmer que tu as installe ton application CakePHP dans le sous-dossier "cakephp" de l'espace web ? (et donc que http://localhost/cakephp est la page d'accueil de l'application)
  • J'ai bien installé CakePHP sans le sous-dossier "cakephp", j'y accède avec l'url http://127.0.0.1/cakephp/
    Le debug donne :
    Array
    (
    [pass] => Array
    (
    )

    [named] => Array
    (
    )

    [controller] => cakephp
    [plugin] =>
    [action] => index
    )
  • Merci pour cette remarque et ces informations ...
    En effet, le code fournit ne marchait pas dans le cas ou Cake etait dans un sous-dossier.

    Au final, je pense que le code suivant (encore plus simple ;) ) devrait faire l'affaire dans tous les cas d'utilisation, j'ai rapidement teste avec CakePHP 1.3RC1.


    protected function _checkRoute() {
    $url = Router::url($this->passedArgs);

    if ($this->here != $url && !$this->isRequestedAction() && !$this->RequestHandler->isAjax()) {
    $this->redirect($url, 301);
    }
    }
  • Merci d'avoir revu le code :)
    Par contre j'utilise CakePHP 1.2.6, donc ça ne marche toujours pas ^^
    Un debug($url); me renvoie /cakephp/inscription et j'obtiens l'erreur suivante : Fatal error: Call to undefined method UsersController::isRequestedAction()
    Je suppose que ça fait pareil pour $this->RequestHandler...
  • Non, cela devrait fonctionner mais j'ai tellement pris l'habitude d'avoir ma propre methode isRequestedAction() que je n'avais pas fait attention a elle !

    Le RequestHandler est un composant a ajouter a vos controleurs si vous faites de l'AJAX ou du REST. Il aide a gerer les differents types d'appels de page.

    La partie importante du "if" est "$this->here != $url" ... le reste ce n'est que pour se parer des problemes pouvant survenir lors de requestAction ou d'appels Ajax, donc cela peut etre supprime si ils ne sont pas utilises.
  • Eh ben en fait toujours pas, je suis redirigé sur http://127.0.0.1/cakephp/cakephp/inscription au lieu de http://127.0.0.1/cakephp/inscription :/
  • Ça ne marche pas pour moi, aucune redirection n'est faite...
  • As-tu essayé de débugguer le code pour voir ce qui bloquait ?
    Que donnent les debug suivants si (en les mettant dans _checkRoute juste avant le if) ?

    debug($this->here);
    debug($url);
blog comments powered by Disqus
  • Speaker at the CakeFest 2010

    Creative Commons License


  • A lire aussi …