Contenu | Menu | Rechercher :
Le réseau des étudiants | Contact

Reflection en PHP

“Le présent article n'a pas encore été revu par un modérateur, pour cela veuillez faire attention quant à son contenu, que nous ne pouvons pas vous garantir son exactitude.”

Introduction à la reflection en PHP

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-engineering 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écisément 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.

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

Exemple de réflexion

C'est bien beau toutes ces classes, mais quelles méthodes proposent-elles ? Eh bien, utilisons la réflexion pour le savoir ! Nous allons donc utiliser la classe ReflectionClass, et plus précisément 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.

Classe à documenter avec la reflection

Voici la classe que nous prendrons comme exemple dans cet 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 requête 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 :

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 œuvre. 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.

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) :

protected getProperties()

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

protected getPropertyValues()

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

protected getConstants()

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

protected getMethods()

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

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é))

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.

Approfondissez votre lecture :

- reflection

Informations sur l'auteur

codeur
  • Pseudo : codeur
  • Date de naissance : 1985-10-03
  • Age : 32 ans
  • Pays : Maroc
  • école

Informations sur le cours

  • Nombre de fois vus : 713
  • Langue de rédaction : Français
  • Date de création : le 05/05/2017 à 23:43:28
  • Date de mise à jour : le 06/05/2017 à 00:17:15
Cacher ce panneau   Accéder à la version étendue du chat

Cacher ce panneau