Réflexion

Introduction

PHP 5 introduit API de réflexion complète qui permet de faire du reverse-engineering sur les classes, les interfaces, les fonctions et les méthodes tout comme les extensions. L'API de réflexion permet également d'obtenir les commentaires de la documentation pour les fonctions, les classes et les méthodes.

L'API de réflexion est une extension orientée objet du Moteur Zend, constituée des classes suivantes :

Exemple 19.33. API de réflexion

<?php
class  Reflection  { }
interface 
Reflector  { }
class 
ReflectionException  extends  Exception  { }
class 
ReflectionFunction  extends  ReflectionFunctionAbstract  implements  Reflector  { }
class 
ReflectionParameter  implements  Reflector  { }
class 
ReflectionMethod  extends  ReflectionFunctionAbstract  implements  Reflector  { }
class 
ReflectionClass  implements  Reflector  { }
class 
ReflectionObject  extends  ReflectionClass  { }
class 
ReflectionProperty  implements  Reflector  { }
class 
ReflectionExtension  implements  Reflector  { }
?>

Note: Pour plus de détails sur ces classes, lisez les chapitres suivants.

Si nous exécutons le code de l'exemple ci-dessous :

Exemple 19.34. Utilisation basique de l'API de réflexion

<?php
Reflection
:: export (new  ReflectionClass ( 'Exception' ));
?>

L'exemple ci-dessus va afficher :


Class [ <internal> class Exception ] {

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [6] {
    Property [ <default> protected $message ]
    Property [ <default> private $string ]
    Property [ <default> protected $code ]
    Property [ <default> protected $file ]
    Property [ <default> protected $line ]
    Property [ <default> private $trace ]
  }

  - Methods [9] {
    Method [ <internal> final private method __clone ] {
    }

    Method [ <internal> <ctor> public method __construct ] {

      - Parameters [2] {
        Parameter #0 [ <required> $message ]
        Parameter #1 [ <required> $code ]
      }
    }

    Method [ <internal> final public method getMessage ] {
    }

    Method [ <internal> final public method getCode ] {
    }

    Method [ <internal> final public method getFile ] {
    }

    Method [ <internal> final public method getLine ] {
    }

    Method [ <internal> final public method getTrace ] {
    }

    Method [ <internal> final public method getTraceAsString ] {
    }

    Method [ <internal> public method __toString ] {
    }
  }
}

     

ReflectionException

ReflectionException étend le standard Exception et est lancé par l'API Reflection. Aucune méthode spécifique ni de propriété ne sont introduites.

ReflectionFunction

La classe ReflectionFunction vous permet de faire du reverse-engineering sur les fonctions.

Exemple 19.35. La classe ReflectionFunction

<?php
class  ReflectionFunction  extends  ReflectionFunctionAbstract  implements  Reflector
{
    final private 
__clone ()
    public 
object __construct ( string name )
    public 
string __toString ()
    public static 
string export ( string name bool  return)
    public 
string getName ()
    public 
bool isInternal ()
    public 
bool isUserDefined ()
    public 
string getFileName ()
    public 
int getStartLine ()
    public 
int getEndLine ()
    public 
string getDocComment ()
    public array 
getStaticVariables ()
    public 
mixed invoke ( mixed args )
    public 
mixed invokeArgs (array  args )
    public 
bool returnsReference ()
    public 
ReflectionParameter []  getParameters ()
    public 
int getNumberOfParameters ()
    public 
int getNumberOfRequiredParameters ()
}
?>

Note: getNumberOfParameters() et getNumberOfRequiredParameters() ont été ajoutés en PHP 5.0.3, tandis que invokeArgs() a été ajouté en PHP 5.1.0.

Pour connaître le fonctionnement d'une fonction, vous devez tout d'abord créer une instance de la classe ReflectionFunction . Ainsi, vous pouvez appeler n'importe quelle méthode de cette instance.

Exemple 19.36. Utilisation de la classe ReflectionFunction

<?php
/**
 * Un simple compteur
 *
 * @return    int
 */
function  counter ()
{
    static 
$c  0 ;

    return 
$c ++;
}

// Création d'une instance de la classe Reflection_Function
$func  = new  ReflectionFunction ( 'counter' );

// Affichage d'informations basiques
printf (
    
"===> The %s function '%s'\n" .
    
"     declared in %s\n" .
    
"     lines %d to %d\n" ,
    
$func -> isInternal () ?  'internal'  'user-defined' ,
    
$func -> getName (),
    
$func -> getFileName (),
    
$func -> getStartLine (),
    
$func -> getEndline ()
);

// Affichage du commentaire de la documentation
printf ( "---> Documentation:\n %s\n" var_export ( $func -> getDocComment (),  1 ));

// Affichage des variables statiques si elles existent
if ( $statics  $func -> getStaticVariables ())
{
    
printf ( "---> Variables statiques : %s\n" var_export ( $statics 1 ));
}

// Appel de la fonction
printf ( "---> Invocation des résultats dans : " );
var_dump ( $func -> invoke ());


// vous pouvez préférer utiliser la méthode export()
echo  "\nRésultat de ReflectionFunction::export() :\n" ;
echo 
ReflectionFunction :: export ( 'counter' );
?>

Note: La méthode invoke() accepte un nombre variable d'arguments, tout comme la fonction call_user_func() .

ReflectionParameter

La classe ReflectionParameter récupère les informations concernant les paramètres des fonctions ou des méthodes.

Exemple 19.37. La classe ReflectionParameter

<?php
class  ReflectionParameter  implements  Reflector
{
    final private 
__clone ()
    public 
object __construct ( string  function,  string parameter )
    public 
string __toString ()
    public static 
string export ( mixed  function,  mixed parameter bool  return)
    public 
string getName ()
    public 
bool isPassedByReference ()
    public 
ReflectionClass getDeclaringClass ()
    public 
ReflectionClass getClass ()
    public 
bool isArray ()
    public 
bool allowsNull ()
    public 
bool isPassedByReference ()
    public 
bool isOptional ()
    public 
bool isDefaultValueAvailable ()
    public 
mixed getDefaultValue ()
}
?>

Note: getDefaultValue() , isDefaultValueAvailable() et isOptional() ont été ajoutés en PHP 5.0.3, tandis que isArray() a été ajoutée en PHP 5.1.0.

Pour connaître le fonctionnement des paramètres d'une fonction, vous devez tout d'abord créer une instance de la classe ReflectionFunction ou ReflectionMethod et, ainsi, utiliser leurs méthodes getparameters() pour récupérer un tableau de paramètres.

Exemple 19.38. Utilisation de la classe ReflectionParameter

<?php
function  foo ( $a $b $c ) { }
function 
bar ( Exception $a , & $b $c ) { }
function 
baz ( ReflectionFunction $a $b  1 $c  null ) { }
function 
abc () { }

// Création d'une instance de la classe Reflection_Function avec le
// paramètre fourni en ligne de commande.
$reflect  = new  ReflectionFunction ( $argv [ 1 ]);

echo 
$reflect ;

foreach (
$reflect -> getParameters () as  $i  =>  $param ) {
    
printf (
        
"-- Paramètre #%d : %s {\n" .
        
"   Classe : %s\n" .
        
"   Autorise NULL : %s\n" .
        
"   Passé par référence : %s\n" .
        
"   Est optionnel ?: %s\n" .
        
"}\n" ,
        
$i ,
        
$param -> getName (),
        
var_export ( $param -> getClass (),  1 ),
        
var_export ( $param -> allowsNull (),  1 ),
        
var_export ( $param -> isPassedByReference (),  1 ),
        
$param -> isOptional () ?  'oui'  'non'
    
);
}
?>

ReflectionClass

La classe ReflectionClass vous permet de faire du reverse-engineering sur des classes.

Exemple 19.39. La classe ReflectionClass

<?php
class  ReflectionClass  implements  Reflector
{
    final private 
__clone ()
    public 
object __construct ( string name )
    public 
string __toString ()
    public static 
string export ( mixed  class,  bool  return)
    public 
string getName ()
    public 
bool isInternal ()
    public 
bool isUserDefined ()
    public 
bool isInstantiable ()
    public 
bool hasConstant ( string name )
    public 
bool hasMethod ( string name )
    public 
bool hasProperty ( string name )
    public 
string getFileName ()
    public 
int getStartLine ()
    public 
int getEndLine ()
    public 
string getDocComment ()
    public 
ReflectionMethod getConstructor ()
    public 
ReflectionMethod getMethod ( string name )
    public 
ReflectionMethod []  getMethods ()
    public 
ReflectionProperty getProperty ( string name )
    public 
ReflectionProperty []  getProperties ()
    public array 
getConstants ()
    public 
mixed getConstant ( string name )
    public 
ReflectionClass []  getInterfaces ()
    public 
bool isInterface ()
    public 
bool isAbstract ()
    public 
bool isFinal ()
    public 
int getModifiers ()
    public 
bool isInstance ( stdclass object )
    public 
stdclass newInstance ( mixed args )
    public 
stdclass newInstanceArgs (array  args )
    public 
ReflectionClass getParentClass ()
    public 
bool isSubclassOf ( ReflectionClass  class)
    public array 
getStaticProperties ()
    public 
mixed getStaticPropertyValue ( string name  [,  mixed  default])
    public 
void setStaticPropertyValue ( string name mixed value )
    public array 
getDefaultProperties ()
    public 
bool isIterateable ()
    public 
bool implementsInterface ( string name )
    public 
ReflectionExtension getExtension ()
    public 
string getExtensionName ()
}
?>

Note: hasConstant() , hasMethod() , hasProperty() , getStaticPropertyValue() et setStaticPropertyValue() ont été ajoutées en PHP 5.1.0, tandis que newInstanceArgs() a été ajoutée dans PHP 5.1.3.

Pour connaître le fonctionnement d'une classe, vous devez d'abord créer une instance de la classe ReflectionClass . Vous pourrez donc appeler n'importe quelle méthode sur cette instance.

Exemple 19.40. Utilisation de la classe ReflectionClass

<?php
interface  Linearisable
{
    
// ...
}

class 
Object
{
    
// ...
}

/**
 * Une classe compteur
 *
 */
class  Compteur  extends  Object  implements  Linearisable
{
    const 
START  0 ;
    private static 
$c  Compteur :: START ;

    
/**
     * Invocation du compteur
     *
     * @access  public
     * @return  int
     */
    
public function  count ()
    {
        return 
self :: $c ++;
    }
}

// Création d'une instance de la classe ReflectionClass
$class  = new  ReflectionClass ( 'Compteur' );

// Affichage d'informations basiques
printf (
    
"===> La %s%s%s %s '%s' [extension de %s]\n" .
    
"     déclarée dans %s\n" .
    
"     lignes %d à %d\n" .
    
"     a le modificateur %d [%s]\n" ,
    
$class -> isInternal () ?  'internal'  'user-defined' ,
    
$class -> isAbstract () ?  ' abstract'  '' ,
    
$class -> isFinal () ?  ' final'  '' ,
    
$class -> isInterface () ?  'interface'  'class' ,
    
$class -> getName (),
    
var_export ( $class -> getParentClass (),  1 ),
    
$class -> getFileName (),
    
$class -> getStartLine (),
    
$class -> getEndline (),
    
$class -> getModifiers (),
    
implode ( ' ' Reflection :: getModifierNames ( $class -> getModifiers ()))
);

// Affichage du commentaire de la documentation
printf ( "---> Documentation:\n %s\n" var_export ( $class -> getDocComment (),  1 ));

// Affichage de l'interface qui implémente cette classe
printf ( "---> Implémenté :\n %s\n" var_export ( $class -> getInterfaces (),  1 ));

// Affichage des constantes de la classe
printf ( "---> Constantes : %s\n" var_export ( $class -> getConstants (),  1 ));

// Affichage des propriétés de la classe
printf ( "---> Properties: %s\n" var_export ( $class -> getProperties (),  1 ));

// Affichage des méthodes de la classe
printf ( "---> Méthodes : %s\n" var_export ( $class -> getMethods (),  1 ));

// Si cette classe est instanciable, création d'une instance
if ( $class -> isInstantiable ()) {
    
$counter  $class -> newInstance ();

    echo 
'---> $counter est uneinstance ? ' ;
    echo 
$class -> isInstance ( $counter ) ?  'oui'  'non' ;

    echo 
"\n---> Le nouvel objet Object() est une instance ? " ;
    echo 
$class -> isInstance (new  Object ()) ?  'oui'  'non' ;
}
?>

Note: La méthode newinstance() accepte un nombre variable d'arguments, tout comme la fonction call_user_func() .

Note: $class = new ReflectionClass('Foo'); $class->isInstance($arg) est équivalent à $arg instanceof Foo ou is_a($arg, 'Foo') .

ReflectionObject

La classe ReflectionObject vous permet de retrouver les objets.

<?php
class  ReflectionObject  extends  ReflectionClass
{
    final private 
__clone ()
    public 
object __construct ( mixed object )
    public 
string __toString ()
    public static 
string export ( mixed object bool  return)
}
?>

ReflectionMethod

La classe ReflectionMethod vous permet de faire du reverse-engineering sur les méthodes des classes.

Exemple 19.41. La classe ReflectionMethod

<?php
class  ReflectionMethod  extends  ReflectionFunctionAbstract  implements  Reflector
{
    public 
__construct ( mixed  class,  string name )
    public 
string __toString ()
    public static 
string export ( mixed  class,  string name bool  return)
    public 
mixed invoke ( stdclass object mixed args )
    public 
mixed invokeArgs ( stdclass object , array  args )
    public 
bool isFinal ()
    public 
bool isAbstract ()
    public 
bool isPublic ()
    public 
bool isPrivate ()
    public 
bool isProtected ()
    public 
bool isStatic ()
    public 
bool isConstructor ()
    public 
bool isDestructor ()
    public 
int getModifiers ()
    public 
ReflectionClass getDeclaringClass ()

    
// Inherited from ReflectionFunction
    
final private  __clone ()
    public 
string getName ()
    public 
bool isInternal ()
    public 
bool isUserDefined ()
    public 
string getFileName ()
    public 
int getStartLine ()
    public 
int getEndLine ()
    public 
string getDocComment ()
    public array 
getStaticVariables ()
    public 
bool returnsReference ()
    public 
ReflectionParameter []  getParameters ()
    public 
int getNumberOfParameters ()
    public 
int getNumberOfRequiredParameters ()
}
?>

Pour connaître le fonctionnement d'une méthode, vous devez d'abord créer une instance de la classe ReflectionMethod . Vous pourrez ainsi appeler n'importe quelle méthode de cette instance.

Exemple 19.42.  Utilisation de la classe ReflectionMethod

<?php
class  Compteur
{
    private static 
$c  0 ;

    
/**
     * Incrémentation d'un compteur
     *
     * @final
     * @static
     * @access  public
     * @return  int
     */
    
final public static function  increment ()
    {
        return 
self :: $c ;
    }
}

// Création d'une instance de la classe Reflection_Method
$method  = new  ReflectionMethod ( 'Compteur' 'increment' );

// Affichage d'informations basiques
printf (
    
"===> La méthode %s%s%s%s%s%s%s '%s' (qui est %s)\n" .
    
"     déclaré dans %s\n" .
    
"     lignes %d à %d\n" .
    
"     a les modificateurs %d[%s]\n" ,
    
$method -> isInternal () ?  'internal'  'user-defined' ,
    
$method -> isAbstract () ?  ' abstract'  '' ,
    
$method -> isFinal () ?  ' final'  '' ,
    
$method -> isPublic () ?  ' public'  '' ,
    
$method -> isPrivate () ?  ' private'  '' ,
    
$method -> isProtected () ?  ' protected'  '' ,
    
$method -> isStatic () ?  ' static'  '' ,
    
$method -> getName (),
    
$method -> isConstructor () ?  'the constructor'  'a regular method' ,
    
$method -> getFileName (),
    
$method -> getStartLine (),
    
$method -> getEndline (),
    
$method -> getModifiers (),
    
implode ( ' ' Reflection :: getModifierNames ( $method -> getModifiers ()))
  );

// Affichage du commentaire de la documentation
printf ( "---> Documentation:\n %s\n" var_export ( $method -> getDocComment (),  1 ));

// Affichage des variables statiques si elles existent
if ( $statics $method -> getStaticVariables ()) {
    
printf ( "---> Variales statiques : %s\n" var_export ( $statics 1 ));
}

// Invocation de la méthode
printf ( "---> Résultat de l'invocation dans : " );
var_dump ( $method -> invoke ( NULL ));
?>

Note: Invoquer des méthodes privées, protégées ou abstraites provoquera une exception jetée par la méthode invoke() .

Note: Pour les méthodes statiques comme vu précédemment, vous devez passer NULL comme premier argument à la fonction invoke() . Pour les méthodes non-statiques, passez une instance de la classe.

ReflectionProperty

La classe ReflectionProperty vous permet de faire du reverse-engineering sur les propriétés des classes.

Exemple 19.43. La classe ReflectionProperty

<?php
class  ReflectionProperty  implements  Reflector
{
    final private 
__clone ()
    public 
__construct ( mixed  class,  string name )
    public 
string __toString ()
    public static 
string export ( mixed  class,  string name bool  return)
    public 
string getName ()
    public 
bool isPublic ()
    public 
bool isPrivate ()
    public 
bool isProtected ()
    public 
bool isStatic ()
    public 
bool isDefault ()
    public 
int getModifiers ()
    public 
mixed getValue ( stdclass object )
    public 
void setValue ( stdclass object mixed value )
    public 
ReflectionClass getDeclaringClass ()
    public 
string getDocComment ()
}
?>

Note: getDocComment() a été ajouté en PHP 5.1.0.

Pour connaître le fonctionnement d'une propriété, vous devez d'abord créer une instance de la classe ReflectionProperty . Vous pourrez ainsi appeler n'importe quelle méthode de cette instance.

Exemple 19.44.  Utilisation de la classe ReflectionProperty

<?php
class  Chaine
{
    public 
$length   5 ;
}

// Création d'une instance de la classe ReflectionProperty
$prop  = new  ReflectionProperty ( 'Chaine' 'length' );

// Affichage d'informations basiques
printf (
    
"===> Les propriétés %s%s%s%s '%s' (qui a %s)\n" .
    
"     a les modificateurs %s\n" ,
    
$prop -> isPublic () ?  ' public'  '' ,
    
$prop -> isPrivate () ?  ' private'  '' ,
    
$prop -> isProtected () ?  ' protected'  '' ,
    
$prop -> isStatic () ?  ' static'  '' ,
    
$prop -> getName (),
    
$prop -> isDefault () ?  'déclaré au moment de la compilation'  'créé au moment de l\'exécution' ,
    
var_export ( Reflection :: getModifierNames ( $prop -> getModifiers ()),  1 )
);

// Création d'une instance de Chaine
$obj = new  Chaine ();

// Récupération de la valeur courante
printf ( "---> La veleur est : " );
var_dump ( $prop -> getValue ( $obj ));

// Modification de la valeur
$prop -> setValue ( $obj 10 );
printf ( "---> Définition de la valeur à 10, la nouvelle valeur est : " );
var_dump ( $prop -> getValue ( $obj ));

// Affichage de l'objet
var_dump ( $obj );
?>

Note: Essayer de récupérer ou de définir les valeurs des propriétés d'une classe privée ou protégée produira une exception.

ReflectionExtension

La classe ReflectionExtension vous permet de faire du reverse-engineering sur les extensions. Vous pouvez connaître toutes les extensions chargées à l'exécution en utilisation la fonction get_loaded_extensions() .

Exemple 19.45. La classe ReflectionExtension

<?php
class  ReflectionExtension  implements  Reflector
{
    final private 
__clone ()
    public 
__construct ( string name )
    public 
string __toString ()
    public static 
string export ( string name bool  return)
    public 
string getName ()
    public 
string getVersion ()
    public 
ReflectionFunction []  getFunctions ()
    public array 
getConstants ()
    public array 
getINIEntries ()
    public 
ReflectionClass []  getClasses ()
    public array 
getClassNames ()
}
?>

Pour connaître le fonctionnement d'une extension, vous devez d'abord créer une instance de la classe ReflectionExtension . Vous pourrez ainsi appeler n'importe quelle méthode sur cette instance.

Exemple 19.46. Utilisation de la classe ReflectionExtension

<?php
// Création d'une instance de la classe ReflectionProperty
$ext  = new  ReflectionExtension ( 'standard' );

// Affichage d'informations basiques
printf (
    
"Nom        : %s\n" .
    
"Version     : %s\n" .
    
"Fonctions   : [%d] %s\n" .
    
"Entrées INI : [%d] %s\n"  .
    
"Classes     : [%d] %s\n" ,
        
$ext -> getName (),
        
$ext -> getVersion () ?  $ext -> getVersion () :  'NO_VERSION' ,
        
sizeof ( $ext -> getFunctions ()),
        
var_export ( $ext -> getFunctions (),  1 ),

        
sizeof ( $ext -> getConstants ()),
        
var_export ( $ext -> getConstants (),  1 ),

        
sizeof ( $ext -> getINIEntries ()),
        
var_export ( $ext -> getINIEntries (),  1 ),

        
sizeof ( $ext -> getClassNames ()),
        
var_export ( $ext -> getClassNames (),  1 )
);
?>

Extension des classes de réflexion

Dans le cas où vous voudriez créer des versions spéciales des classes embarquées (par exemple pour créer du HTML colorisé lorsqu'il est exporté, pour avoir un accès facile aux variables des membres au lieu des méthodes ou pour avoir des méthodes utiles), vous devez étendre la classe.

Exemple 19.47. Extension des classes embarquées

<?php
/**
 * Ma classe Reflection_Method
 *
 */
class  My_Reflection_Method  extends  ReflectionMethod
{
  public 
$visibility '' ;

  public function 
__construct ( $o $m )
  {
    
parent :: __construct ( $o $m );
    
$this -> visibility Reflection :: getModifierNames ( $this -> getModifiers ());
  }
}

/**
 * Démo classe #1
 *
 */
class  {
  protected function 
x () {}
}

/**
 * Démo classe #2
 *
 */
class  extends  {
  function 
x () {}
}

// Affichage des informations
var_dump (new  My_Reflection_Method ( 'U' 'x' ));
?>

Note: Attention : si vous écrasez le constructeur, n'oubliez pas d'appeler le constructeur parent avant d'insérer le moindre code. Sinon, votre code produira l'erreur suivante : Fatal error: Internal error: Failed to retrieve the reflection object