Dans cet article, nous allons voir comment exploiter à fond les trois composantes du design pattern MVC, ainsi que l’utilisation de quelques unes des nombreuses nouveautés de Prestashop 1.5 pour :

  • Créer un modèle objet, qui sera stocké dans la base de données
  • Gérer les vues entièrement en Smarty. Bye bye le code HTML dans le code PHP.
  • Utiliser les Controllers aussi bien pour gérer les page de back office et de front office.

Spécifications

Le module Prestashop que nous allons créer permet aux utilisateurs de donner leur avis sur le site, au travers d’un formulaire sur une page dédiée du front office. Les utilisateurs peuvent choisir dans une liste de valeurs, qui sera stockée en base de données. Dans le back office, un onglet permet de valider les avis des clients, afin de les laisser apparaître sur la page du front office.

Structure du module

La structure des modules sous PrestaShop 1.5 impose une structure plus stricte.

Module MVC Prestashop 1.5

  • /controllers accueille les Controllers de back office et de front office
  • /views accueille les vues Smarty pour les Controllers ainsi que les hooks
  • /models n’est pas obligatoire, mais nous allons y placer les modèles objet

La classe du module

Pour commencer, nous allons créer la classe du module de manière classique.

class PrCustomerOpinion extends Module {

    public function __construct() {

        $this->name      = 'prcustomeropinion';
        $this->tab       = 'front_office_features';
        $this->version       = 1.0;
        $this->author        = '...';
        $this->displayName   = $this->l('Customer Opinion');
        $this->description   = $this->l('Example module for PrestaShop 1.5');

        parent :: __construct();

    }

    public function install() {
        return parent :: install()
        && $this->registerHook('leftColumn');
    }

    public function hookDisplayLeftColumn($params) {
        return $this->display(__FILE__, 'left-column.tpl');
    }

}

On greffe le module sur le hook leftColumn lors de l’installation, afin de créer un lien vers la page front office du module.

On note au passage le changement de convention de nommage des hooks : les hooks d’affichage (ceux qui sont visibles), sont désormais préfixés par hookDisplay, tandis que les hooks d’action sont préfixés par hookAction.

Création de la vue pour le hook

Les vues pour les hooks sont désormais à placer dans le dossier /views/templates/hook.

On créé donc le fichier left-column.tpl

<a href="{$link->getModuleLink('prcustomeropinion')}">
{l s='Give my opinion' mod='prcustomeropinion'}
</a>

On appelle la méthode getModuleLink de la classe Link afin de générer un lien vers la page du module.

Les liens seront de la forme :

URLs classique

//www.monsite.com/index.php?fc=module&module=prcustomeropinion&controller=default

URL SEO

//www.monsite.com/module/prcustomeropinion/default

Module MVC Prestashop 1.5


Le Modèle

Nous allons gérer le stockage des avis via la table ci-dessous. On stocke une référénce vers le client qui a donné son avis via la colonne id_customer.

mysql> desc ps_opinion;
+-------------+------------------------------------+
| Field       | Type                               |
+-------------+------------------------------------+
| id_opinion  | int(10) unsigned                   |
| id_customer | int(10) unsigned                   |
| opinion     | enum('AVERAGE','GOOD','VERY_GOOD') |
| active      | tinyint(1) unsigned                |
+-------------+------------------------------------+

Dans le dossiers /models, on crée un fichier Opinion.php, pour définir le modèle objet :

class Opinion extends ObjectModel {

    public $id_opinion;
    public $id_customer;
    public $opinion;
    public $active;

    public static $definition = array(
        'table' => 'opinion',
        'primary' => 'id_opinion',
        'multilang' => false,
        'fields' => array(
            'id_opinion' => array(
                'type' => ObjectModel :: TYPE_INT
            ),
            'id_customer' => array(
                'type' => ObjectModel :: TYPE_INT,
                'required' => true
            ),
            'opinion' => array(
                'type' => ObjectModel :: TYPE_STRING,
                'required' => true
            ),
            'active' => array(
                'type' => ObjectModel :: TYPE_BOOL,
                'required' => true
            )
        )
    );
}

Comme précédemment, la classe doit étendre ObjectModel afin de pouvoir disposer des fonctionnalités ORM et CRUD, et exposer des propriétés publiques pour les champs de la table.

La nouveauté est l’utilisation du tableau statique $definition pour spécifier les caractéristiques de la table. Ici, on spécifie simplement les champs obligatoires de l’objet.

On ajoute également une méthode permettant de charger tous les avis utilisateurs validés pour l’affichage :

public static function findAll() {
    $sql = 'select * from ' . _DB_PREFIX_ . 'opinion where active = 1';
    if ($rows = Db :: getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS($sql)) {
        return ObjectModel :: hydrateCollection(__CLASS__, $rows);
    }
    return array();
}

On note l’utilisation de la méthode ObjectModel :: hydrateCollection, qui permet de transformer un tableau de résultats MySQL en instances de classe.


Création du Controller de front office

Affichage de la liste des avis

Dans le dossier /controllers/front, nous ajoutons un fichier default.php, qui va contenir la classe du Controller.

class PrCustomerOpinionDefaultModuleFrontController extends ModuleFrontController {

    public function initContent() {

        parent :: initContent();

        $opinions = Opinion :: findAll();
        $this->context->smarty->assign('opinions', $opinions);

        $this->setTemplate('form.tpl');

    }

}

On récupère le tableau d’objets de type Opinion, et on l’assigne à la vue form.tpl, à placer dans le dossier /views/templates/front :

<h3>{l s='What people think' mod='prcustomeropinion'}</h3>

<table style="width: 100%;">
{foreach from=$opinions item=opinion}
    <tr>
        <td>{$opinion->getCustomerName()}</td>
        <td>{$opinion->opinion}</td>
    </tr>

{/foreach}
</table>

Formulaire client

On va maintenant créer un formulaire qui permet aux client de donner leur avis, autrement dit d’ajouter un enregistrement en base de données.

<h2>{l s='Give your opinion' mod='prcustomeropinion'}</h2>

<form method="post" class="std">
    <fieldset>
    <label for="opinion" id="opinion">{l s='Our website is...' mod='prcustomeropinion'}</label>
    <select name="opinion">
        <option>--</option>
        <option value="VERY_GOOD">{l s='Very Good' mod='prcustomeropinion'}</option>
        <option value="GOOD">{l s='Good' mod='prcustomeropinion'}</option>
        <option value="AVERAGE">{l s='Average' mod='prcustomeropinion'}</option>
    </select>
    <input type="submit" class="button" value="{l s='Give my opinion' mod='prcustomeropinion'}" />
    </fieldset>
</form>

Dans le Controller, on ajoute le traitement du formulaire (la vérification de l’existence du client a été omise pour l’exemple) :

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if ($opinion = Tools :: getValue('opinion', false)) {
        $opinionObj = new Opinion();
        $opinionObj->id_customer = $this->context->customer->id;
        $opinionObj->active = false;
        $opinionObj->opinion = $opinion;
        $opinionObj->add();

        $link = new Link();
        Tools :: redirect($link->getModuleLink('prcustomeropinion', 'default'));
    }
}

On se sert simplement des méthodes CRUD de la classe ObjectModel pour stocker l’objet en base.

Lorsque le formulaire est posté, un enregistrement est ajouté dans la table.

La page du module s’affiche bien dans la colonne centrale du thème.

Module MVC Prestashop 1.5


Création du Controller de back office

Nous allons désormais ajouter un onglet dans le back office permettant de valider les avis client, afin que ceux-ci apparaissent sur le front office.

Nous souhaitons donc afficher une liste d’avis, avec des boutons permettant de valider un avis ou de le supprimer.

La classe du Controller

Dans /controllers/admin, on créé un fichier adminopinion.php, qui contrôle le modèle Opinion. Les Controllers de back office sont en effet prévus par défaut pour gérer un modèle objet. Il est cependant possible de faire ce que l’on veut.

class AdminOpinionController extends ModuleAdminController {

    public function __construct() {

        $this->table     = 'opinion';
        $this->className     = 'Opinion';

        parent :: __construct();

    }

}

Attention, les conventions de nommage sont importantes pour garantir le fonctionnement du Controller.

On ajoute ensuite l’onglet :

Module MVC Prestashop 1.5

Affichage de la liste d’avis à valider

Afin de simplifier et homogéniser l’affichage du back office, PrestaShop 1.5 fournit des View Helpers (Aides de vue) : ce sont en quelques mots des classes qui génèrent du code HTML.

Pour afficher de manière simple la liste des avis, il suffit de specifier des propriétés dans le constructeur du module, par exemple :

$this->fields_list = array(
    'id_opinion' => array(
        'title'     => '#'
    ),
    'id_customer' => array(
        'title'     => $this->module->l('Customer'),
        'callback'  => 'getCustomerName'
    ),
    'opinion' => array(
        'title'     => $this->module->l('Opinion')
    ),
    'active' => array(
        'title'     => $this->module->l('Valider'),
        'active'    => 'status'
    )
);

$this->actions = array('delete');

La propriété $fields_list permet de personnaliser l’affichage des colonnes de la liste.

Dans l’exemple ci-dessus, la colonne active affichera le bouton standard rouge/vert, et mettra à jour le statut de l’objet en base de données.

Afin de personnaliser la colonne id_customer et afficher le nom complet du client (au lieu de son identifiant), on utilise une fonction de callback. C’est à dire que pour chaque élément de la liste, PrestaShop va appeler une méthode, qui doit retourner la valeur à afficher. Par défaut, la méthode est appelée sur le Controller

public function getCustomerName($echo, $row) {
    $id_customer = $row['id_customer'];
    $customer = new Customer($id_customer);
    return $customer->firstname . ' ' . $customer->lastname;
}

Nous ajoutons également le bouton permettant de supprimer un élément de la liste via la variable $actions

Et… C’est tout !

En effet, dans ce cas il est inutile de créer une quelconque vue, car l’affichage est intégralement pris en charger par les View Helpers.

Module MVC Prestashop 1.5

Pour aller plus loin

Les possibilités offertes par PrestaShop 1.5 en matière de MVC permettent donc de mieux structurer les différents composants back et front office du module, en répartissant les responsabilités au sein de différentes classes.

Le support des Controllers de front office est en très gros plus, et va permettre aux développeurs de personnaliser l’apparence, voire le fonctionnement intégral du front office.

Désormais, les modules sont à concevoir comme des APIs : en effet, chaque Controller de module dispose d’une référence au module, et peut en appeler les méthodes.

Ressources

Si vous souhaitez les étudier, les sources du module sont à disposition sur le GitHub de Prestarocket.

Co-écrit avec @alexmex_, auteur du framework Pan pour Prestashop