Wamania.com

Framy

Conception intégrale d'un framework implémentant le design pattern MVC

Fil des billets - Fil des commentaires

lundi, août 25 2008

Framy - Partie 4 - Url-rewritons !!

Cet article fait suite à la partie 3

Nous voici donc arrivé à cette grosse partie.
Je vais commencer par expliquer ce qu'on cherche à faire.

Une application est constituée de un ou plusieurs contrôleurs, contenant des actions étroitement liées au contrôleur.
Nous allons créer un contrôleur application, contenant 1 action : helloworld qui prendra en paramètre la langue (en ou fr), ainsi que la période de la journée (matin, aprem, soir, nuit) Nous devons maintenant appeler ce couple contrôleur/action.

Pour les pressés, voici le code complet de notre Url Rewriting : Télécharger le code complet

Lire la suite...

lundi, août 18 2008

Framy - Partie 3 - (Episode -51, Là ou tout a vraiment commencé)

Cet article fait suite à la Partie 2

Avant de commencer à compliquer le bouzin, et surtout, avant de commencer l'url-rewriting, je me suis dit que présenter tout de suite l'objectif final permettrait sûrement de gagner beaucoup de temps. Pour cela, je me suis replongé dans l'UML2, mais j'avoue que je galère, c'est pas trop mon truc les blocks (je préférais les patates).

Schema UML de Framy

Alors, étudions un peu ça. Tout d'abord, il ne faut surtout pas avoir peur !

Le commencement, c'est la méthode yBoot::launch() qui sera appelée par exemple dans l'index.php Cette méthode static crée 2 objets :

  • $resquest (yRequest)
  • $response (yResponse)

La classe yRequest contient les attributs et méthodes nécessaires à l'analyse de la requête de l'utilisateur. C'est elle qui va déterminer le couple contrôleur/action à utiliser à travers les différents classes de traitement de l'URL disponibles.
La classe yResponse contient simplement la réponse que Framy enverra au navigateur, ce qui comprend entête HTTP et contenu. On crée ces classes dans le boot pour

  • Déterminer le couple contrôleur/action ainsi que les paramètres.
  • Valider la requête et si besoin, envoyer une erreur 404
  • Charger la configuration globale de Framy (que nous ne verrons pas du tout ici)
  • Lancer l'application (ie : Instancier le contrôleur et lancer l'action)

Ensuite, l'ensemble des pouvoirs est transmis au contrôleur (applicationController est un contrôleur arbitraire pour servir d'exemple). N'oublions pas que le contrôleur hérite de la classe abstraite yController, qui possède tout ce dont on a besoin, et qui va récupérer au passage l'objet $request et l'objet $response (regardez donc le constructeur de yController).

Voila, maintenant que les choses sont claires, nous pouvons commencer à travailler sur chacune des parties. A commencer bientôt par l'url-rewriting.

Allez à la 4ième partie

jeudi, août 14 2008

Framy - Partie 2 - Le contrôleur

Cet article fait suite à celui-ci : Partie 1 - Pourquoi utiliser un framework MVC

Je vais vous présenter ici la base du début du commencement d'un bon contrôleur dans une optique MVC. Tout d'abord, un peu de théorie. Selon wikipedia "Le contrôleur prend en charge la gestion des évènements de synchronisation pour mettre à jour la vue ou le modèle et les synchroniser.". Ce qui signifie que le contrôleur est appelé à chaque évènement lié à l'utilisateur (en tout cas dans le web). Il va ensuite synchroniser la vue et le modèle en fonction de la requète. Attention cependant, si le contrôleur est le "système nerveux" du programme, il n'est pas le cerveau. En effet, l'ensemble des classes "métiers" nécessaires au traitement des informations seront à l'extérieur du MVC, et seront appelées si besoin par le contrôleur. Au final, le contrôleur est le coordinateur de notre programme. Ce qui signifie que tout commence par lui et tout fini par lui (en tout cas, d'un point de vue extérieur au framework). Chronologiquement, le framework va analyser la requète, puis va déterminer :

  • Le contrôleur à utiliser
  • L'action à appelée
  • Les paramètres supplémentaires.

Le couple contrôleur/action se cherchera de coordonner les différents acteurs pour finir (en général) par l'envoi de la vue.

Par exemple, voici un contrôleur et quelques actions typiques pour gérer les utilisateurs d'un espace membre.

class userController {
   
    // On va détailler un peu cette action
    function register() {
        // On attend ici dans les paramètres le pseudo et le pass du user
        if (empty($this->params['pseudo'] || empty($this->params['pass'])) {
            $this->render_view('formulaire_incomplet.tpl');
        } else {
            // on insere
            $user = new User($this->params['pseudo'], $this->params['pass'] );
            if ($user) {
                $this->render_view('inser_user_success.tpl');
            } else {
                $this->render_view('inser_user_failed.tpl');
            }
        }
    }

    function update() {
        // On attend ici en paramètre l'id du user à modifier et le pseudo et/ou pass à modifier
    }

    function delete() {
        // On attend ici l'id du user à supprimer
    }

    function login() {
        // On attend ici l'id du user à connecter
    }

    function logout() {
        // On attend ici l'id du user à déconnecter
    }
}

La jonction formulaire <-> base de donnée est la partie la plus redondante, la plus pénible et la plus grande source d'erreur de tout programme web. Le but du contrôleur est de simplifier et centraliser tout ça.

Parlons justement de centralisation. Jusqu'ici, hormis ressembler les fonctions, on a pas encore grand chose. On a déjà vaguement aperçu le modèle User, ainsi que la méthode $this->render_view encore inconnue. Concentrons nous sur ce qu'il y a à rassembler pour que notre classe contienne TOUT ce dont elle a besoin:

  • Le modèle
  • La vue
  • La session
  • Les paramètres d'entrées
class userController {

    private $db;
 
    private $view;

    private $session;

    private $params;

    // Lié au fonctionnement
    private $data;

    public __construct() {}

    public register() {}

    public update() {}

    public delete() {}

    public login() {}

    public logout() {}

    // Nouvelle méthode, vu au paragraphe avant, qui serivra à afficher une vue
    private render_view() {}
}

Dans ces quelques lignes, il y a toute l'architecture de notre espace membre. Toutes les variables extérieures sont accessibles à chaque action. Reste maintenant à alimenter ces variables, et à rendre nos actions appelables par un visiteur.

Mais avant tout, regardons de plus prêt notre classe. Nous l'avons créée pour gérer un espace membre, or un site n'est jamais constitué de si peu. Il va nous falloir utiliser d'autres contrôleurs pour les autres contextes, par exemple sur une galerie d'images, il nous faudra gérer les images.

Pour cela nous allons créer un contrôleur imageController qui devra lui aussi utiliser les sessions, les paramètres, les vues, etc. Grâce à l'héritage, nous allons pouvoir créer une classe Controller mère dont chaque controller héritera. Cette classe mère se chargera des variables et méthodes communes. Ainsi, nous aurons :

abstract class Controller {
    /**
     * Notre modèle
     */

    protected $db;
    /**
     * Notre vue
     */

    protected $view;
    /**
     * La session
     */

    protected $session;
    /**
     * Les paramètres POST et GET
     */

    protected $params;
    /**
     * Un tableau qui contiendra se qu'on enverra à la vue.
     * La vue ne disposera que de ces données et aucune autre
     */

    protected $data;
    /**
     * Constructeur
     */

    public function __construct() {}
    /**
     * Méthode privée appelée par chaque action et qui se chargera de la vue.
     */

    protected function render_view() {}
}

Ce qui permet de simplifier notre contrôleur :

class userController extends Controller{

    public register() {}

    public update() {}

    public delete() {}

    public login() {
        // Notre vue n'étant pas encore écrite, nous utiliserons un simple echo
        echo 'Hello World';
    }

    public logout() {}
}

Testons maintenant notre contrôleur. Il faut pour cela avoir un moyen pour se diriger vers le bon couple contrôleur/action en fonction de la requète. Nous ferons très simple pour commencer :

Placer ceci dans un fichier index.php

// La classe abstraite
require_once 'controller.php';
// Notre controleur user
require_once 'userController.php';

if (isset($_GET['controller'])) {
    $controller = $_GET['controller'];
} else {
    // On peut mettre un contrôleur par défaut
}
if (isset($_GET['action'])) {
    $action = $_GET['action'];
} else {
    // On peut mettre une action par défaut
}
$oController = new $controller;
$oController->$action;

Il vous reste à appeler l'adresse correspondante, par exemple en local, si les fichiers sont dans le répertoire framework, nous appelerons http://localhost/framework/index.php?controller=user&action=login

Cette partie est maintenant finie. Nous verrons dans la prochaine partie la réécriture des urls car il est clair qu'une url comme celle du dessus n'est pas très sexy sur un site sérieux. Nous verrons aussi, dans les différents articles qui suivront, comment améliorer nettement la classe abstraite par l'ajout de nombreuses fonctionnalités, notamment la pagination, les filtres, le cache, etc...

Aller à la partie 3

Framy - Partie 1 - Pourquoi utiliser un framework MVC

Un framework se défini par "cadre d'application". Le MVC signie Modèle Vue Contrôleur. Il s'agit d'un design pattern consistant à séparer la vue (ce qu'on voit dans le navigateur), le modèle de données (le plus souvent l'accès à la base) et le contrôleur qui fait la jonction. Cette définition est en fait un peu simpliste, mais permet de bien voir le rôle de chacuns. D'une manière générale, l'utilisation de design pattern et notamment celui du MVC demande du temps pour étre assimilé. D'autant plus qu'on le retrouve souvent dans des frameworks beaucoup plus riches en général. Je ne m'intéresse ici à aucun framework en particulier, mais juste au pattern MVC. Le but final de tous ces articles sera d'avoir une vue d'ensemble bien construite de ce qu'un framework typique peut proposer.

PHP est longtemps resté un langage réputé "pour débutant" et donc peu prisé par les entreprises, notamment à cause de son coté "bordel". En effet, avec php, il n'y a pas séparation entre le coté serveur et le coté client, ce qui veut dire que le même fichier peu contenir

  • du php
  • du SQL
  • du javascript
  • du CSS
  • du (X)HTML

Non seulement ces langages peuvent se retrouver tous ensembles dans la même page, mais en plus, depuis l'avènement d'AJAX, on peut réaliser des appels de la page sur elle-même en ajax ou en http traditionnel. On perd vite les pédales entre tous les appels, tous les langages, ajax pas ajax, et pour peu qu'on ajoute un formulaire, des redirections conditionnelles (qu'on peut réaliser en php avec header(), en html ou en javascript avec window.location et c'est fini la page seule est devenue un godzilla incontrôlable qu'il va falloir abattre.

D'un point de vue plus théorique, l'utilisation du MVC permet de revoir toute la structure d'un programme (j'utilise le terme "programme" et non "site"). En effet, la méthode courante stipule qu'une bonne séparation des différents acteurs implique leur écriture dans des fichiers séparés. Ce qui se résume en gros par : la structure du programme est la structure des fichiers ET la structure des fichiers est la structure du programme. C'est en fait une horreur de conception. Penser comme ça signifie qu'on ne fait pas ce qu'on doit faire, mais ce qu'on peut faire, il signifie que le fonctionnel n'est réfléchis qu'après l'opérationnel. En gros, ça signifie que le programme est mal ou pas pensé (Attention, je ne dis pas le prog est mauvais, je dis juste qu'il est pensé à l'envers).

Pour illustrer un peu, je vais prendre un espace membre tel qu'il est fait dans 90% des cas. On retrouve donc un fichier connection.php, un fichier inscription.php, un fichier passperdu.php et un fichier "protégé" index.php. Chacun d'eux utilise les fichiers fonctions.php, classes.php, db.php Lorsqu'on arrive sur index.php, la page est protégée, et redirige vers connection.php pour que le gars se loggue, elle inclue donc fonctions.php qui va probablement définir des fonctions bourrées de variable globales ou de avoir des paramètres excessivement élevés, classes.php qui définira sûrement les méthodes pour gérer l'espace membre et enfin db.php

Que peut-on faire pour simplifier ? Simplement rassembler les acteurs par intérêt et non par chronologie.

class user {
   
    private isConnected = false;
    private user_id = null;

    function connection() {
    }

    function inscription() {
    }

    function passperdu() {
    }

    function isConnected() {
    }

    function isLoginAlreadyExists() {
    }

    function changePass() {
    }

    // ..........
}

Il est possible que l'interêt majeur du MVC vous échappe encore, et c'est normal sans exemple concret. Sachez cependant qu'avec ceci, il est déjà possible de voir les futures possibilités. Par exemple, en conservant le format class/méthode (qu'on nommera contrôleur/action), on pourra définir une norme pour la réécriture d'URL. Par exemple http://toto.com/user/connection appelera automatiquement le méthode connection de la classe user.

N'hésitez pas à fouillez dans la réécriture d'URL avec ou sans apache.

Aller à la 2ième partie