IV. Programmation Orientée Objet (POO)▲
IV-A. Modèle objet▲
IV-A-1. Bref historique▲
La programmation orientée objet (POO) a fait son apparition dans la version 3 de PHP. C'était alors simplement un moyen d'autoriser la syntaxe OO (par opposition au procédural), mais pas réellement un moyen de programmer efficacement avec des objets.
PHP4 a continué dans la lancée, proposant de nouveaux mots clefs, mais toujours sans proposer une syntaxe proche de langages ayant une plus grande maturité comme C++ ou Java. C'est ce qui a facilité le passage des applications et des hébergements de PHP3 à PHP4, et c'est ce qui retarde la mise en place massive de PHP5 à travers le Web.
PHP5, en revanche, introduit de véritables concepts OO : le constructeur est plus clairement identifié, le destructeur fait son apparition, les objets sont tous pris en charge comme des références, de nouveaux mots clefs font leur apparition (public, protected et private) ainsi que des interfaces et des classes abstraites...
IV-A-2. Terminologie▲
Une classe est une représentation abstraite d'un objet. C'est la somme des propriétés de l'objet et des traitements dont il est capable. Une Chaise, un Livre, un Humain, une Rivière sont autant d'exemples possibles de classes. Une classe peut généralement être rendu concrète au moyen d'une instance de classe, que l'on appelle objet : on parle d'instancier la classe. Un objet est donc un exemple concret de la définition abstraite qu'est la classe.
On parle d'héritage lorsque l'on définit une hiérarchie de classes. On peut par exemple définir la classe Chien mais elle nous semble trop abstraite : si j'instancie Chien par un objet $médor, je ne sais pas de quelle race est mon chien. Je pourrais avoir une propriété "race" dans la classe Chien, mais la solution de l'héritage me donne plus de souplesse : elle me permet de redéfinir des propriétés et des méthodes de la classe Chien, tout en héritant de celles que je ne modifie pas.
IV-A-3. Les mots réservés▲
- class : Déclaration de classe ;
- const : Déclaration de constante de classe ;
- function : Déclaration d'une méthode ;
- public/protected/private : Accès (par défaut "public" si aucun accès n'est explicitement défini) ;
- new : Création d'objet ;
- self : Résolution de portée (la classe elle-même) ;
- parent : Résolution de portée (la classe "parent") ;
- static : Résolution de portée (appel statique) disponible depuis PHP 5.3 et 6.0 ;
- extends : Héritage de classe ;
- implements : Implémentation d'une interface (dont il faut redéclarer toutes les méthodes).
Les mots clefs "self" et "parent" sont utiles pour accéder à une propriété ou méthode (statique ou non) de la classe elle-même ou de son parent.
Le mot clef "static" a la même utilité mais il résoud la portée au moment de l'exécution du script (cf. plus loin).
Les méthodes magiques sont des méthodes qui, si elles sont déclarées dans une classe, ont une fonction déjà prévue par le langage.
- __construct() : Constructeur de la classe ;
- __destruct() : Destructeur de la classe ;
- __set() : Déclenchée lors de l'accès en écriture à une propriété de l'objet ;
- __get() : Déclenchée lors de l'accès en lecture à une propriété de l'objet ;
- __call() : Déclenchée lors de l'appel d'une méthode inexistante de la classe (appel non statique) ;
- __callstatic() : Déclenchée lors de l'appel d'une méthode inexistante de la classe (appel statique) : disponible depuis PHP 5.3 et 6.0 ;
- __isset() : Déclenchée si on applique isset() à une propriété de l'objet ;
- __unset() : Déclenchée si on applique unset() à une propriété de l'objet ;
- __sleep() : Exécutée si la fonction serialize() est appliquée à l'objet ;
- __wakeup() : Exécutée si la fonction unserialize() est appliquée à l'objet ;
- __toString() : Appelée lorsque l'on essaie d'afficher directement l'objet : echo $object; ;
- __set_state() : Méthode statique lancée lorsque l'on applique la fonction var_export() à l'objet ;
- __clone() : Appelés lorsque l'on essaie de cloner l'objet ;
- __autoload() : Cette fonction n'est pas une méthode, elle est déclarée dans le scope global et permet d'automatiser les "include/require" de classes PHP.
Enfin, la variable $this utilisée à l'intérieur d'une classe, vaut toujours une référence vers l'objet lui-même. Pour plus d'informations sur les références, cf. plus loin à la section "Références et clônage".
IV-A-4. Syntaxe▲
Je ne prétends pas refaire un cours complet sur la POO, car il y a d'excellents cours sur le Net et dans de nombreux livres. Je vais toutefois survoler le sujet afin de rappeler les aspects en rapport avec PHP.
La POO représente la programmation par objets. Un objet est la concrétisation d'une classe, une classe étant un ensemble générique (par opposition à concret) de propriétés et de fonctionnalités. On parle d'instancier une classe pour en faire un objet.
Une classe s'écrit au moyen du mot "class" suivi du nom de la classe et d'accolades (pas de point virgule). Les conventions d'écriture de code recommandent d'écrire ce mot comme un nom propre, à savoir avec une seule majuscule. Certaines recommandations (PEAR, Zend Framework etc.) préconisent d'utiliser l'underscore pour identifier l'héritage :
<?php
class
Animal{}
class
Animal_Chien extends
Animal{}
class
Animal_Chien_Caniche extends
Animal_Chien{}
class
Animal_Chien_Labrador extends
Animal_Chien{}
La création d'une instance de classe se fait au moyen du mot clef "new". L'accès aux propriétés et méthodes de l'objet se fait par la flèche "->", et l'accès statique par l'opérateur de résolution de portée "::". Le point n'est pas utilisé dans la POO de PHP.
IV-A-5. Droits d'accès▲
Les classes ont des propriétés et méthodes privées, c'est-à-dire internes et qui ne concernent pas l'extérieur. Ces propriétés sont déclarées en tant que "private".
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<?php
class
Caniche
{
private
$nbPattes
;
public
function
__construct
()
{
$this
->
nbPattes =
4
;
}
}
$froufrou
=
new
Caniche();
echo $froufrou
->
nbPattes;
//illégal depuis l'extérieur de la classe
Fatal error: Cannot access private property Caniche::$nbPattes
in C:\Web\online\http\tests\error.php on line 8
<?php
class
Caniche
{
private
$nbPattes
;
public
function
__construct
()
{
$this
->
nbPattes =
4
;
}
public
function
nbPattes() //voici ce que l'on appelle un "getter"
{
return
$this
->
nbPattes;
//ok depuis l'intérieur de la classe
}
}
$froufrou
=
new
Caniche();
echo $froufrou
->
nbPattes();
//affiche "4"
Les propriétés ou méthodes "protected" concernent les objets de la même classe ainsi que ses dérivées, mais pas ceux des classes étrangères.
<?php
class
Chien
{
protected
$presenceQueue
;
public
function
__construct
()
{
$this
->
presenceQueue =
TRUE
;
}
}
class
Chien_Labrador extends
Chien
{
public
function
aUneQueue()
{
if
($this
->
presenceQueue) //ok depuis une classe fille
{
return
'oui'
;
}
else
{
return
'non'
;
}
}
}
$médor
=
new
Chien_Labrador();
echo $médor
->
aUneQueue();
//affiche "oui"
Les propriétés ou méthodes "public" sont visibles et manipulables par tous les objets, même s'ils sont d'autres classes.
<?php
class
Caniche
{
public
$âge
=
2
;
public
function
__construct
()
{
$this
->
âge =
2
;
}
}
$froufrou
=
new
Caniche();
echo $froufrou
->
âge;
//affiche "2"
IV-A-6. Résolution de portée▲
Les mots clefs "parent" et "self" combinés à l'opérateur "::" résolvent respectivement la classe dont ils héritent et leur propre classe :
<?php
class
Chien
{
protected
function
aboyer()
{
return
'Je suis un chien'
;
}
}
class
Chien_Labrador extends
Chien
{
protected
function
aboyer()
{
return
'Je suis un labrador'
;
}
public
function
identifierParent()
{
return
parent
::
aboyer();
}
public
function
identifierSelf()
{
return
self
::
aboyer();
}
}
$médor
=
new
Chien_Labrador();
echo $médor
->
identifierParent().
'<br/>'
;
echo $médor
->
identifierSelf().
'<br/>'
;
Je suis un chien
Je suis un labrador
Les variables et méthodes "static" sont communes à l'ensemble des objets d'une même classe au moyen de l'opérateur "::". On parle alors de propriétés ou de méthodes "de classe" puisqu'elles n'appartiennent pas à un objet en particulier.
<?php
class
Caniche
{
public
static
$caniches
=
0
;
public
function
__construct
()
{
++
self
::
$caniches
;
}
}
$froufrou
=
new
Caniche();
$froufrette
=
new
Caniche();
echo Caniche::
$caniches
;
//affiche "2"
L'accès "public/protected/private" s'applique de la même manière que pour les accès non statiques :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
<?php
class
Caniche
{
protected
static
$caniches
=
0
;
public
function
__construct
()
{
++
self
::
$caniches
;
}
}
$froufrou
=
new
Caniche();
$froufrette
=
new
Caniche();
echo Caniche::
$caniches
;
Fatal error: Cannot access protected property Caniche::$caniches
in C:\Web\online\http\tests\error.php on line 14
IV-A-7. Interfaces▲
Une interface est un ensemble de méthodes que les classes doivent définir si elles veulent l'implémenter.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
<?php
interface
Joueur
{
// une classe qui veut implémenter l'interface Joueur
// doit définir la méthode jouer()
public
function
jouer();
}
class
Labrador implements
Joueur{}
//illégal car pas de définition de jouer()
$médor
=
new
Labrador();
$médor
->
jouer();
Fatal error: Class Labrador contains 1 abstract method
and must therefore be declared abstract or implement the remaining methods
(Joueur::jouer) in C:\Web\online\http\tests\error.php on line 7
<?php
interface
Joueur
{
// une classe qui veut implémenter l'interface Joueur
// doit définir la méthode jouer()
public
function
jouer();
}
class
Labrador implements
Joueur
{
public
function
jouer()
{
echo 'Ouah!'
;
}
}
$médor
=
new
Labrador();
$médor
->
jouer();
De nombreuses interfaces intéressantes sont définies dans la Standard PHP Library (SPL).
<
pre>
<?php
print_r(get_declared_interfaces());
IV-A-8. Références et clônage▲
Depuis PHP 5, les objets sont tous des références. Ainsi, copier un objet vers un autre au moyen de l'opérateur "=" ne duplique pas l'objet, au contraire il créé une deuxième référence vers le même objet.
Lorsque l'on créé un objet avec l'opérateur new, PHP créé l'objet en mémoire et référence son emplacement en mémoire dans la variable :
La variable $object contient maintenant une référence vers un objet de la classe stdClass, mais elle ne contient pas l'objet lui-même. Pour détruire cet objet en mémoire, il faut détruire toutes les références vers cette instance de la classe.
$object
=
new stdClass();
unset($object
);
//toutes les références sont détruites, l'objet n'existe donc plus en mémoire
$object_1
=
new stdClass();
$object_2
=
$object_1
;
unset($object_1
);
//il reste une référence, l'objet persiste donc en mémoire
unset($object_2
);
//l'objet n'est plus référencé par aucune variable, il est donc détruit
Pour nous en convaincre, essayons le script suivant :
<?php
class
Test
{
public
function
__destruct
()
{
echo 'Objet détruit<br/>'
;
}
}
$obj_1
=
new
Test();
$obj_2
=
$obj_1
;
$obj_3
=
$obj_2
;
echo 'Marqueur n° 1<br/>'
;
unset($obj_1
);
echo 'Marqueur n° 2<br/>'
;
unset($obj_2
);
echo 'Marqueur n° 3<br/>'
;
Marqueur n° 1
Marqueur n° 2
Marqueur n° 3
Objet détruit
La phrase "Objet détruit" apparaît uniquement lorsque la fin du script cause la destruction de la 3° et dernière référence (appel automatique du destructeur).
Voyons maintenant avec des clônes de l'objet initial :
<?php
class
Test
{
public
function
__destruct
()
{
echo 'Objet détruit<br/>'
;
}
}
$obj_1
=
new
Test();
$obj_2
=
clone
$obj_1
;
$obj_3
=
clone
$obj_2
;
echo 'Marqueur n° 1<br/>'
;
unset($obj_1
);
echo 'Marqueur n° 2<br/>'
;
unset($obj_2
);
echo 'Marqueur n° 3<br/>'
;
Marqueur n° 1
Objet détruit
Marqueur n° 2
Objet détruit
Marqueur n° 3
Objet détruit
Ici, il y a bien une seule référence de chaque objet.
IV-A-9. Late Static Bindings (LSB)▲
Une innovation de PHP 5.3 et 6.0 est ce que l'on appelle les "Late Static Bindings".
Les LSB sont une nouvelle faculté de PHP : résoudre les appels statiques plus tard dans la chronologie des évènements. C'est une question de fonctionnement interne de PHP. Le plus important pour nous est de savoir que les deux exemples suivants agissent différemment :
<?php
class
Chien
{
protected
function
aboyer()
{
return
'Je suis un chien'
;
}
public
function
identifier()
{
return
self
::
aboyer();
//appel à la classe elle-même
}
}
class
Chien_Labrador extends
Chien
{
protected
function
aboyer()
{
return
'Je suis un labrador'
;
}
}
$médor
=
new
Chien();
$félix
=
new
Chien_Labrador();
echo $médor
->
identifier().
'<br/>'
;
echo $félix
->
identifier().
'<br/>'
;
Je suis un chien
Je suis un chien
Ici, la méthode Chien::identifier() est bien transmise par héritage à la classe Chien_Labrador mais elle n'est pourtant pas appelée. Cela a été perçu comme un problème par de nombreuses personnes, et a donné lieu aux Late Static Bindings. De nombreux articles ont été écrits sur le sujet, si vous souhaitez en savoir davantage.
Les Late Static Bindings permettent d'utiliser un nouveau mot clef "static::" pour résoudre correctement la portée statique :
<?php
class
Chien
{
protected
function
aboyer()
{
return
'Je suis un chien'
;
}
public
function
identifier()
{
return
static
::
aboyer();
//appel statique
}
}
class
Chien_Labrador extends
Chien
{
protected
function
aboyer()
{
return
'Je suis un labrador'
;
}
}
$médor
=
new
Chien();
$félix
=
new
Chien_Labrador();
echo $médor
->
identifier().
'<br/>'
;
echo $félix
->
identifier().
'<br/>'
;
Je suis un chien
Je suis un labrador
Dans cet exemple, "static::" permet de négocier l'appel de la méthode aboyer() au moment de l'exécution, et ainsi d'utiliser la méthode Chien_Labrador::aboyer()... Au moment de la compilation du script, on ne sait pas encore quelle classe fournira la méthode : c'est à l'exécution que tout devient clair.
Une application particulièrement intéressante des LSB est dans le cadre de projets ORM (bases de données) :
<?php
class
Table
{
static
function
getByPk($id
)
{
return
'SELECT * FROM '
.
get_called_class().
' WHERE id = '
.(int)
$id
;
}
}
class
Album extends
Table{}
class
Artist extends
Table{}
echo Album::
getByPk(3
);
echo Artist::
getByPk(6
);
SELECT
*
FROM
Album WHERE
id =
3
SELECT
*
FROM
Artist WHERE
id =
6
La fonction get_called_class() s'apparente à la constante magique __CLASS__, à la différence qu'elle retourne la classe appelée par le développeur plutôt que la classe actuelle dans le code.
Les late static bindings ne sont disponibles qu'à partir de PHP 5.3, qui est actuellement en cours de développement. Par conséquent, rien de ce qui a ici trait aux LSB n'est final (jusqu'à la sortie effective de PHP 5.3). Des mots clefs peuvent notamment apparaître, disparaître ou être renommés.
IV-A-10. Exceptions▲
Il existe déjà de nombreux articles sur ce sujet, je ne vais donc pas m'étendre. Exemple : Exceptions et PHP5Tutoriel POO en PHP, par Guillaume Affringue
Les exceptions sont un moyen de gérer les situations marginales mais dont nous savons qu'elles peuvent survenir. Un exemple est l'indisponibilité du serveur pendant la connexion à une base de données : ce n'est pas une situation normale, néanmoins elle est facilement prévisible et identifiable. Un modèle Orienté Objet permet de repérer ces situations et de les gérer de manière personnalisée.
try
{
throw new Exception('
incident
'
);
}
catch(Exception $e
)
{
echo $e
->
getMessage();
//affiche "incident"
}
Lorsqu'une exception est lancée depuis un bloc "try", le bloc "catch" permet de l'attraper et de traiter l'incident. Dans l'exemple ci-dessus, nous avons lancé nous-mêmes volontairement une exception.
PHP permet d'utiliser plusieurs blocs "catch" à la suite, mais ne dispose pas (encore ?) d'instruction "finally" comme dans d'autres langages.
IV-A-11. Fonctions et constantes utiles▲
- class_parents() : Retourne un tableau de la classe parent et de tous ses parents ;
- class_implements() : Retourne un tableau de toutes les interfaces implémentées par la classe et par tous ses parents ;
- get_class() : Retourne la classe de l'objet passé en paramètre ;
- get_called_class() : À utiliser dans une classe, retourne la classe appelée explicitement dans le code PHP et non au sein de la classe ;
- class_exists() : Vérifie qu'une classe a été définie ;
- get_declared_classes() : Liste des classes définies ;
- get_class_methods() : Liste des méthodes d'une classe ;
- get_class_vars() : Liste des propriétés d'une classe.
- __CLASS__ : Donne le nom de la classe en cours ;
- __METHOD__ : Donne le nom de la méthode en cours.
get_class($this) et __CLASS__ ont le même effet.
IV-B. Espaces de noms▲
IV-B-1. Introduction▲
Les espaces de noms, aka namespaces, sont un moyen de résoudre les collisions de noms de constantes, fonctions et classes.
Par exemple si j'ai besoin d'une classe pour filtrer mes variables, je peux avoir envie de créer une classe nommée "Filter". Or, ce nom est sans doute déjà utilisé par PHP ou par l'une des bibliothèques incluses dans mes scripts. La solution classique est de préfixer le nom de la classe de manière à le rendre unique : "DVP_Filter".
Ou dans un contexte MVC, j'ai souvent des modèles et des contrôleurs pour le même concept. Selon le framework utilisé, la nomenclature change mais le nom de classe est généralement préfixé lui aussi.
PHP 5.3 introduit le concept des namespaces dans PHP. Cela nous permet de définir des noms de classes, fonctions etc. au sein d'un espace de noms, par exemple le namespace "DVP" peut contenir la classe "Filter" alors que l'espace de noms global contient également une classe "Filter". Nous pouvons ainsi avoir autant de classes "Filter" que nous pouvons imaginer de noms de namespaces.
Les espaces de noms ne sont disponibles qu'à partir de PHP 5.3, qui est actuellement en cours de développement. Par conséquent, rien de ce qui a ici trait aux espaces de noms n'est final (jusqu'à la sortie effective de PHP 5.3). Des mots clefs peuvent notamment apparaître, disparaître ou être renommés.
IV-B-2. Syntaxe▲
<?php
namespace
<
Nom>;
...
<?php
use
<
Nom>
as
<
Alias>;
$object
=
new
Models::
Member();
//avec le nom complet
$object
=
new
M::
Member();
//avec l'alias
Les espaces de noms composés peuvent être importés tels quels ou avec un alias :
<?php
namespace
Cours::
Models;
...
<?php
use
Cours::
Models;
use
Cours::
Models as
M;
...
En revanche, les noms qui ne sont pas composés doivent être importés avec un alias :
<?php
namespace
Models;
...
<?php
use
Models as
M;
...
Erreur si on ne définit pas d'alias :
<?php
use
Models;
...
Warning: The use statement with non-compound name 'Models' has
no effect in C:\Web\online\http\cours-php\namespaces\index.php on line 6
Bien entendu, il est possible d'imbriquer les espaces de noms :
<?php
namespace
Offline::
Sites::
Application::
Models;
<?php
use
Offline::
Sites::
Application::
Models as
M;
$object
=
new
M::
Member();
Si vous êtes dans un espace de noms (c'est-à-dire dans un script contenant par exemple "namespace Cours;"), vous pouvez vous abstenir d'utiliser le préfixe "Cours::" devant les symboles de cet espace de noms. En effet, PHP suppose que vous utilisez des symboles du même espace de noms, ou bien un symbole du langage. Si vous voulez utiliser un symbole du scope global (mais pas interne à PHP), préfixez le nom du symbole par "::". Si vous utilisez un symbole d'un autre espace de noms, il faut bien entendu préfixer votre symbole de l'espace de noms complet.
Si vous êtes hors d'un espace de noms, il faut systématiquement préfixer soit de l'alias, soit du nom complet.
N'avez-vous jamais rêvé d'utiliser un nom de fonction déjà réservé par le langage ? Les espaces de noms vous permettent d'utiliser les noms déjà réservés pour des constantes, fonctions ou classes internes de PHP.
Il était initialement prévu d'utiliser le mot clef "import" comme en Java, mais cela a finalement été changé pour le mot clef "use". Voir les archives de php.internals[PHP-DEV] import/use last call pour plus d'informations.
IV-B-3. Exemple d'utilisation▲
Reprenons un contexte MVC assez simple pour illustrer une utilité possible des namespaces.
<Directory "C:/Web/online/http/cours-php/namespaces">
AllowOverride
None
php_value
include_path ".;C:/Web/offline/sites/cours-php/namespaces"
SetEnv
HTTP_ROOT /cours-php/namespaces/
RewriteEngine
on
RewriteCond
%{REQUEST_URI} !\.(js|css|jpg|png|gif)$
RewriteRule
.* index.php
</Directory>
<?php
namespace
Cours::
Controllers;
class
Member
{
public
static
function
whoAmI()
{
return
'Je suis un Contrôleur'
;
}
}
<?php
namespace
Cours::
Models;
class
Member
{
public
static
function
whoAmI()
{
return
'Je suis un Modèle'
;
}
}
<?php
namespace
Cours::
Views;
class
Member
{
public
static
function
whoAmI()
{
return
'Je suis une Vue'
;
}
}
<?php
require 'models/member.php'
;
require 'views/member.php'
;
require 'controllers/member.php'
;
header('Content-Type: text/html; charset=utf-8'
);
echo Cours::
Models::
Member::
whoAmI().
'<br/>'
;
echo Cours::
Views::
Member::
whoAmI().
'<br/>'
;
echo Cours::
Controllers::
Member::
whoAmI();
<?php
require 'models/member.php'
;
require 'views/member.php'
;
require 'controllers/member.php'
;
use
Cours::
Models;
use
Cours::
Views;
use
Cours::
Controllers;
header('Content-Type: text/html; charset=utf-8'
);
echo Models::
Member::
whoAmI().
'<br/>'
;
echo Views::
Member::
whoAmI().
'<br/>'
;
echo Controllers::
Member::
whoAmI();
<?php
require 'models/member.php'
;
require 'views/member.php'
;
require 'controllers/member.php'
;
use
Cours::
Models as
M;
use
Cours::
Views as
V;
use
Cours::
Controllers as
C;
header('Content-Type: text/html; charset=utf-8'
);
echo M::
Member::
whoAmI().
'<br/>'
;
echo V::
Member::
whoAmI().
'<br/>'
;
echo C::
Member::
whoAmI();
Je suis un Modèle
Je suis une Vue
Je suis un Contrôleur