Php5 - utilisation des itérateurs et de la réflection.


Contenu du snippet

Pfiou, avec un titre aussi compliqué, j'imagine que je ne vais pas avoir beaucoup de gens qui vont lire une source pareil. Remarquez, je vous comprends ^^

Rentrons dans le vif du sujet.
Kesako tout ca ?

Il y a une multitude de classes ici. Je ne décris pas les classes abstraites volontairement qui sont la que pour assurer une homogéité (?) entre les classes filles.
Donc nous avons :
-> Une classe SQL
-> Une classe factory de news
-> Une classe d'interfacage SQL/factory
-> Une classe d'itération
-> 2 classes de news dont l'une étend l'autre.

Comment est ce que ce beau monde fonctionne ensemble ?
Nous instancions la classe "factory" de news et nous lui disons quelle intérface utiliser (sql, xml, fichier standard... etc).
Nous appelons les méthodes de la factory. La factory utilise l'API de reflection pour utiliser la bonne interface.
C'est cette interface qui va renvoyer la ressource exploitable (ici pour MySQL, on revoit une ressource MySQL. Pour XML c'est un peu différent).
La factory, si il y a plusieurs enregistrements, renverra un itérateur. Autrement, elle renverra une classe de news simple.

Nous allons ensuite parcourir cette itérateur (grace à l'interface Iterator de PHP) pour récupérer chaque enregistrement en nous renvoyons systématiquement un objet (l'objet contenant la news en quelque sorte).
Et par la suite, nous allons pouvoir exploiter cet objet.

Je ne sais pas si je me suis bien fait comprendre (c'est assez dur au début), mais voici le code (il est très commenté) :

Source / Exemple :


<?php
$time = microtime(true);
/**

  • Interface permettant le dialogue entre une interface sortie et la/les news.
*
  • /
interface iSimpleNewsIO { /**
  • Récupère la liste entière de news.
*
  • /
public function GetAll(); /**
  • Récupère une liste de news comprise entre 2 bornes.
*
  • @param int $start
  • @param int $limit
  • /
public function GetRange($start, $limit); /**
  • Récupère une news à partir de son ID.
*
  • @param int $id
  • /
public function GetSingle($id); /**
  • Récupère la liste entière de news avec leurs nombres de commentaires respectifs.
*
  • /
public function GetAllWithCommentsCount(); /**
  • Récupère une liste de news comprise entre 2 bornes avec leurs nombres de commentaires.
*
  • @param int $start
  • @param int $limit
  • /
public function GetRangeWithCommentsCount($start, $limit); } /**
  • Interface permettant le dialogue entre une interface E/S et la news.
*
  • /
interface iExtendedNewsIO { /**
  • Ajoute une news.
*
  • @param FullAccessNews $news
  • /
public function Insert(FullAccessNews $news); /**
  • Modifie une news
*
  • @param FullAccessNews $news
  • /
public function Update(FullAccessNews $news); /**
  • Supprime une news.
*
  • @param FullAccessNews $news
  • /
public function Delete(FullAccessNews $news); /**
  • Récupère le nombre de commentaires d'une news.
*
  • @param FullAccessNews $news
  • /
public function GetCommentsCount(FullAccessNews $news); } // Classe DateTime déja posté sur PHPCS. require_once ('date.php'); /**
  • Classe d'itération.
  • Permet d'utiliser les itérateurs en cas de récupération multiples d'un même éléments
  • (ex : news, commentaires, pages, liens).
  • Cette classe renverra un objet utilisable.
*
  • /
class IterateMe implements Iterator { /**
  • Renvoit la clé en cours (autoincrément).
*
  • @var int
  • /
protected $key; /**
  • Sortie de la classe... renvoit un objet utilisable.
*
  • @var stdClass
  • /
protected $output; /**
  • Classe qui sera renvoyée à l'output.
*
  • @var ReflectionClass
  • /
protected $OutputClass; /**
  • Classe permettant de faire l'interface avec la récupération de données.
*
  • @var ReflectionClass
  • /
protected $InterfaceClass; /**
  • Requète utilisé pour l'itération (utilisable que sous MySQLi pour le moment).
*
  • @var MySQLi_stmt
  • /
protected $query; /**
  • Instancie l'itérateur.
*
  • @param MySQLi_stmt $query
  • @param ReflectionClass $InterfaceClass
  • @param ReflectionClass $OutputClass
  • /
public function __construct($query, ReflectionClass $InterfaceClass, ReflectionClass $OutputClass) { $this->key = -1; $this->InterfaceClass = $InterfaceClass; $this->OutputClass = $OutputClass; $this->query = $query; } /**
  • Retourne la clé en cours.
*
  • @return int
  • /
public function Key() { return $this->key; } /**
  • Pointe vers le prochain élément.
*
  • /
public function Next() { $this->output = $this->InterfaceClass->getMethod('Next')->invoke(NULL, $this->query, $this->OutputClass); $this->key++; } /**
  • Rembobine le pointeur.
*
  • /
public function Rewind() { $this->InterfaceClass->getMethod('Rewind')->invoke(NULL, $this->query); $this->Next(); } /**
  • Affiche l'enregistrement courant du pointeur.
*
  • @return stdClass
  • /
public function Current() { return $this->output; } /**
  • Vérifie si l'output est bien défini, sinon on stop.
*
  • @return bool
  • /
public function Valid() { return isset($this->output); } } /**
  • Classe d'interfacage avec n'importe quel type de platforme de stockage.
*
  • /
abstract class QueryInterface { /**
  • Constructeur protégé, cette classe ne sera utilisable qu'en utilisant les namespaces.
*
  • /
protected function __construct() { throw new Exception('This class can only be used with namespace.'); } /**
  • Methode permettant d'aller au prochain enregistrement lors d'une itération.
*
  • @param resource $resource
  • @param ReflectionClass $c
  • /
abstract static public function Next($resource, ReflectionClass $c); /**
  • Methode permettant de rembobiner à 0 le pointeur.
*
  • @param resource $resource
  • /
abstract static public function Rewind($resource); } abstract class SQLQueryInterface extends QueryInterface { /**
  • Garde en mémoire les appels de méthodes.
*
  • @var array
  • /
static protected $methods = array(); /**
  • Redéfinition de la méthode Next.
*
  • @param MySQLi_stmt $query
  • @param ReflectionClass $c
  • @return InstanceOf_ReflectionClass_$c
  • /
static public function Next($query, ReflectionClass $c) { return ( $data = $query->fetch_assoc() ) ? $c->newInstance($data) : NULL; } /**
  • Redéfinition de la méthode Rewind.
*
  • @param MySQLi_stmt $query
  • /
static public function Rewind($query) { $query->data_seek(0); } } class SQLSimpleNewsInterface extends SQLQueryInterface { static public function GetTotalNewsCount() { $var = System::GetSQLConnection()->query('SELECT COUNT(*) FROM news')->fetch_row(); return (int) $var[0]; } static public function GetAllNews() { return System::GetSQLConnection()->query('SELECT * FROM news'); } static public function GetRangedNews($start, $limit) { return System::GetSQLConnection()->query('SELECT * FROM news LIMIT '.$start.','.$limit); } static public function GetNews($id) { return System::GetSQLConnection()->query('SELECT * FROM news WHERE id = '.(int) $id)->fetch_array(); } static public function GetNewsWithCommentsCount() { return System::GetSQLConnection()->query( 'SELECT news.id, news.title, news.content, news.date, COUNT(comments.id) as nbcomms FROM news, comments WHERE news.id = comments.news_id GROUP BY news.id'); } static public function GetRangedNewsWithCommentsCount($start, $limit) { return System::GetSQLConnection()->query( 'SELECT news.id, news.title, news.content, news.date, COUNT(comments.id) as nbcomms FROM news, comments WHERE news.id = comments.news_id GROUP BY news.id LIMIT '.$start.', '.$limit); } } class SQLExtendedNewsInterface extends SQLSimpleNewsInterface { static public function InsertNews(ExtendedNews $news) { return System::GetSQLConnection()->query( 'INSERT INTO news (title, content, date) VALUES ("'.$news->title.'", "'.$news->content.'", "'.$news->date.'")')->affected_rows(); } static public function UpdateNews(ExtendedNews $news) { return System::GetSQLConnection()->query( 'UPDATE news SET title = "'.$news->title.'", content="'.$news->content.'", date = '.$news->date->GetTimestamp().' WHERE id = '.$news->id)->affected_rows(); } static public function DeleteNews(ExtendedNews $news) { return System::GetSQLConnection()->query( 'DELETE FROM news WHERE id = '.$news->id)->affected_rows(); } static public function GetCommentsCountFromNews(ExtendedNews $news) { $data = System::GetSQLConnection()->query('SELECT COUNT(*) FROM comments WHERE news_id = '.$news->id)->fetch_row(); return (int) $data[0]; } } /**
  • Classe abstraite gérant toutes les fabs.
*
  • /
abstract class Factory { /**
  • Attendu dans le constructeur le nom de l'interface utilisé.
*
  • @param string $InterfaceName
  • /
abstract public function __construct($InterfaceName); } class SimpleNewsFactory extends Factory implements iSimpleNewsIO { /**
  • Nom de l'interface utilisé.
*
  • @var ReflectionClass
  • /
static protected $InterfaceClass; /**
  • Instancie la fab avec l'interface voulu.
*
  • @param string $InterfaceName
  • /
public function __construct($InterfaceName = 'SQLSimpleNewsInterface') { try { self::$InterfaceClass = new ReflectionClass($InterfaceName); } catch ( ReflectionException $e ) { die( $e->getMessage() ); } } public function GetAll() { return new IterateMe(self::$InterfaceClass->getMethod('GetAllNews')->invoke(NULL), self::$InterfaceClass, new ReflectionClass('ReadOnlyNews') ); } public function GetRange($start, $limit) { return new IterateMe(self::$InterfaceClass->getMethod('GetRangedNews')->invoke(NULL, $start, $limit), self::$InterfaceClass, new ReflectionClass('ReadOnlyNews') ); } public function GetSingle($id) { return new FullAccessNews( self::$InterfaceClass->getMethod('GetNews')->invoke(NULL, $id) ); } public function GetAllWithCommentsCount() { return new IterateMe(self::$InterfaceClass->getMethod('GetAllNewsWithCommentsCount')->invoke(NULL), self::$InterfaceClass, new ReflectionClass('ReadOnlyNewsWithComments') ); } public function GetRangeWithCommentsCount($start, $limit) { return new IterateMe(self::$InterfaceClass->getMethod('GetRangedNewsWithCommentsCount')->invoke(NULL, $start, $limit), self::$InterfaceClass, new ReflectionClass('ReadOnlyNewsWithComments') ); } } class ExtendedNewsFactory extends SimpleNewsFactory implements iExtendedNewsIO { public function __construct($InterfaceName = 'SQLExtendedNewsInterface') { parent::__construct($InterfaceName); } public function Insert(FullAccessNews $news) { if ( self::$InterfaceClass->getMethod('InsertNews')->invoke(NULL, $news) !== 1 ) throw new Exception('Impossible to add newsitem !'); return true; } public function Update(FullAccessNews $news) { if ( self::$InterfaceClass->getMethod('UpdateNews')->invoke(NULL, $news) !== 0 ) throw new Exception('Impossible to add newsitem !'); return true; } public function Delete(FullAccessNews $news) { if ( self::$InterfaceClass->getMethod('DeleteNews')->invoke(NULL, $news) !== 1 ) throw new Exception('Impossible to delete newsitem !'); return true; } public function GetCommentsCount(FullAccessNews $news) { return self::$InterfaceClass->getMethod('GetCommentsCountFromNews')->invoke(NULL, $news); } } abstract class News { protected $id; protected $title; protected $content; protected $date; public function __construct($params) { $this->id = $params['id']; $this->title = $params['title']; $this->content = $params['content']; $this->date = new oDate($params['date']); } public function __get($var) { if ( property_exists($this, $var) ) return $this->$var; else throw new Exception($var.' doesn\'t exists !'); } abstract public function __set($var, $val); } class ReadOnlyNews extends News { public function __set($var, $val) { throw new Exception('SimpleNews is a read-only class !'); } } class ReadOnlyNewsWithComments extends ReadOnlyNews { protected $coms_count; public function __construct($params) { parent::__construct($params); $this->coms_count = $params['coms_count']; } } class FullAccessNews extends News { public function __set($var, $val) { if ( property_exists($var, $val) ) $this->$var = $val; else throw new Exception($var.' doesn\'t exists !'); } } class System { static private $_mods = array(); static private $_criticals = array(); static public function GetModule($ModuleName, $InterfaceName=null) { if ( !isset(self::$_mods[$ModuleName]) ) { $ClassMod = new ReflectionClass($ModuleName); if ( isset($InterfaceName) ) { self::$_mods[$ModuleName]['instance'] = $ClassMod->newInstance($InterfaceName); self::$_mods[$ModuleName]['interface'] = $InterfaceName; } else { $params = $ClassMod->getConstructor()->getParameters(); self::$_mods[$ModuleName]['interface'] = $params[0]->getDefaultValue(); self::$_mods[$ModuleName]['instance'] = $ClassMod->newInstance(); } unset($ClassMod); } return self::$_mods[$ModuleName]['instance']; } static public function GetSQLConnection() { if ( !isset(self::$_criticals['sql']) ) self::$_criticals['sql'] = new mysqli('localhost', 'root', '', 'blog'); return self::$_criticals['sql']; } static public function GetXMLConnection() { // } } // Instanciation de la classe. $NewsFab = System::GetModule('SimpleNewsFactory'); // Pour une itération simple : $news = $NewsFab->GetSingle(1); echo $news->id.'<br />'.$news->title.'<br />'.$news->content.'<br/><br />'; echo '<br /><br /><br />'; // Pour une itération multiple : $allnews = $NewsFab->GetRange(0,3); foreach ( $allnews as $key=>$line) { echo $line->date.' - '.$line->title.'<br />'; echo $line->content.'<br /><br />'; } echo '<br /><br /><br />'; // Ou alors : foreach ( $NewsFab->GetRange(5,5) as $key=>$line ) { echo $line->date.' - '.$line->title.'<br />'; echo $line->content.'<br /><br />'; } //Même technique qu'au dessus. echo '<br /><br /><br />'; echo microtime(true)-$time; ?>

Conclusion :


Je passe sur la classe DateTime... :)

J'ai utilisé ici un exemple de news.
Mais on peut faire la même chose pour des commentaires, ou alors pour des dossiers ( un dossier avec plusieurs pages ), ou alors pour des liens, ou pour des téléchargements !
Il suffit de créer les classes correspondantes à chaque thème et la classe d'interface qui va avec, puis mettre tout en relation avec la classe d'itération.

C'est un peu compliqué avec la reflection, je vous invite à regarder sur le site officiel de PHP :
http://fr3.php.net/manual/fr/language.oop5.reflection.php

Bonne lecture :)

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.