Logo classes.scriptsphp.org PHP

go to nav bar

Un peu de reflection en PHP5

Date :: 2005-02-24
Last Updated :: 2005-04-25


Introduction

Reflection est une API orientée objet constituée d'une interface et d'un ensemble de classes disponible par défaut depuis PHP5 servant à faire du reverse-enginering sur les fonctions, les classes ou les extensions.
Dans cet article, nous allons voir une utilisation possible de quelques unes des classes proposées pour générer automatiquement de la documentation, et plus précisemment un document XML représentatif du squelette d'une classe.

Classes proposées par l'API reflection

Voici les classes (et l'interface) proposées par l'API de réflexion, comme vous pouvez le constater, leur nom est assez explicite.

  • Reflector : Interface. Elle définit deux méthodes abstraites : public static export() et public __toString().
  • Reflection : Classe de plus haut niveau
  • ReflectionFunction :: Récupère des informations sur les fonctions. Elle implémente l'interface Reflector
  • ReflectionObject :: Récupère des informations sur les Objets. Cette classe étend les fonctionnalités de la classe ReflectionClass.
  • ReflectionExtension :: Récupère des informations sur les extensions. Elle implémente l'interface Reflector.
  • *ReflectionClass :: Récupère des informations sur les classes. Cette classe implémente l'interface Reflector.
  • *ReflectionMethod :: ... sur les méthodes
  • *ReflectionParameter :: ... sur les paramètres (des méthodes et des fonctions). Elle implémente l'interface Reflector
  • *ReflectionProperty :: ... sur les propriétés d'un objet
  • *ReflectionException : Classe de gestion des exceptions, elle hérite de la classe Exception

Les classes marquées d'un * sont celles qui seront utilisées dans cet article.

Un premier exemple simple de réflexion

C'est bien beau toutes ces classes, mais quelles méthodes proposent-elles ? Et bien, utilisons la réflexion pour le savoir ! Nous allons donc utiliser la classe ReflectionClass, et plus pécisemment la méthode ReflectionClass :: getMethods() pour connaître les méthodes disponibles de ces classes :

<?php 

$class 
'ReflectionClass';

try {
    
$R = new ReflectionClass$class );
    foreach( 
$R -> getMethods() as $method ) {
        echo 
$method '<br/>';
    }
}
catch( 
ReflectionException $e ) {
    echo 
'<pre>' $e '</pre>';
}

?>

Il vous suffit de modifier la variable $class à votre guise pour découvrir les méthodes proposées par ces classes (ou toutes les autres classes, internes et utilisateurs, d'ailleurs ...).
Vous remarquerez également que nous tentons d'instancier la classe ReflectionClass, dans un bloc try{}catch{}, en effet, si vous passez un nom de classe inexistante au constructeur de ReflectionClass, une exception est levée.

La classe que nous allons documenter

Voici la classe que nous prendrons comme exemple dans cette article. C'est donc une classe simpliste d'abstraction pour la base de données MySQL, elle comporte quelques propriétés, constantes et méthodes dont nous essaierons de tirer le maximum d'informations grâce à la réflexion.

<?php 

/**
 * Classe d abstraction avec la base de données MySQL
 *
 * @author fab
 * @version 0.1
 */
class _MySQL {

    const 
HOST 'sql.scriptsphp.org';
    const 
USER 'scriptsphp_org';
    const 
PASSWORD '-------';
    const 
DB 'scriptsphp_org_1';
    static 
$Instance false;
    private 
$queryResult false;


    private final function 
__construct() {
        if(!@
mysql_connect(_MySQL::HOST_MySQL::USER_MySQL::PASSWORD)) {
            die(
'connection failed');
        }
        if(!@
mysql_select_db(_MySQL::DB)) {
            die(
'Database not selected');
        }
        echo 
'connected';
    }

    public static function 
GetInstance() {
        if (empty(
self::$Instance)) {
            
$class __CLASS__;
            
self::$Instance = new $class;
        }
        return 
self::$Instance;
    }

    
/**
     * Envoie une requete vers la BDD
     *
     * @since version 0.1
     * @param string $query requete SQL
     * @return ressource pointeur de resultat renvoyé par mysql_query()
     */
    
public function query($query) {
        return 
$this->queryResult = @mysql_query($query);
    }

    public function 
getArray() {
        
$data = array();
        while(
$row mysql_fetch_assoc($this->queryResult)) {
            
$data[] = $row;
        }
        return 
$data;
    }


}
?>

Classes de traitement

Créons donc deux classes :

  • docGen : Classe abstraite gérant la réflexion sur les classes et les méthodes
  • docGen2XML : Export XML des données récupérées

Classe principale

docGen est la classe abstraite qui s'occupe de récupérer toutes les informations possibles grâce à la réflexion. Les méthodes de cette classe sont documentées un peu plus bas.

<?php 
abstract class docGen {

    public 
$method = array();
    protected 
$methods = array();
    protected 
$constants = array();
    protected 
$properties = array();

    public function 
__construct($class) {
        
$this->className $class;
        
$this->class = new ReflectionClass($class);
    }
    
    public function 
getClassName() {
        return 
$this->className;
    }

    protected function 
getClassDefinition() {
        
$T = array();
        if(
$this->class->isInterface()) {
            
$T['interface'] = 'interface';
        }
        if(
$this->class->isAbstract()) {
            
$T['abstract'] = 'abstract';
        }
        if(
$this->class->isFinal()) {
            
$T['final'] = 'final';
        }
        return 
$T;
    }
    
    protected function 
getProperties() {
        if(empty(
$this->properties)) {
            foreach(
$this->class->getProperties() as $prop) {
                
$this->properties[] = $prop;
                
$this->property[$prop->name] = new ReflectionProperty($this->className$prop->name);
            }
        }
        return 
$this->properties;
    }

    protected function 
getPropertyValues($name) {
        
$T = array();
        if(
$this->property[$name]->isPublic()) {
            
$T['visibility'] = 'public';
        }elseif(
$this->property[$name]->isPrivate()) {
            
$T['visibility'] = 'private';
        }elseif(
$this->property[$name]->isProtected()) {
            
$T['visibility'] = 'protected';
        }
        else {
            
$T['visibility'] = '';
        }
        if(
$this->property[$name]->isStatic()) {
            
$T['static'] = 'static';
        }

        return 
$T;
    }
    
    protected function 
getConstants() {
        if(empty(
$this->constants)) {
            
$this->constants $this->class->getConstants();
        }
        return 
$this->constants;
    }
    
    protected function 
getMethods() {
        if(empty(
$this->methods)) {
            foreach(
$this->class->getMethods() as $method) {
                
$this->method[$method->name] = new reflectionMethod($method->class$method->name);
                
$this->methods[] = $method;
            }
        }
        return 
$this->methods;
    }

    protected function 
getMethodProperties($methodName) {
        
$T = array();
        if(
$this->method[$methodName]->isConstructor()) {
            
$T['constructor'] = 'Constructeur de la classe';
        }
        
        
// http://bugs.php.net/bug.php?id=32076
        
if($this->method[$methodName]->isDestructor()) {
            
//$T[] = 'Destructeur de la classe';
        
}
        
        if(
$v $this->getMethodVisibility($methodName)) {
            
$T['visibility'] = $v;
        }
        if(
$this->method[$methodName]->isFinal()) {
            
$T['final'] = 'final';
        }
        if(
$this->method[$methodName]->isAbstract()) {
            
$T['abstract'] = 'abstract';
        }
        if(
$this->method[$methodName]->isStatic()) {
            
$T['static'] = 'static';
        }

        return 
$T;
    }
    
    protected function 
getMethodParameters($methodName) {
        return 
$this->method[$methodName]->getParameters();
    }

    private function 
getMethodVisibility($methodName) {
        if(
$this->method[$methodName]->isPublic()) {
            return 
'public';
        }elseif(
$this->method[$methodName]->isPrivate()) {
            return 
'private';
        }elseif(
$this->method[$methodName]->isProtected()) {
            return 
'protected';
        }
        return 
false;
    }

}

?>

Export XML

Maintenant que nous avons développé les méthodes nécessaires à la récupération des informations de notre classe, il est temps de les mettre en oeuvre. Pour cet exemple, nous allons générer un document XML représentatif du squelette de la classe _MySQL. Le code suivant est un peu long, mais ne nécessite pas de commentaire particulier. C'est une simple classe, docGen2XML héritant des fonctionnalités de la classe docGen, et contenant une unique méthode membre destinée à construire le fichier XML.

<?php 
class docGen2XML extends docGen {

    public function 
toXML() {
        
$r  '<root>';
        
$r .= '<class';
        foreach(
$this->getClassDefinition() as $key => $v) {
            
$r .= ' '.$key.'="1" ';
        }
        
$r .= '>'.$this->getClassName().'</class>';
        
        
// Propriétés de la classe
        
$P $this->getProperties();
        if(!empty(
$P)) {
            
$r .='<properties>';
            foreach(
$P as $prop => $Pvalue) {
                if(
$Pvalue->class == $this->className) {
                    
$PV $this->getPropertyValues($Pvalue->name);
                    
$r .= '<item name="'.$Pvalue->name.'" ';
                    
$r .= ' visibility="'.$PV['visibility'].'" ';
                    if(isset(
$PV['static'])) {
                        
$r .= ' static="1" ';
                    }
                    
$r .= '>';    
                    
$r .= '</item>';
                }
            }
            
$r .= '</properties>';


        }

        
// Constantes de la classe
        
$C $this->getConstants();
        if(!empty(
$C)) {
            
$r .= '<constants>';
            foreach(
$C as $name => $value) {
                
$r .= '<item name="'.$name.'">';
                
$r .= '<value>'.$value.'</value>';
                
$r.= '</item>';
            }
            
$r .= '</constants>';
        }

        
// Méthodes de la classe
        
$M $this->getMethods();
        if(!empty(
$M)) {
            
$r .= '<methods>';
            foreach(
$M as $method) {
                
$r .= '<item name="'.$method->name.'"';
                
$pValues $this->getMethodProperties($method->name);
                
$r .= ' visibility="'.$pValues['visibility'].'" ';
                if(isset(
$pValues['static'])) {
                    
$r .= ' static="1" ';
                }
                if(isset(
$pValues['final'])) {
                    
$r .= ' final="1" ';
                }
                if(isset(
$pValues['abstract'])) {
                    
$r .= ' abstract="1" ';
                }
                
$r .= '>';
                
$Param $this->getMethodParameters($method->name);
                if(!empty(
$Param)) {
                    foreach(
$Param as $parameter) {
                        
$r .= '<parameter>'.$parameter->name.'</parameter>';
                    }
                }
                
$r .= '</item>';
            }
            
$r .= '</methods>';
        }
        
$r .= '</root>';
        return 
$r;
    }
}
?>

Détails des méthodes de la classe docGen

public __construct( string $className )

Constructeur de la classe, il attend en unique paramètre le nom de la classe à documenter. Une instance de la classe ReflectionClass() est créée.

  • ReflectionClass :: __construct()

public getClassName()

Renvoie simplement le nom de la classe courante sur laquelle on fait de la réflexion.

protected getClassDefinition()

Cette méthode détermine si la classe est abstraite, finale, ou une interface.
Voici les méthodes de réflexion utilisées (Leurs noms parlent d'elles-mêmes) :

  • ReflectionClass :: isAbstract()
  • ReflectionClass :: isFinal()
  • ReflectionClass :: isInterface()

protected getProperties()

Cette méthode renvoie toutes les propriétés de la classe. Pour chaque propriété, nous instancierons un objet ReflectionProperty.

  • ReflectionClass :: getProperties() : Renvoie un tableau contenant toutes les propriétés;
  • ReflectionProperty :: __construct() : Pour chaque propriété renvoyée par la méthode précédente, un objet ReflectionProperty sera instancié.

protected getPropertyValues()

Cette méthode détermine la visibilité d'une propriété (public, protected ou private) et si elle est statique ou non.

  • ReflectionProperty :: isPublic()
  • ReflectionProperty :: isProtected()
  • ReflectionProperty :: isPrivate()
  • ReflectionProperty :: isStatic()

protected getConstants()

Cette méthode renvoie un tableau descriptif des constantes de la classe (nom et valeur).

  • ReflectionClass :: getConstants()

protected getMethods()

Cette méthode retourne un tableau de toutes les méthodes de la classe et instancie un objet ReflectionMethod pour chaque.

  • ReflectionClass :: getMethods() : Renvoie un tableau de toutes les méthodes de la classe
  • ReflectionMethod :: __construct() : Pour chaque méthode renvoyée par la méthode précedente, un objet ReflectionMethod sera instancié.

protected getMethodProperties()

Cette méthode récupère la visibilité de chaque méthode (public, protected ou private) et détermine si elle declarée comme finale, abstraite ou statique. Elle détermine également si la méthode est le constructeur de la classe ou le destructeur(voir le bug soumis (et corrigé))

  • ReflectionMethod :: isConstructor()
  • ReflectionMethod :: isDestructor()
  • ReflectionMethod :: isPublic()
  • ReflectionMethod :: isProtected()
  • ReflectionMethod :: isPrivate()
  • ReflectionMethod :: isStatic()
  • ReflectionMethod :: isFinal()
  • ReflectionMethod :: isAbstract()

Utilisation

<?php

try {
    
header ('content-type: text/xml');
    
$doc = new docGen2XML('_MySQL');
    
$display $doc->toXML();
}
catch(
ReflectionException $e) {
    
$display $e;
}

echo 
$display;

?>

Résultat

Voici le code XML qui sera généré :

<root>
    <class>_MySQL</class>
    <properties>
        <item name="Instance" visibility="public" static="1"/>
        <item name="queryResult" visibility="private"/>
    </properties>
    <constants>
        <item name="HOST">
            <value>localhost</value>
        </item>
        <item name="USER">
            <value>scriptsphp_org</value>
        </item>
        <item name="PASSWORD">
            <value>-------</value>
        </item>
        <item name="DB">
            <value>scriptsphp_org_1</value>
        </item>
    </constants>
    <methods>
        <item name="__construct" visibility="private" final="1"/>
        <item name="GetInstance" visibility="public" static="1"/>
        <item name="query" visibility="public">
            <parameter>query</parameter>
        </item>
        <item name="getArray" visibility="public"/>
    </methods>
</root>

Conclusion

Nous avons donc, grâce à l'API de réflexion récupérer un nombre non-négligeable d'informations sur notre classe exemple. Il aurait été très fastidieux d'arriver au même résultat sans la réflexion, il aurait notamment fallu utiliser massivement les expressions régulières.

Trackback

Il n'y a pas de trackback recensé pour cet article.

Faire un trackback sur cet article http://classes.scriptsphp.org/Trackbackserver.Un-peu-de-reflection-en-PHP5, récupérer les trackback sur cet article

Merci de ne pas suivre ce lien emails.

0.1483s | «»
PHP powered