CakePHP, routes, SEO et plus si affinités

jeudi 17 février 2011, par Minimalteck

Petite note d’initiation aux joies du framework CakePHP au travers d’un exemple simple.

Soit un catalogue produits, chaque produit étant associée à une marque.

Nous allons voir comment présenter la liste des produits d’une marque incluant :
- une pagination
- le tri par "attributs" du modèle
- la mise en place d’URLS conviviales
- un affichage du prix en euros

Rien de plus classique, mais cela permet d’appréhender quelques fonctionnalités très appréciables du framework.

Passons sans plus attendre au code, cela vaut mieux qu’un long discours…:-)

 Définir les relations entre modèles

Il s’agit d’établir ici une relation simple : à une marque sont associés n produits.
Il peut exister d’autres relations avec le modèle "Produit", de même type comme ici le modèle Gamme, ou d’autres types d’association [1]…

- Extrait de "models/produits.php"

class Produit extends AppModel {
       
        public $name = 'Produit';

        [...]
       
        //DÉFINITION DES RÈGLES DE VALIDATION DU MODÈLE
        public $validate = array(
                                'name' => array(
                                        'nom' => array(
                                                        // PETITE REGLE PERSONNALISÉE :
                                                        // APHANUM (ACCENT + APOSTROPHE) 3 CARACTÈRES MINIMUM
                                                        'rule' => "/^[^0-9°_%:().\/\*\^\?#!@$%+=,\"><~\[\]{}]{3,}$/i",
                                                        'message' => 'Vous devez préciser la dénomination du produit.',
                                                        'allowEmpty' => false,
                                                        'required' => false
                                                ),
                                        ),
                                        [...]
                                );
        [...]

        //ASSOCIATION PRODUIT ET MARQUE : belongsTo (plusieurs vers un)        
        public $belongsTo = array(
                        'Marque' => array(
                                'className' => 'Marque',
                                'foreignKey' => 'marque_id',
                                'conditions' => '',
                                'fields' => '',
                                'order' => ''
                        ),
                        'Gamme' => array(
                                'className' => 'Gamme',
                                'foreignKey' => 'gamme_id',
                                'conditions' => '',
                                'fields' => '',
                                'order' => ''
                        ),
                        [...]
                );
        [...]
}

- Extrait de "models/marques.php"

class Marque extends AppModel {
        public $name = 'Marque';
       
        [...]

        //ASSOCIATION MARQUE ET PRODUIT : hasMany (un vers plusieurs)
        public $hasMany = array(
                'Produit' => array(
                        'className' => 'Produit',
                        'foreignKey' => 'marque_id',
                        'dependent' => false,
                        'conditions' => '',
                        'fields' => '',
                        'order' => '',
                        'limit' => '',
                        'offset' => '',
                        'exclusive' => '',
                        'finderQuery' => '',
                        'counterQuery' => ''
                )
        );

}

 Récupérer les données issues de la relation

La consultation d’une marque doit permettre l’affichage d’une liste paginée des produits associés. Il faut récupérer les informations liés :

  • à la marque courante ;
  • aux produits liés ;
  • aux autres modèles liés à ces produits.

- Extrait de "controllers/marques_controller.php" :

        public function view($id = null) {
                if (!$id) {
                        $this->Session->setFlash(__('Marque inexistante', true));
                        $this->redirect(array('action' => '/'));
                }
                $this->set('marque', $this->Marque->read(null, $id));
                $this->helpers[] = 'Number';
                //ON CHARGE LE MODÈLE PRODUIT
                $this->loadModel('Produit');
                //POUR RÉCUPÈRER LES INFOS DES AUTRES MODÈLES LIÉS AU PRODUIT
                $gammes = $this->Produit->Gamme->find('list',array('order' => array('id ASC')));
                [...]
                $this->set(compact('gammes',[...]));
                //ON RÉCUPÉRE LES PRODUITS ASSOCIÉS À LA MARQUE,
                $this->paginate = array('Produit'=> array(
                        'conditions' => array('`Produit`.`marque_id`'=>$id),
                        'limit' => 10,
                        'order' => '`Produit`.`name` ASC'
                    ));
               
                $this->set('produits', $this->paginate('Produit'));
        }

 Mise en oeuvre d’URLs conviviales

Nous souhaitons mettre en place des urls de la forme :
- "/nos-produits", pour afficher le listing de tous les produits ;
- "/nos-produits/1-titre-du-premier-produit" , pour consulter la page d’un produit spécifique ;
- "/nos-produits/marques", pour afficher le listing de toutes les marques ;
- "/nos-produits/marques/1-titre-de-la-premiere-marque" , pour consulter la page d’une marque en particulier et le listing des produits associés. Pour ce dernier cas la pagination des listings sera du type :
- "/nos-produits/marques/1-titre-de-la-premiere-marque/pageX" où "X" représentera le numéro de page ;
- "/nos-produits/marques/1-titre-de-la-premiere-marque/pageX/trier-par-YYYYY" ou "YYYYY" représentera un critère tri dans ordre croissant.
On ajoutera "/inverse" à l’url pour obtenir un ordre décroissant.

Complexe ? Non, c’est juste une demande courante…:-/

Grâce à CakePHP tout ou presque va se réaliser au niveau de la configuration des routes du framework.

- Extrait de "config/routes.php"

//PRODUITS
        Router::connect('/nos-produits/:id-:suffixe',
                                        array('controller' => 'produits', 'action' => 'view'),
                                        array('pass' => array('id', 'suffixe'),'id' => '[0-9]+','suffixe' => '[a-z0-9\-]+'))
                                        );
                                       
        Router::connect('/nos-produits/:id-:suffixe',
                                        array('controller' => 'produits', 'action' => 'view'),
                                        array('pass' => array('id', 'suffixe'),'id' => '[0-9]+','suffixe' => '[a-z0-9\-]+'))
                                        );
        Router::connect('/nos-produits',
                                        array('controller' => 'produits', 'action' => 'index')
                                        );

//MARQUES
        //MARQUES TRI ASC
        Router::connect('/nos-produits/marques/:marque_id-:suffixe/page:page/trier-par-:sort',
                                        array('controller' => 'marques', 'action' => 'view','direction'=>'asc'),
                                        array('pass' => array('marque_id', 'suffixe','page','sort','direction'),'marque_id' => '[0-9]+','suffixe' => '[a-z0-9\-]+')
                                        );
        //MARQUES TRI DESC
        Router::connect('/nos-produits/marques/:marque_id-:suffixe/page:page/trier-par-:sort/inverse',
                                        array('controller' => 'marques', 'action' => 'view', 'direction'=>'desc'),
                                        array('pass' => array('marque_id', 'suffixe','page','sort','direction'),'marque_id' => '[0-9]+','suffixe' => '[a-z0-9\-]+')
                                        );
        //MARQUES PAGINATION
        Router::connect('/nos-produits/marques/:marque_id-:suffixe/page:page',
                                        array('controller' => 'marques', 'action' => 'view'),
                                        array('pass' => array('marque_id', 'suffixe'),'marque_id' => '[0-9]+','suffixe' => '[a-z0-9\-]+')
                                        );
                                       
        //MARQUES ALL
        Router::connect('/nos-produits/marques/:marque_id-:suffixe',
                                        array('controller' => 'marques', 'action' => 'view'),
                                        array('pass' => array('marque_id', 'suffixe'),'marque_id' => '[0-9]+','suffixe' => '[a-z0-9\-]+')
                                        );
        //MARQUES INDEX
        Router::connect('/nos-produits/marques',
                                        array('controller' => 'marques', 'action' => 'index')
                                        );

Hum, et la pagination au sein de la vue ? B-)

Il faut passer la jolie url au ’Helper Paginator’ [2]

- Extraits de "views/marques/view.ctp"

$this->Paginator->options(array('url' => array('marque_id' => $marque['Marque']['id'],'suffixe'=>Inflector::slug($marque['Marque']['name'],'-'))));

Le dernier point concerne le tri.
Dans la vue il suffit d’utliser la méthode "sort" du Paginator :

echo $this->Paginator->sort('Dénomination','name',array('title'=>'Trier par dénomination'));
echo $this->Paginator->sort('Gamme','gamme_id',array('title'=>'Trier par gamme'));
[...]

Ainsi si critère de tri le champ "name" des produits nous allons obtenir en l’état l’url : "/nos-produits/marques/1-titre-de-la-premiere-marque/pageX/trier-par-name" et par gamme on aurait "/nos-produits/marques/1-titre-de-la-premiere-marque/pageX/trier-par-gamme_id"

c’est pas beau… :’-( Comment faire autrement ?

On va définir au sein du modèle produit les "intitulés conviviaux" pour les attributs souhaités, grâce aux "virtualFields [3] de CakePHP.

Ajoutons cela à notre modèle ("models/produits.php")

        public  $virtualFields = array(
           'denomination' => 'Produit.name',
           'marque' => 'Produit.marque_id',
           'gamme' => 'Produit.gamme_id',
           [...]
           );

- Notre vue devient (extrait de "views/marques/view.ctp")

echo $this->Paginator->sort('Dénomination','denomination',array('title'=>'Trier par dénomination'));
echo $this->Paginator->sort('Dénomination','gamme',array('title'=>'Trier par gamme'));

 Piti bonus, la cerise du Cake [4]

Nos résultats sont affichés au sein d’un tableau dont les entêtes contiennent les critères de tri.

<table id="results">
        <tr>
                        <th class="trierpar">Tri :</th>
                        <th><?php echo $this->Paginator->sort('Dénomination','denomination',array('title'=>'Trier par dénomination'));?></th>
                        <th><?php echo $this->Paginator->sort('Gamme','gamme',array('title'=>'Trier par gamme'));?></th>
        [...]

Le système de pagination du framework ajoute au lien de tri une classe css ("asc" ou "desc") pour discriminer l’ordre de tri. Sachant cela et que nous avons définit un attribut "id" à notre tableau de résultats, il devient trivial d’ajuster via javascript (ici jQuery) le titre du lien, rajoutons ce code à notre vue et le tour est joué.

$this->Html->script('jquery/jquery.min.js', array('inline'=>false));
$this->Html->scriptBlock("
        $(document).ready(function() {
                $(\"#results th a\").each(function(){
                        if ($(this).hasClass(\"asc\")){
                                 $(this).attr(\"title\",($(this).attr(\"title\")+', dans l\'ordre décroissant'));
                        } else {
                                 $(this).attr(\"title\",($(this).attr(\"title\")+', dans l\'ordre croissant'));
                        }
                });
         });
",
array('inline'=>false));

 Conclusion

CakePHP c’est à la mode, accessible, plein de bonnes idées, alors pourquoi pas… essayez !!!

Notes

[1] cf. Book CakePHP : "Associations : relier les modèles entre eux"

[2] cf. Book CakePHP :"Paginator"

[3] cf. Book CakePHP : "Virtual fields"

[4] Désolé… c’est pitoyable, mais j’assume :o)

SPIP | squelette | | Plan du site | Suivre la vie du site RSS 2.0