IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

[MVC] Ajouter des fonctionnalités au modèle


Sujet :

Langage PHP

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé

    Profil pro
    Inscrit en
    Mai 2002
    Messages
    641
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2002
    Messages : 641
    Par défaut [MVC] Ajouter des fonctionnalités au modèle
    Bonjour,
    Dans mon framework PHP, je dispose d'une classe Model simple, qui sert de base à tous mes modèles :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    abstract class Model {
     
    	public function save() {
    		/* Sauvegarde les données en base */
    	}
     
    	public function delete() {
    		/* Supprime les données en base */
    	}
     
    }
    Je voudrais ajouter des fonctionnalités à mes modèles, de manière optionnelle et en réutilisant mon code.
    Mon idée est d'utiliser le pattern Decorator.

    Je définis une classe Decorator abstraite :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    abstract class Decorator {
    	$protected $model;
     
    	public function __construct( $model ) {
    		$this->model = $model;
    	}
     
    	public function __call( $method, $args ) {
    		return call_user_func_array( array( get_class( $this->model ), $method), $args );
    	}
    }
    J'aurais par exemple un Decorator permettant de gérer un modèle multilingue, en ajoutant simplement un suffixe (_en, _fr) aux noms des champs.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class TranslateDecorator extends Decorator {
     
    	public $language;
    	public $translated;
     
    	public function __construct() {
    		parent::__construct();
    		if ( !empty( $_SESSION['language'] ) ) {
    			$this->language = $_SESSION['language'];
    		}
    	}
     
    	public function __set( $name, $value ) {
    		if ( in_array( $name, $this->translated ) ) {
    			$name .= '_' . $this->language;
    		}
    		$this->model->name = $value;
    	}
     
    }
    Ou Decorator permettant de gérer une corbeille :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class TrashDecorator extends Decorator {
     
    	/* Surcharge de delete() */
    	public function delete() {
    		$this->model->is_deleted = 1;
    		$this->model->save();
    	}
     
    	public function unDelete() {
    		$this->model->is_deleted = 0;
    		$this->model->save();
    	}
     
    	public function emptyTrash() {
    		foreach ( $this->model->findAll() as $model ) {
    			$model->delete();
    		}
    	}
    }
    Je définis mes modèles :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class ArticleModel extends Model {
    	// Définition de la classe
    }
    Ensuite je peux instancier mon modèle et ajouter mes decorators :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $article = new TrashDecorator( new TranslateDecorator( new ArticleModel ) );
    $article->translated = array( 'title', 'text' );
    $article->language = 'fr';
     
    $article = $article->findOne( 4 );
    $article->title = 'Mon titre';
    $article->save();
     
    $article->delete();
    $article->emptyTrash();
    Je trouve ce système efficace, simple et élégant.

    Que pensez-vous de cette approche ?

  2. #2
    Membre chevronné
    Avatar de bricecol
    Homme Profil pro
    Consultant informatique
    Inscrit en
    Avril 2007
    Messages
    364
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : Avril 2007
    Messages : 364
    Par défaut
    et bien personnellement, je trouve çà très bien.
    tu utilises bien les notions autour des objets.

  3. #3
    Membre très actif Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Par défaut
    Je me pose la question:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $article = $article->findOne( 4 );
    Juste avant ce code, $article était un TrashDecorator.
    Que retourne findOne() ? Une nouvelle instance ? Dans ce cas quel est son type ?
    Si findOne() se contente juste de modifier $this (donc ne pas créer de nouvelle instance), il va falloir je pense mettre en place un mécanisme assez complexe pour répercuter dans la chaîne de décorators le fait qu'il y ait un changement d'état parce que findOne() est probablement définie dans la classe Model.

    Il faudrait que Model et Decorator implémentent une interface commune (genre IModel) pour que tu ne sois pas embêter par des questions de type hinting.

    N'hésite pas à clarifier les choses si je me suis trompé. C'est un post intéressant

  4. #4
    Membre éclairé

    Profil pro
    Inscrit en
    Mai 2002
    Messages
    641
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2002
    Messages : 641
    Par défaut
    Citation Envoyé par metagoto Voir le message
    Juste avant ce code, $article était un TrashDecorator.
    Que retourne findOne() ? Une nouvelle instance ? Dans ce cas quel est son type ?
    Si findOne() se contente juste de modifier $this (donc ne pas créer de nouvelle instance), il va falloir je pense mettre en place un mécanisme assez complexe pour répercuter dans la chaîne de décorators le fait qu'il y ait un changement d'état parce que findOne() est probablement définie dans la classe Model.
    Merci de ta réponse.

    En effet cela pose un problème.
    Pour l’instant findOne() crée une nouvelle instance. Je pourrais le modifier pour retourner $this. Cela devrait fonctionner car les decorators ne font que surcharger ou ajouter des méthodes. A première vue le changement d’état ne pose donc pas trop de problème. Mais je me trompe peut-être…

    Par contre ma classe Model dispose d’une méthode findAll() quil retourne instancie une liste d’objets et retourne un tableau.

    J’ai étudié différentes possibilités mais je n’ai rien trouvé de très satisfaisant.
    J’ai pensé à utiliser une Factory pour instancier mes modèles. La liste des decorators à appliquer serait stockée quelque part. De cette manière on instancierait toujours un objet convenablement décoré. Mais cela ne permet de pas répercuter un changement d’état d’un décorateur effectué avant la création des nouveaux objets.

    Autre possibilité : La classe Model pourrait implémenter l’interface Iterator, de manière à ce qu’on puisse le parcourir comme un tableau. Dans ce cas ma méthode findAll() retournerait simplement $this et non plus un tableau après avoir instancié de nouveaux objets.

    Mais il y a certainement d’autres approches auxquelles je n’ai pas pensé.

  5. #5
    Membre très actif Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Par défaut
    Notre échange me conforte dans mon idée qu'une approche Decorator n'est pas judicieuse. En revanche, un truc à base de subjects/observers me paraît plus intéressante.

    Voici un code à l'arrache (pour php 5.3 uniquement) qui esquisse une solution:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    // pour le type hinting (passage de Models à des fonctions)
    interface IModel
    {
      public function save(); 
      public function delete();
      //...
    }
     
     
    // subjects/observers mechanics
    abstract class ModelBase implements SplSubject
    {
      private $observers;
     
      public function __construct()
      {
        $this->observers = new SplObjectStorage;
      }
     
      public function attach(SplObserver $o)
      {
        $this->observers->attach($o);
      }
     
      public function detach(SplObserver $o)
      {
        $this->observers->detach($o);
      }
     
      public function notify($key = null)
      {
        foreach ($this->observers as $o) {
          /*$res = */$o->update($this, $key); // si besoins d'un retour
        }
      } 
    }
     
     
    abstract class Model extends ModelBase implements IModel
    {
      public function save()
      {
        $this->notify("save");   
        // reste du save() si ok 
      }
     
      public function delete()
      {
        $this->notify("delete");    
        // ...
      }
     
     
      // static members
      public static function findOne($id)
      {
        $class = get_called_class(); // "Late Static Binding" class name
        // utiliser un query builder pour construire $class
        return new $class;
      }
     
      public static function findAll($specialClause = null)
      {
        // même chose que findOne()
        // retourner un array de $class 
      }
     
    }
     
     
    // quelques observers
    class TrashObserver implements SplObserver
    {
      public function update(SplSubject $subject, $key = null)
      {
        echo $key;
        var_dump($subject);  
        //return true; // si besoins d'un retour
      }
    }
     
    class TranslateObserver implements SplObserver
    {
      public function update(SplSubject $subject, $key = null)
      {
        echo $key;
        var_dump($subject);  
      }
    }
     
     
    // on définit ici le comportement des Articles
    abstract class ArticleBase extends Model
    {
      public function __construct()
      {
        parent::__construct();
        $this->attach(new TrashObserver);
        $this->attach(new TranslateObserver);
      }
     
    }
     
    class Article extends ArticleBase
    {
      // high level custom stuff (au cas où) 
    }
     
    $article = new Article;
    $article->save();
     
    $article2 = Article::findOne(3);
    $article2->delete();

  6. #6
    Membre éclairé

    Profil pro
    Inscrit en
    Mai 2002
    Messages
    641
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2002
    Messages : 641
    Par défaut
    En fait j'avais envisagé d'utiliser le motif Observateur mais pour une autre problématique : avoir un couplage faible entre modules.
    En effet ça semble une solution très intéressante. Je vais explorer cette idée.

    Cependant je n'abandonne pas complètement l'idée du Decorator.

    Que penses-tu du système suivant :
    La méthode findAll() retourne un itérateur (Je n'utilise pas le late static binding car je suis encore en PHP 5.2.). La méthode findOne retourne $this.
    Un Decorateur peut être appliqué soit directement à un modèle, soit à un intérateur (implémentant l'interface itérateur).
    Dans ce cas le code suivant fonctionne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    $article = new TrashDecorator( new TranslateDecorator( new ArticleModel() ) );
    $article = $article->findOne( 4 );
    /* findOne( 4 ) retourne $this */
    $article->translated = array( 'title', 'text' );
    $article->language = 'fr';
    $article->title = 'Mon titre';
    $article->save();
     
    $article->delete();
    $article->emptyTrash();
     
    $articles = new ArticleModel();
    $articles = $articles->findAll();
    $articles = new TrashDecorator( new TranslateDecorator( $articles ) );
    $articles->translated = array( 'title', 'text' );
    $articles->language = 'fr';
     
    foreach ( $articles as $article ) {
    	echo $articles->title;
    }
    Ou mieux : La classe Model peut elle-même se comporter comme un itérateur.
    Dans ce cas findAll() retourne $this et le code suivant fonctionne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $articles = new TrashDecorator( new TranslateDecorator( new ArticleModel() ) );
    $articles = $articles->findAll();
    $articles->translated = array( 'title', 'text' );
    $articles->language = 'fr';
     
    foreach ( $articles as $article ) {
    	echo $articles->title;
    }

Discussions similaires

  1. Réponses: 7
    Dernier message: 13/07/2010, 13h33
  2. [C# 2.0] Ajouter des fonctionnalités au MonthCalandar
    Par margagn dans le forum Windows Forms
    Réponses: 2
    Dernier message: 01/08/2006, 16h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo