Retrouvez cet article dans : Linux Magazine Hors série 20
A ses débuts, PHP n’était pas un langage orienté objet. Petit à petit, les fonctionnalités et concepts de l’objet ont été ajoutés
depuis la version 4. Mais, dans cette version de PHP, il manquait des concepts comme les destructeurs, par exemple. PHP5
va plus loin dans l’objet. Certes, il ne faut pas s’attendre à un langage objet comme Java, mais les concepts fondamentaux
de l’objet sont implémentés et permettent de faire du code réutilisable et propre.
PHP5 repose désormais sur le moteur Zend2. Zend2
propose un modèle objet complètement revu qui permet
à PHP5 de fournir de nouvelles fonctionnalités comme :
- Constructeurs et destructeurs qui vous permettent de créer des instances de classe (des objets);
- Méthodes privées et visibilité qui vous permettent de définir des méthodes qui ne peuvent être utilisées que par des méthodes de la même classe;
- Gestion des exceptions qui vous permette de faire une gestion fine des erreurs et de les propager;
- Clonage et comparaison d’objets pour manipuler facilement les objets deux à deux.
On retrouve toujours les concepts essentiels de l’objet qui étaient déjà disponibles dans PHP4 :
- Héritage qui vous permet de spécialiser un objet (au niveau attribut ou méthode) tout en pouvant accéder aux attributs et méthodes de la super classe;
- Polymorphisme qui vous permet de surcharger une méthode pour l’utiliser dans des paramètres différents;
- Encapsulation qui vous permet de masquer l’implémentation d’un objet.
Concepts objets en PHP
Mais tout d’abord, commençons par quelques rappels (ou introduction) sur les termes utilisés et comment les utiliser en PHP4.
Classes, objets, héritage et polymorphisme
Pour créer des objets, il vous faut utiliser une classe. Les débutants confondent souvent classes et objets. Un objet est une instance d’une classe.Ainsi, pour une classe donnée, vous pouvez créer un nombre important d’objets différents.
Pour schématiser, une classe est une template : c’est une usine à objets.
Les classes sont hiérarchisées.Au sommet de la hiérarchie, on trouve une classe générique dont toutes les autres classes héritent. C’est la classe stdClass. Cette hiérarchisation s’appelle l’héritage. Une classe héritant de sa classe mère hérite de ce fait de l’ensemble des attributs et méthodes. Par exemple, on peut avoir une classe développeur et deux classes héritantes : PHP et Java. On peut schématiser ça avec UML :

Les deux sous-classes héritent des attributs nom et prénom qui sont généralisés à l’ensemble des développeurs (qu’ils soient développeurs PHP ou Java) ainsi que des méthodes associées. Ensuite, on spécialise les classes en ajoutant les attributs spécifiques. Par exemple, pour un développeur Java, on ajoute la version de la JVM, alors que pour un développeur PHP, on va plutôt s’attacher à la version de PHP et de Apache. L’héritage est un concept essentiel à tout langage objet car il permet la réutilisation facile de code.
Voilà comment définir une classe en PHP :
class developpeur {
var $nom;
var $prenom;
function getNom() { ... }
function setNom($nom) { ... }
}
Nous pouvons maintenant instancier un objet à l’aide de cette classe :
$toto = new developpeur;
Pour accéder aux attributs ou méthodes d’un objet, il faut utiliser l’indirection -> :
echo $toto->nom; echo $toto->getNom();
Lorsque vous codez une méthode, il est fréquent de faire appel à d’autres méthodes de la classe ou à des attributs. Mais comment faire puisqu’on ne sait pas encore le nom de l’objet instancié. La variable $this est utilisée à l’intérieur d’une classe pour désigner l’objet courant :
class developpeur {
var $salaire;
...
function augmentation($montant) {
$this->salaire = $this->salaire + $montant;
}
}
Vous pouvez définir des attributs statiques dans une classe :
class developpeur {
var $used_os = “Linux”;
...
}
Il existe un type de méthode particulier : les constructeurs et les destructeurs. Un constructeur est une méthode appelée lors de l’instanciation d’un objet. Il vous permet de définir des attributs ou de faire des actions données. De la même manière, un destructeur est appelé au moment de la destruction d’un objet. Il vous permet de supprimer des variables ou de fermer des ressources externes (des fichiers par exemple) :
class developpeur {
var $nom;
var $prenom;
...
function developpeur($nom, $prenom) {
$this->nom = $nom;
$this->prenom = $prenom;
}
}
$toto = new developpeur(‘toto’,’monsieur’);
Il est également possible de faire des méthodes statiques. Une méthode statique ne s’applique pas à un objet, mais est générique à la classe.Ainsi, vous pouvez l’appeler sans instancier la classe :
class conversion {
var $taux = 6.55957;
function euro2franc($montant) {
return ($montant * $taux);
}
}
$salaire = 2000;
$salaire_france = conversion::euro2franc($salaire);
Pour faire hériter une classe, il faut utiliser le mot réservé extends. Nous arrivons là à une deuxième propriété de l’objet fort intéressante : le polymorphisme. Le polymorphisme est le fait de pouvoir utiliser une même méthode sur des objets complètement différents et sans nécessairement connaître de quel type d’objet il s’agit. Par exemple, considérons cette hiérarchie d’objets :
class db {
var $host;
var $port;
var $db;
...
}
class mysqldb extends db {
function connect() { ... }
function query() { ... }
function close() { ... }
}
class pgdb extends db {
function connect() { ... }
function query() { ... }
function close() { ... }
}
Nous avons une classe mère qui décrit les attributs nécessaires à l’ensemble des bases de données (le nom du serveur de base, le port et le nom de la base sur laquelle se connecter). Nous définissons deux classes héritantes : une spécialisant la connexion à MySQL et l’autre à PostgreSQL.
On remarque que les deux classes implémentent les mêmes méthodes. On va donc pouvoir appeler, de la même manière la méthode connect, que l’on soit sur MySQL ou PostgreSQL. Il n’est même pas nécessaire de savoir si on fait l’appel sur mysqldb ou pgdb, l’appel est le même (c’est l’implémentation qui est masquée). On appelle également cela la surcharge de méthode.
Sérialisation et désérialisation
La sérialisation vous permet d’obtenir une représentation en bytes d’un objet donné. Cette représentation peut être alors stockée dans un fichier, par exemple. Cette fonctionnalité est particulièrement utile pour les données persistantes : par exemple, la sauvegarde et la restauration des sessions. Pour le développeur, la mise en place de ce mécanisme consiste uniquement à implémenter les méthodes serialize() et unserialize() :
class developpeur {
var $nom;
var $prenom;
function serialize() { ... }
function unserialize() { ... }
}
Les sessions en PHP utilisent déjà la sérialisation.Vous n’avez pas besoin de la gérer. C’est pour cela que lorsque vous voulez utiliser une session dans une page, il faut faire appel à la fonction session_start() qui désérialise la session. PHP propose des fonctions qui sont appelées durant le processus de sérialisation/désérialisation : __sleep() et __wakeup(). Ces méthodes sont utilisées pour modifier un objet qui va être sérialisé/désérialisé. Ces méthodes ne sont pas nécessaires pour sérialiser/désérialiser, mais dans ce cas, l’objet n’est absolument pas modifié durant le processus.
La méthode __sleep() est appelée sur un objet juste avant sa sérialisation. Elle peut réaliser du nettoyage pour préserver un état cohérent, comme par exemple fermer des connexions à une base de données. Elle retourne un tableau avec le nom des attributs à sauvegarder sous forme de bytes. Si le tableau est vide, rien n’est sauvegardé. De la même manière, __wakeup() est une méthode appelée immédiatement après que l’objet soit créé à partir d’un flux de bytes (désérialisation). Cette méthode a en charge la remise de l’objet dans un état cohérent, comme par exemple la restauration des connexions aux bases de données.
Considérons par exemple une classe qui trace les pages d’où viennent les utilisateurs dans un fichier plat :
<?
class RefererTracker {
var $flatfile;
var $flatfile_pointer;
function RefererTracker($flatfile) {
$this->flatfile = $flatfile;
$this->open();
}
function open() {
$this->flatfile_pointer = fopen($this->flatfile,”a”)
or die(“Ne peut ouvrir “ . $this->flatfile);
}
function track() {
fwrite($this->flatfile_pointer, $REFERER);
}
function __wakeup() {
$this->open();
}
function __sleep() {
fclose($this->flatfile_pointer);
return array(‘flatfile’);
}
}
?>
Les nouveautés objets de PHP5 Constructeurs et destructeurs
Nous avons vu en introduction comment définir les constructeurs (avec le style PHP4). En PHP5, les développeurs ont décidé d’unifier la déclaration des constructeurs en utilisant un nom de méthode particulier : __construct.Toutefois, si l’interpréteur PHP ne trouve pas cette méthode, il cherche une déclaration du constructeur avec la définition PHP4 (le nom de la classe). Attention toutefois, les constructeurs des classes parentes ne sont pas automatiquement appelés, pour les utiliser, il vous faut utiliser parent::__construct().
En PHP4, pour détruire un objet, il fallait faire un " classique " :
$toto = new developper(‘toto’,’titi’); ... unsert($toto);
Cette solution présente l’inconvénient de ne pas pouvoir faire de traitements automatiques au moment de la destruction. Par exemple, si vous ouvrez un fichier dans le constructeur, il est impératif que vous fermiez le fichier avant de faire le unset. De plus, il n’est pas possible de réellement contrôler le cycle de vie d’un objet. PHP5 introduit la notion de destructeur. Un destructeur est une méthode spéciale qui est appelée juste avant la destruction de l’objet.Ainsi, vous pouvez faire vos traitements spéciaux comme fermer un fichier par exemple.
Un destructeur porte le nom spécial __destruct().
Voilà comment utiliser les constructeurs et destructeurs en PHP5 :
<?
class developper {
function __construct() {
print “Nouveau developpeur”;
$this->name = “Developper”;
}
function __destruct() {
print “Destruction de “.$this->name.”\n”;
}
}
class developperphp extends developper {
function __construct() {
parent::__construct();
print “Nouveau développeur PHP”;
}
}
?>
Visibilité
PHP5 a désormais inclus la notion de visibilité que ce soit pour les attributs ou les méthodes. Il y a trois niveaux de visibilité : public, protected, private.
- Déclaré en
public, l’attribut ou la méthode peut être accédé(e) par n’importe quelle classe et de n’importe où. - Déclaré en
protected, l’attribut ou la méthode peut être accédé(e) uniquement par les classes descendantes
(c’est-à-dire les classes héritant de la classe dans laquelle l’attribut ou la méthode est définie).
- Déclaré en
private, l’attribut ou la méthode peut être accédé(e) uniquement dans la classe de définition.
Attention : désormais, la déclaration d’un attribut dans une classe ne nécessite plus l’utilisation du mot réservé var. Dorénavant, il suffit d’utiliser la déclaration de la visibilité suivie du nom de la variable.
Voilà quelques exemples de déclaration de visibilité
<?
class developper {
public $nom;
private $prenom;
protected function hello() {
print “Bonjour “ . $this->prenom;
}
}
class phpdevelopper extends developper {
private $version;
public hello() {
developper::hello();
print “PHP version : “ . $this->$version;
print “Nom : “ . $this->nom;
// par contre, on ne peut pas accèder
// à l’attribut prenom :
// print “Prenom : “ . $this->prenom;
// Fatal Error
}
}
$developper = new developper();
$developper->nom = “Toto”;
// $developper->prenom = “Titi”;
// Fatal Error
// $developper->hello();
// Fatal Error
$phpdevelopper = new phpdevelopper();
$phpdevelopper->nom = “Toto”;
// $phpdevelopper->prenom = “Titi”;
// Fatal Error
// $phpdevelopper->$version = “5.0”;
// Fatal Error
$phpdevelopper->hello();
?>
Statiques et constantes
L’utilisation du double colon :: vous permet d’accéder à des éléments statiques ou des constantes. Désormais, il est possible de définir des constantes dans une classe :
<?
class developper {
const WEB_LANGUAGE = “php”;
}
echo developper::WEB_LANGUAGE;
?>
Vous pouvez définir des méthodes statiques (appelées aussi méthodes de classes) avec sa visibilité :
<?
class developper {
const WEB_LANGUAGE = “php”;
}
class phpdevelopper extends developper {
public static $current_version = “5.0”;
public static function display_info() {
echo parent::WEB_LANGUAGE . “ “ .
self::$current_version . “\n”;
}
}
phpdevelopper::display_info();
?>
Attention, lorsque vous définissez une méthode qui ne surcharge (qui a donc le même nom) qu’une méthode de la classe mère, PHP n’appelle QUE la méthode de la classe descendante. Si vous voulez tout de même appeler la méthode de la classe mère, il vous faut utiliser parent::method_name().
Vous avez sans doute remarqué l’utilisation du mot clef static. Un attribut ou une méthode déclaré(e) statique le rend accessible en dehors d’un contexte objet.
Il n’est pas nécessaire d’avoir une instance de la classe (un objet) pour appeler l’attribut ou la méthode. On appelle aussi ces attributs ou méthodes des attributs de classe et des méthodes de classe. Comme l’élément est statique, il n’est pas possible d’y accéder dans une sous-classe sans le préfixer de la classe mère et bien sûr, il n’est pas possible d’utiliser $this dans une méthode statique (puisque cette dernière n’a pas d’objet associé). Pour bien comprendre la différence entre attribut de classe et attribut d’objet, voilà comment il est possible de compter le nombre d’instances d’une classe :
<?
class developper {
public static $number_of_objects = 0;
private $nom;
public __construct($nom) {
$this->nom = $nom;
developper::$number_of_objects =
evelopper::$number_of_objects + 1;
}
public static function DisplayObjectsNumber() {
echo developper::$number_of_objects;
}
}
class phpdevelopper extends developper {
private $version;
public __construct($nom, $version) {
parent::__construct($nom);
$this->version = $version;
}
}
$developper = new developper(“Toto”);
developper::DisplayObjectsNumber();
// affiche 1
$phpdevelopper = new phpdevelopper(“Titi”, “5.0”);
developper::DisplayObjectsNumber();
// affiche 2
$developper2 = new developper(“Tata”);
developper::DisplayObjectsNumber();
// affiche 3
?>
Classe abstraite et méthode abstraite
PHP5 introduit la notion de classes et méthodes abstraites.
Une méthode abstraite ne peut pas être instanciée, cela signifie donc qu’il n’est pas possible d’avoir un objet d’une classe abstraite. Il est donc nécessaire de créer une classe héritant de la classe abstraite. De même, lorsque vous déclarez une méthode abstraite, vous ne faites que définir la signature de la méthode et non l’implémentation.
L’implémentation de la méthode sera faite dans une sousclasse.
Mais à quoi cela sert ? Avec l’abstraction, vous définissez des comportements par défaut que vous retrouverez dans toutes les sous-classes de la classe abstraite. Ainsi, sans connaître l’implémentation sous-jacente, vous pourrez utiliser les méthodes abstraites. Dès qu’une classe possède une méthode abstraite, la classe elle-même est abstraite. Une méthode abstraite ne peut être définie qu’en public ou protected. En effet, si on pouvait la définir en private, on ne pourrait pas la définir dans une sous-classe, et donc cela ne servirait à rien.
Voilà comment utiliser l’abstraction :
<?
abstract class abstractdevelopper {
abstract protected function getVersion();
public function displayVersion() {
print $this->getVersion();
}
}
class phpdevelopper extends abstractdevelopper {
protected function getVersion() {
return “PHP Version 5.0”;
}
}
class javadevelopper extends abstractdevelopper {
protected function getVersion() {
return “J2SDK 1.4.2”;
}
}
$dev1 = new phpdevelopper;
$dev1->displayVersion();
$dev2 = new javadevelopper;
$dev2->displayVersion();
?>
Interfaces
Une interface ne fait que définir des attributs et des signatures de méthodes. Une classe implémente ensuite les méthodes définies dans l’interface. Une interface permet de définir les comportements communs à un ensemble d’objets qui n’ont rien à voir entre eux :
<?
interface IDevelopper {
public function setNom($nom);
public function setVersion($version);
}
class phpdevelopper implements IDevelopper {
public $nom;
public $version;
public function setNom($nom) {
$this->nom = $nom;
}
public function setVersion($version) {
$this->version = version;
}
}
?>
Itération
Il existe une interface Iterator qui vous oblige à surcharger certaines méthodes. Grâce à ces méthodes, il est très facile d’itérer dans les attributs de l’objet (NDLR : l’itération permet d’utiliser foreach avec des objets) :
<?
class ArrayCollector implements Iterator {
private $collector = array();
public function __construct($array) {
if(is_array($array)) {
$this->collector = $array;
}
}
public function rewind() {
reset($this->collector);
}
public function current() {
$collector = current($this->collector);
return $collector;
}
public function key() {
$collector = key($this->collector);
return $collector;
}
public function next() {
$collector = next($this->collector);
return $collector;
}
public function valid() {
$collector = $this->current() !== false;
return $collector;
}
}
$values = array(1,2,3);
$iterator = new ArrayCollector($values);
foreach($iterator as $a => $b) {
print “$a : $b\n”;
}
?>
Final
Le mot clef final vous permet de définir des attributs ou des méthodes qui ne peuvent pas être surchargé(e)s dans les sous-classes:
<?
class developper {
public function test() {
echo “test called\n”;
}
final public function tested() {
echo “tested called\n”;
}
}
class phpdevelopper extends developper {
public function tested() {
echo “tested called\n”;
}
}
// Fatal Error : Cannot override final method
// developper::tested()
?>
Clonage et comparaison d’objets
Cloner un objet ne signifie pas uniquement recopier les valeurs des attributs. Prenons par exemple un objet dont un des attributs est lui-même un objet. Si vous faites une simple affectation, le nouvel objet va avoir une référence sur le même objet attribut. Le clonage consiste à créer un objet indépendant, y compris pour les objets attributs.
Un objet cloné s’obtient en utilisant le mot réservé clone qui va appeler la méthode __clone(). Il n’est pas possible d’appeler directement la méthode __clone().
<?
class developper {
public $nom;
public function __construct($nom) {
$this->nom = $nom;
}
public function __clone() {
$this->nom = $nom;
}
}
class developperroom {
public $developper;
public function __clone() {
$this->developper =
clone($this->developper);
}
}
$room = new developperroom;
$room->developper = new developper(“Toto”);
$room2 = clone $room;
?>
Assez similairement, vous devez préciser comment vous comparez deux objets, surtout si l’objet est composé d’objets attributs. La comparaison d’objets est plus compliquée en PHP5 qu’en PHP4.
Lorsque vous utilisez l’opérateur de comparaison (==), les objets sont comparés de la façon suivante : deux objets sont égaux s’ils ont les mêmes attributs, si ces attributs ont la même valeur et si les deux objets sont de la même classe.
D’un autre côté, lorsque vous utilisez l’opérateur d’identité (===), les objets sont identiques si et seulement si ils référencent la même instance de classe.
Prenons un exemple :
<?
class developper {
public $nom;
public __construct($nom) {
$this->nom = $nom;
}
}
$dev1 = new developper(“foo”);
$dev2 = $dev1;
$dev3 = new developper(“foo”);
// $dev1 == $dev3 est vrai
// mais $dev1 === $dev3 est faux
// $dev1 == $dev2 est vrai
/ et $dev1 === $dev2 est vrai
?>
Exception
PHP5 propose une gestion des exceptions plus fine.Vous avez la possibilité de créer vos propres exceptions en héritant de la classe Exception :
<?
class developperexception extends Exception {
public $message;
public __construct($message) {
parent::__construct();
$this->message = $message;
}
public getMessage() {
return $this->message;
}
}
?>
Ensuite, vous pouvez récupérer les exceptions et les traiter. Prenons l’exemple de la gestion d’exception dans l’utilisation des méthodes SOAP :
<?
try {
$client = new SoapClient(“some.wsdl”);
$result = $client->SomeFunction(...);
}
catch(SoapFault $fault) {
trigger_error(“SOAP Fault : (faultcode: {
fault->faultcode},
faultstring: {$fault->faultstring})”,
_ERROR);
}
?>
Quand PHP rencontre Java
Il y a deux manières d’intégrer PHP et Java.Vous pouvez intégrer PHP dans un contexte de Servlet. Pour cela, il vous faut compiler PHP avec le support de Java (avec l’option —with-servlet[=DIR]).
Vous pouvez utiliser les extensions Java. Dans ce cas, la JVM est créée en utilisant JNI et vous pouvez appeler des méthodes sur les objets Java directement en PHP.
Le seul pré-requis est d’avoir une JVM installée sur le serveur. Vous pouvez configurer diverses options Java dans le fichier php.ini, comme par exemple le classpath (avec l’option java.class.path) ou le Java Home (avec l’option java.home).
Ainsi, vous pouvez créer des objets Java directement en PHP et les utiliser directement :
<? // on crée un objet system, instance de // la classe Java java.lang.System $system = new Java(‘java.lang.System’); // on accéde à différents attributs de // l’objet java system echo “Java version : “ . $system->getProperty(‘java.version’) . “<br/>\n”; echo “Java vendor : “ . $system->getProperty(‘java.vendor’) . “<br/>\n”; // on va créer un objet formatter, instance de // la classe Java java.text.SimpleDateFormat // on peut remarquer le passage de paramètres // au constructeur de la classe Java $formatter = new Java(‘java.text.SimpleDateFormat’, “EEEE, MMMM dd, yyyy ‘at’ h:mm:ss a zzzz”); // on utilise l’objet Java formatter pour afficher // un objet Java de la classe java.util.Date // pour cela, on utilise la méthode format de // l’objet Java formatter echo $formatter->format(new Java(‘java.util.Date’)); ?>
Conclusion
PHP5 a introduit de nouvelles fonctionnalités objets qui le rendent plus proche d’un véritable langage orienté objet comme Java ou Eiffel.
Certes, ce n’est pas encore Java, surtout au niveau design que l’on peut mettre en oeuvre, mais on peut déjà réaliser du code plus propre et plus réutilisable.
De plus, l’intégration facile de Java directement dans PHP donne un niveau d’interopérabilité plus accru et ouvre le chemin vers des applications Web très évoluées. Avec le moteur Zend 2, il serait dommage de ne pas utiliser la programmation orientée objet de PHP5.
Retrouvez cet article dans : Linux Magazine Hors série 20

