Un MVC en PHP5 : Data Access Layer ou couche d’accès aux données

Ce qui suit est déprécié depuis janvier 2010.
Merci de votre compréhension.

Dans l’optique d’écrire quelques brèves sur ce site, j’ai décidé de présenter la réalisation d’un Modèle Vue Contrôleur en PHP5. J’expliquerai fonctionnalité par fonctionnalité et pas à pas comment réaliser un MVC type en PHP5.

Cet article présente la couche d’accès aux données (Data Access Layer). Elle utilise PDO, ce qui permet notamment d’utiliser plusieurs Systèmes de Gestion de Bases de Données (SGBD).

Je présenterai ici la classe wPdo qui permet d’intéragir avec la base de données. Suivrons les présentations respectives des classes wStatement, permettant la récupération de véritables objets depuis la base et wResultObjects, représentation abstraite d’un objet en base.

Une couche supplémentaire est en place via la classe wOrm qui implémente les méthodes Create Read Update Delete (CRUD) en se servant d’une instance de la classe wPdo. Cette classe sera détaillée dans un autre article.

A noter que le code original vient de Julien Pauly pour le site www.developpez.com. Je n’ai qu’intégré et adapté cette implémentation dans wMVC.

 

wPdo : connexion

<?php

/**
* Class wPdo. Comes with other classes
* mainly to show how to tune PDO.
*
* @author Julien PAULI
* @author William DURAND <william.durand1@gmail.com>
*
* Code original de Julien PAULI, modifi&eacute; pour wMVC
*/

class wPdo extends PDO
{
  /**
  * Constructor.
  * Enables PDOExceptions
  * Initiates wStatement
  *
  * @param string $dsn
  * @param string $username
  * @param string $password
  * @param array $driver_options
  */

  final public function __construct($dsn, $username = '', $password = '', $driver_options = array())
  {
    parent::__construct($dsn, $username, $password, $driver_options);
    $this->setAttribute(self::ATTR_ERRMODE, self::ERRMODE_EXCEPTION);
    $this->setAttribute(self::ATTR_STATEMENT_CLASS, array('wStatement'));
    wStatement::setPDOInstance($this);
  }
}

?>

Cette classe permet de créer un connexion avec la base de données. Ici, on modifie quelques peu les attributs par défaut pour pouvoir utiliser notre propre classe wStatement.

 

wStatement : traitement

<?php

/**
* Replacement of PDOStatement
* Allow fetchObjectOfClass() and fetchAllObjectOfClass()
*
* @author Julien PAULI
* @author William DURAND <william.durand1@gmail.com>
*
* Code original de Julien PAULI, modifi&eacute; pour wMVC
*/

final class wStatement extends PDOStatement
{
  /**
  * wPdo instance, passed to classes allowed by
  * fetchObjectOfClass() and fetchAllObjectOfClass()
  *
  * @var wPdo
  */

  private static $_pdo;

  /**
  * PDOStatement doesn't allow a public constructor
  * probably due to internal job. However, we need a way
  * to pass the wPdo instance to us.
  *
  * @param wPdo $pdo
  */

  public static function setPDOInstance(wPdo $pdo)
  {
    self::$_pdo = $pdo;
  }

  /**
  * Internal stuff to check for class validity and
  * discovering of table name
  *
  * @param string $className
  * @throws PDOException
  * @return array
  */

  private function _prepareFetchObject($className)
  {
    if (!preg_match("/.*FROM\s(.*?)[\s|;]/i", $this->queryString, $table)) {
      throw new PDOException('Could not find table name in query');
    }

    if (!class_exists($className, true)) {
      throw new PDOException('Class '.$className.' does not exist');
    }

    $reflection = new ReflectionClass($className);

    if (!$reflection->isSubclassOf('JPDO_ResultObjects')) {
      throw new PDOException('Class '.$className.' should extend wResultObjects');
    }

    return $table;
  }

  /**
  * Fetch a result as an object of a class extending
  * wResults. Those class should allow their objects
  * to be saved back to the DB.
  *
  * @param string $className
  * @return wResultObjects
  */

  public function fetchObjectOfClass($className)
  {
    $table = $this->_prepareFetchObject($className);
    $instance = new $className(self::$_pdo, $table[1]);
    $this->setFetchMode(PDO::FETCH_INTO, $instance);
   
    return parent::fetch(PDO::FETCH_INTO);
  }

  /**
  * Fetch a result as an object of a class extending
  * wResults. Those class should allow their objects
  * to be saved back to the DB.
  *
  * @param string $className
  * @return wResultObjects
  */

  public function fetchAllObjectOfClass($className)
  {
    $table = $this->_prepareFetchObject($className);
    return parent::fetchAll(PDO::FETCH_CLASS, $className, array(self::$_pdo, $table[1]));
  }

  /** Cette m&eacute;thode ne fonctionne pas en PHP < 5.2.6
  * A cause d'un bug PHP que j'ai report&eacute; et qui a &eacute;t&eacute; corrig&eacute;
  */

  public function __call($method, $args)
  {
    if (preg_match("/^fetchAll(\w+)$/", $method, $matches)) {
      return $this->fetchAllObjectOfClass($matches[1]);
    } elseif (preg_match("/^fetchOne(\w+)$/", $method, $matches)) {
      return $this->fetchObjectOfClass($matches[1]);
    } else {
      trigger_error('Call to undefined method '.$matche[1], E_USER_ERROR);
    }
  }
}

?>

En ajoutant deux méthodes fetchObjectOfClass() et fetchAllObjectOfClass() on est en mesure de récupérer des objets depuis la base de données.

 

wResultObjects : représentation d’un objet

<?php

/**
* Database objects
* Allow fetchObjectOfClass()
*
* @author Julien PAULI
* @author William DURAND <william.durand1@gmail.com>
*
* Code original de Julien PAULI, modifi&eacute; pour wMVC
*/


abstract class wResultObjects
{
  /**
  * wPdo Instance
  *
  * @var wPdo
  */

  protected $_pdo;

  /**
  * table name
  *
  * @var string
  */

  protected $_tableName;

  /**
  * primary key
  *
  * @var string
  */

  protected static $_pk;

  /**
  * Constructor. Should be called if extended
  *
  * @param wPdo $pdo
  * @param string $tableName
  */

  public function __construct(wPdo $pdo, $tableName)
  {
    $this->_pdo = $pdo;
    $this->_tableName = $tableName;
  }

  /**
  * Returns all public attributes
  *
  * @return array
  */

  final protected function _getPublicMembers()
  {
    $reflect = new ReflectionObject($this);
   
    foreach ($reflect->getProperties() as $var) {
      if (!$var->isPublic()) {
        continue;
      }
      $prop[] = $this->{$var->getName()};
    }
   
    return $prop;
  }

  /**
  * Allow stringification
  *
  * @return string
  */

  public function __toString()
  {
    $prop = $this->_getPublicMembers();
    return implode(' - ', $prop);
  }

  /**
  * Should set a PK
  */

  public static function setPk($pk)
  {
    self::$_pk = $pk;
  }

  /**
  * Returns PK name
  */

  public function getPk()
  {
    return $this->$_pk;
  }

  /**
  * Should allow saving the object
  */

  abstract public function save();
}

?>

Chacun de nos objets étend cette classe. Elle contient le nom de la table et la clé primaire, deux informations nécessaires au bon fonctionnement du système. Notre classe représentant l’objet concret devra fournir ces deux informations mais également définir la manière par laquelle l’objet doit être sauvegardé.

Utilisation

Les classes précédentes font parties du core du MVC. Ce qui nous intéresse c’est le principe d’utilisation. Prenons un exemple, un objet Page.

Une Page sur un site contient les informations suivantes :

- Les métas Titre et Description.

- Le titre de la page

- Une description de la page

- Le stripped title qui permet d’avoir une URL compréhensible (Ex: mon-premier-article.html)

- Le contenu de la page

- Les dates de création et de mise à jour

Ce qui donne cette classe :

<?php

class Page extends wResultObjects
{
  private $id;
  private $metaTitle;
  private $metaDescription;
  private $title;
  private $description;
  private $strippedTitle;
  private $htmlContent;
  private $dateHeureCreation;
  private $dateHeureMiseAJour;

  public function __construct()
  {
    self::setPk('id');
  }

  public function __set($attribute, $value)
  {
    $this->$attribute = $value;
  }

  public function __get($attribute)
  {
    return $this->$attribute;
  }

  public function __toString()
  {
    return isset($this->htmlContent) ? $this->htmlContent : '';
  }

  public function save()
  {
    $cols = $this->_getPublicMembers();
    foreach ($cols as $col) {
      $set[] = $col.'='.$this->_pdo->quote($this->$col);
    }
   
    $query = 'UPDATE '.$this->_tableName.' SET ' .
    implode(',', $set) .
    ' WHERE ' .
    self::$_pk .'='. $this->{self::$_pk};

    $this->_pdo->exec($query);
    $this->{self::$_pk} = $this->_pdo->lastInsertId();
  }
}

?>

 

Pour créer une Page, on procède de cette manière :

$maPage = new Page();

 

Et pour la sauvegarder en base :

$maPage->save();

 

En toute transparence, on sauvegarde nos objets en base, sans devoir se préoccuper de la manière de sauvegarder l’objet. C’est très pratique.

  • Print
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Twitter
  • Google Bookmarks
  • FriendFeed
  • LinkedIn
  • MySpace
  • Netvibes
  • PDF
  • Ping.fm
  • RSS
  • Technorati
  • viadeo FR
  • Wikio
  • Yahoo! Buzz

Related Posts

Cet article a été publié dans Ancien blog avec les mots-clefs : , , , , , . Bookmarker le permalien. Laisser un commentaire ou faire un trackback : URL de trackback.

Laisser un commentaire

Votre e-mail ne sera jamais publié ni communiqué. Les champs obligatoires sont indiqués par *

*
*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting