I. Introduction▲
Cet article était jusque là en privé, juste pour mon usage personnel, et puis je me suis dit qu'il serait bête de ne pas en faire profiter les autres (c'est le but d'un blog non ?). Voici donc quelques rappels sur la Programmation orientée objet (POO) en PHP. Bien que j'explique un peu les différentes notions, cet article est une cheatsheet sur la POO. Par conséquent, il n'est pas destiné à ceux qui n'en ont jamais fait (ils seront totalement perdus). En revanche, ceux qui connaissent déjà, mais qui, comme moi, ont quelques trous de mémoire, vous pouvez bookmarquer cet article !
I-A. Pseudo variable $this▲
C'est une variable qui est disponible dans les méthodes lorsqu'on instancie une classe. Elle représente l'objet qu'on est en train d'utiliser, donc tous ces attributs et ses méthodes.
Vous noterez que lorsqu'on appelle un attribut avec $this->, on ne met pas le dollar.
I-B. Les types de visibilité▲
Ils définissent la portée des attributs et méthodes d'un objet. Il y en a trois :
L'attribut ou méthode est visible à partir de n'importe où.
L'attribut ou méthode n'est visible qu'à partir de l'intérieur de la classe à laquelle il appartient.
Comme private, mais il étend la visibilité aux classes filles.
I-C. Auto chargement des classes▲
Il est bon pour l'organisation d'avoir un fichier PHP par classe. Vous êtes certainement au courant, quand on veut utiliser du code d'un fichier A dans un fichier B, on doit inclure le fichier A dans B. Cela s'effectue grâce aux fonctions require ou include. Néanmoins, avec un fichier par classe, vous n'êtes pas sortis de l'auberge ! C'est pourquoi il est possible d'automatiser tout ça.
On part du principe que les noms de vos fichiers respectent une certaine logique. Par exemple, en ce qui me concerne, mes fichiers de classe s'appellent NomDeLaClasse.class.php. On va donc créer une fonction qui permettra d'inclure automatiquement mes classes.
OK, jusque là, ça ne sert pas à grand-chose… Mais c'est sans compter sur [spl_autoload_register](http://php.net/manual/fr/function.spl-autoload-register.php). Cette fonction de PHP permet d'enregistrer une fonction d'autoload. En d'autres termes, à chaque fois que l'on appelle une classe non déclarée, la fonction que vous avez enregistrée via spl_autoload_register sera appelée avec le nom de la classe qui va bien en paramètre !
2.
3.
4.
5.
//
On
enregistre
la
fonction
en
autoload
//
pour
qu'elle
soit
appelée
dès
qu'on
instanciera
une
classe
non
déclarée
spl_autoload_register ('
chargerClasse
'
);
$obj
=
new MaClasseNonDéclarée;
I-D. Constantes de classe▲
2.
3.
4.
5.
6.
7.
class MaClasse {
//
Déclaration
de
la
constante
const
MA_CONSTANTE =
20
;
}
//
Instanciation
de
l'objet
$obj
=
new
MaClasse (MaClasse:
:
MA_CONSTANTE);
Les « doubles deux points » sont ce qu'on appelle l'opérateur de résolution de portée. Comme une constante n'appartient pas à un objet, mais à une classe, on ne peut pas accéder à ces valeurs avec l'opérateur ->.
I-E. Attributs et méthodes statiques▲
Les méthodes statiques sont des méthodes qui sont liées à une classe et non à un objet. Par conséquent, l'opérateur self vient en remplacement du $this. En effet, $this signifie « dans cet objet ». Comme par définition un attribut statique appartient à une classe, il serait insensé d'utiliser $this. self veut donc dire : « dans cette classe ».
I-E-1. Méthodes statiques▲
I-E-2. Les attributs statiques▲
Notez bien que contrairement à $this, avec self, l'attribut prend le « $ ». Par contre, self, lui, n'en prend pas !
L'intérêt des méthodes et attributs statiques est qu'appartenant aux classes, tout objet a accès à la même valeur, même si celle-ci vient à être modifiée par un objet ! Un petit exemple pour la route :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
class MaClass {
private
static
$_texte
=
'
hello
World
'
;
public
function
changerTexte() {
if
(self
:
:
$_texte
=
=
'
hello
World
'
) {
self
:
:
$_texte
=
'
hello
Mars
!
'
;
}
else
{
self
:
:
$_texte
=
'
hello
World
'
;
}
return
self
:
:
$_texte
;
}
}
$class
=
new
maclasse();
echo $class
-
>
changerTexte();
$class2
=
new
maclasse();
echo $class2
-
>
changerTexte();
//
********
/*
Affichera
:
hello
Mars
!hello
World
*/
On voit donc bien que le premier objet change l'attribut statique de la classe. Ainsi, le second objet accède bien à cet attribut modifié, sinon sa méthode changerTexte aurait retourné la même valeur que celle du premier objet !
I-F. POO et BDD▲
Une classe ne doit répondre qu'à UNE SEULE fonction. On doit toujours garder ça à l'esprit. Donc, une instance d'une classe qui représente des données stockées en BDD a pour rôle de représenter ces données, pas de les gérer. On va pour cela créer une seconde classe, qui aura pour rôle de gérer les accès à la base. Elle prendra en général le nom de manager. Elle répondra aux fonctions CRUD.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
class MonObjetManager {
//
Instance
de
PDO
private
$_db
;
public
function
__construct
($db
) {
$this
-
>
setDb($db
);
}
public
function
add(MonObjet $obj
) {
$req
=
$this
-
>
_db-
>
prepare('
INSERT
INTO
nom_table
(
colonne1
,
colonne2
,
colonne4
)
VALUES
(
:
data_colonne1
,
:
data_colonne2
,
:
data_colonne4
)
'
);
$req
-
>
execute(array
(
'
data_colonne1
'
=
>
$obj
-
>
data_colonne1,
'
data_colonne2
'
=
>
$obj
-
>
data_colonne2,
'
data_colonne4
'
=
>
$obj
-
>
data_colonne4
));
}
public
function
delete(MonObjet $obj
) {
//
Exécute
une
requête
de
type
DELETE
}
public
function
get($id
) {
//
Exécute
une
requête
de
type
SELECT
avec
une
clause
WHERE
}
public
function
getList() {
//
Retourne
la
liste
de
toutes
les
entrées
}
public
function
update(MonObjet $obj
) {
//
Exécute
une
requête
de
type
UPDATE
}
public
function
setDb(PDO $db
) {
$this
-
>
_db =
$db
;
}
}
I-G. Hydrater ses objets▲
Hydrater un objet, c'est fournir des valeurs à ses attributs. Ainsi, un objet voiture est vide lorsqu'on l'instancie :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
class Voiture {
private
$_placesTotales
;
private
$_vitesseMax
;
private
$_nbrPortes
;
public
function
getPlaces() {
return
$this
-
>
placesTotales;
}
public
function
getVitesseMax {
…}
public
function
getNbrPortes {
…}
public
function
setPlaces($places
) {
$places
=
(int)
$places
;
if
($places
>
0
&
&
<
10
) {
this-
>
placesTotales =
$places
;
}
}
public
function
setVitesseMax {
…}
public
function
setNbrPortes {
…}
}
Si l'on veut rendre cet objet opérationnel, il faut donc l'instancier et donner des valeurs à ces attributs :
$voiture
=
new Voiture;
voiture-
>
setPlaces(4);
//
etc
Vous conviendrez qu'il y a plus pratique… Nous allons donc mettre en place la méthode hydrate. Elle prend en général un tableau associatif en argument (nom de la propriété en clef et valeur en valeur ;)).
Première chose à toujours faire, nous avons des setters, donc on utilisera ces derniers pour attribuer des valeurs à nos propriétés. Les setters sont là pour quelque chose : ils permettent de vérifier les valeurs avant de les attribuer, on ne les court-circuitera donc pas !
Ensuite, comme nous connaissons les différentes propriétés de notre objet, nous pourrions simplement faire quelque chose comme cela :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
class Voiture {
private
$_placesTotales
;
private
$_vitesseMax
;
private
$_nbrPortes
;
public
function
hydrate(array
$donnees
) {
$this
-
>
setPlaces($donnees
[
'
places
'
]
);
$this
-
>
setVitesseMax($donnees
[
'
vitesseMax
'
]
);
//
etc
}
}
En soi, ça fonctionne parfaitement. Néanmoins, si on décide de rajouter des propriétés à notre objet, il faudra modifier notre méthode hydrate. On peut faire mieux.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
class Voiture {
private
$_placesTotales
;
private
$_vitesseMax
;
private
$_nbrPortes
;
public
function
hydrate(array
$donnees
) {
//
On
fait
une
boucle
avec
le
tableau
de
données
foreach
($donnees
as
$key
=
>
$value
) {
//
On
récupère
le
nom
des
setters
correspondants
//
si
la
clef
est
placesTotales,
son
setter
est
setPlacesTotales
//
il
suffit
de
mettre
la
1ere
lettre
de
key
en
Maj
et
de
le
préfixer
par
set
$method
=
'
set
'
.
ucfirst($key
);
//
On
vérifie
que
le
setter
correspondant
existe
if
(method_exists($this
,
$method
)) {
//
S'il
existe,
on
l'appelle
$this
-
>
$method
($value
);
}
}
}
public
function
getPlaces() {
return
$this
-
>
placesTotales;
}
public
function
getVitesseMax {
…}
public
function
getNbrPortes {
…}
public
function
setPlaces($places
) {
$places
=
(int)
$places
;
if
($places
>
0
&
&
$places
<
10
) {
this-
>
placesTotales =
$places
;
}
}
public
function
setVitesseMax {
…}
public
function
setNbrPortes {
…}
}
Et voilà le travail, on appelle les méthodes dynamiquement. Vous remarquez d'ailleurs que method a un $ dans $this->$method($value). C'est normal puisqu'ici on appelle la variable qui représente la méthode !
I-H. L'héritage▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
class MaClasseMere {
//
Attributs
et
méthodes
…
}
//
Création
d'une
classe
qui
hérite
de
MaClasse
class
MaClasseFille extends
MaClasseMere {
//
Attribut
unique
à
la
méthode
fille
private
$_monAttribut
;
public
function
nouvelleMethode {
//
Méthode
qui
ajoute
des
fonctions
à
la
classe
fille
}
}
Une classe fille hérite de toutes les méthodes et tous les attributs de la classe mère. Cependant, elle ne peut pas utiliser les attributs et méthodes en private. Elle devra donc utiliser les setters et getters correspondants.
I-H-1. Surcharger les méthodes▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
class MaClasseMere {
private
$_attributMere
;
public
function
superMethode {
//
Cette
fonction
fait
des
trucs
//
…
}
}
class
MaClasseFille extends
MaClasse {
//
Attribut
unique
à
la
méthode
fille
private
$_monAttribut
;
public
function
nouvelleMethode {
//
Méthode
qui
ajoute
des
fonctions
à
la
classe
fille
}
public
function
superMethode {
//
Méthode
qui
ajoute
des
fonctions
à
la
classe
fille
//
on
appelle
d'abord
la
méthode
de
la
classe
mère
//
pour
ne
pas
écraser
les
instructions
de
celle-ci
parent
:
:
superMethode();
//
Maintenant
on
en
ajoute
d'autre
…
}
}
Alors, petite explication ici. Surcharger une méthode, ça veut dire que la méthode de la classe fille aura plus de fonctions que celle de la classe mère. Problème, si on écrit dans la classe fille une méthode du même nom que celle héritée de la classe mère et que l'on y met des instructions, ça va faire comme si on définissait une nouvelle méthode, pas la surcharger. Pour ça, il faut donc appeler à l'intérieur de cette méthode la méthode de la classe mère avant de lui adjoindre de nouvelles instructions. Cela se fait en utilisant le mot clef parent qui, comme son nom l'indique, fait référence à la classe parente.
I-H-2. Les contraintes▲
Sans contrainte, on peut utiliser les classes sans restriction et les hériter à l'infini. Il existe des contraintes pour remédier à cela.
I-H-2-a. Classe abstraite▲
On peut créer des classes abstraites pour empêcher de les instancier. Ces classes ne devront servir que dans le cadre d'un héritage.
Cette contrainte nous garantit qu'aucune classe de type ClasseAbstraite ne sera instanciée. Dans le cas où on tenterait de le faire, une erreur fatale sera levée.
I-H-2-b. Méthode abstraite▲
On peut aussi déclarer une méthode comme étant abstraite. Dans ce cas, toutes les classes filles devront la réécrire. Ceci vise à forcer les classes filles à écrire une méthode donnée. Cependant, on n'inscrira aucune instruction dans la méthode de la classe mère.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
abstract class ClasseAbstraite {
abstract
public
function
methodeAbstraite();
//
Cette
méthode
n'aura
pas
besoin
d'être
réécrite.
public
function
autreMethode() {
}
}
class
ClasseFille extends
ClasseAbstraite {
//
On
réécrit
la
méthode
du
même
type
de
visibilité
//
que
la
méthode
abstraite
« methodeAbstraite »
de
la
classe
mère.
public
function
methodeAbstraite() {
//
Instructions.
…
}
}
Note : une classe doit être abstraite pour pouvoir déclarer une méthode abstraite.
I-H-2-c. Classes finales▲
Lorsqu'une classe est finale, il n'est pas possible d'en hériter.
Si on tente d'instancier une classe qui hérite d'une classe finale, on obtient une erreur fatale.
I-H-2-d. Méthodes finales▲
Il est aussi possible de déclarer une méthode comme étant finale. La classe fille de cette méthode pourra en hériter, mais il sera impossible de surcharger la méthode.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
class ClasseMere {
//
Méthode
normale.
public
function
uneMethode() {
//
Instructions.
}
//
Méthode
finale.
final
public
function
methodeFinale() {
//
Instructions.
}
}
class
ClasseFille extends
ClasseMere {
//
Aucun
problème.
public
function
methodeFinale() {
//
Impossible
de
surcharger
la
méthode.
}
}
I-I. Les méthodes magiques▲
Ce sont des méthodes qui sont appelées lors d'un événement particulier.
À l'instanciation d'une classe.
À la destruction d'un objet (et automatiquement à la fin de l'exécution du script).
Lorsqu'on tente d'assigner une valeur à un attribut auquel on n'a pas accès (héritage en private) ou qui n'existe pas. Prend deux paramètres : le nom de l'attribut auquel on a tenté d'assigner une valeur et la valeur en question.
Lorsqu'on essaye d'accéder à un attribut auquel on n'a pas accès ou qui n'existe pas. Prend comme paramètre l'attribut auquel on a tenté d'accéder.
__isset
Lorsqu'on fait un isset sur un attribut auquel on n'a pas accès ou qui n'existe pas. Prend en paramètre l'attribut sur lequel on a fait le test. Doit renvoyer un booléen (comme isset).
__unset
Lorsqu'on fait un unset sur un attribut auquel on n'a pas accès ou qui n'existe pas. Prend en paramètre l'attribut sur lequel on a fait unset. Ne doit rien renvoyer.
Lorsqu'on appelle une méthode inexistante ou privée. Prend deux arguments : le nom de la méthode et un tableau avec les arguments qu'on lui a passés.
__callStatic
Lorsqu'on appelle une méthode inexistante statiquement. Prend deux arguments : le nom de la méthode et un tableau avec les arguments qu'on lui a passés. En outre, cette méthode doit obligatoirement être statique.
Lorsqu'un objet est appelé à être converti en chaîne de caractères avec un cast ou un echo.
__invoke
Lorsque l'on appelle un objet comme une fonction.
__clone
Lorsque l'on clone un objet. Cette méthode est appelée sur l'objet cloné, par sur le nouveau.
I-I-1. Exemples▲
I-I-1-a. __set▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class MaClasse {
private
$monAttribut
;
public
function
__set
($nom
,
$valeur
) {
echo '
Il
n
\'
est
pas
possible
d
\'
attribuer
la
valeur
"
'
.
$valeur
.
'
"
à
l
\'
attribut
"
'
.
$nom
.
'
"
'
;
}
}
$obj
=
new
MaClasse;
$obj
-
>
monAttribut =
'
nouvelle
valeur
de
l
\'
attribut
'
;
/*
Retournera
:
Il
n'est
pas
possible
d'attribuer
la
valeur
"nouvelle
valeur
de
l'attribut"
à
l'attribut
"monAttribut"
*/
?
>
I-I-1-b. __invoke▲
I-J. Clonage▲
Il faut savoir qu'au niveau des objets, la variable représentant l'objet ne contient pas l'objet lui-même, mais un id pointant vers celui-ci. Donc si on fait $objet1 = $objet2 et que l'on modifie l'un des deux, les deux seront modifiés. C'est comme si l'on avait créé un alias, c'est d'ailleurs pour cette raison qu'il y a une fonction de clonage.
I-K. Comparaisons▲
Pour comparer deux objets :
=
=
Vérifie que les deux objets sont issus d'une même classe. Ainsi, même s'ils sont identiques (méthodes et attributs), mais issus de deux classes différentes, == renverra FALSE;
=
=
=
Vérifie que les deux objets correspondent à la même instance. Deux clones renverront donc FALSE.
I-L. Interfaces▲
Une interface est une classe complètement abstraite. Elles sont différentes de l'héritage, car elles ne représentent pas un sous-ensemble, elles décrivent un comportement à un objet. Ainsi, il est logique que des classes voiture et moto héritent d'une classe véhicule, mais pas la classe son. En revanche, toutes ces classes peuvent implémenter l'interface vitesse, car voiture, comme moto ou son, ont une vitesse de déplacement.
On implémente une interface comme ceci :
2.
3.
4.
5.
6.
7.
8.
9.
interface vitesse {
public
function
rapidite($kmh
);
}
class
voiture implements
vitesse {
public
function
rapidite($kmh
) {
}
}
- Toute méthode contenue dans une interface doit être publique.
- Les méthodes des interfaces ne doivent rien contenir, elles servent simplement à obliger les classes qui les implémentent à créer ces méthodes.
- Une interface ne peut pas lister de méthode abstraite ou finale.
- On ne peut donner à une interface le même nom qu'une classe.
Il est possible d'implémenter plusieurs interfaces dans une même classe, on séparera leurs noms par des virgules.
Il est aussi possible d'hériter des interfaces entre elles, et contrairement aux classes (mais de manière similaire à implements) il est possible d'hériter de plusieurs interfaces à la fois. Pour ce faire, comme pour l'implémentation, il suffit de séparer le nom des interfaces par une virgule.
De nombreuses interfaces prédéfinies existent dans la SPL. Je vous laisse vous référer à la doc pour ça.
I-M. Exceptions▲
Les exceptions sont une manière différente de gérer les erreurs. Au lieu d'avoir les erreurs standard de PHP (erreurs fatales, alertes, notices ou parse errors), on peut obtenir des erreurs personnalisées et les « attraper », ce qui permettra la poursuite du script.
On peut lancer une exception depuis n'importe où dans notre code. Pour cela, il faut créer une instance de la classe Exception. La classe prend trois arguments (tous sont facultatifs) :
- Le message d'erreur ;
- Le code d'erreur ;
- L'exception précédente.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
//
on
crée
une
fonction
qui
calcule
l'aire
d'un
rectangle
function aire($largeur
,
$longueur
) {
if (!
is_numeric($largeur
) OR !
is_numeric($longueur
)) {
throw new Exception('
Les
paramètres
doivent
être
des
nombres
'
);
}
else {
echo $largeur
*
$longueur
;
}
}
try {
aire(7,
45);
}
catch(Exception $e
) {
echo $e
-
>
getMessage();
}
Il est possible d'hériter la classe exception pour la personnaliser. Pour cela, se reporter à la doc. De plus, la bibliothèque SPL contient de nombreuses exceptions standard.
Enfin, sachez qu'il est aussi possible d'avoir plusieurs blocs catch dans le code. Cela permet par exemple, en utilisant des exceptions adaptées, de mieux se repérer dans le code et d'effectuer la capture d'une expression donnée à un endroit précis. Cependant, si on précise exception dans le bloc catch, ceci attrapera toutes les exceptions, car elles héritent forcément toutes de celle-ci.
I-N. Les classes anonymes▲
Actuellement, on sait que pour créer une classe, il faut la déclarer dans un fichier, définir son namespace, inclure le fichier et utiliser la classe avec son namespace. Ce n'est pas très rapide et ça prend de la place, surtout si l'on ne va l'utiliser qu'une fois. PHP7 permet de créer des classes anonymes, des classes n'ayant pas de nom et qui sont instanciables à l'endroit de l'utilisation.
I-O. Réflexion▲
C'est un point intéressant de la POO. PHP fourni une API de réflexion qui permet de faire du reverse-engineering sur les classes, les méthodes, les fonctions et les extensions. Pour en apprendre davantage là-dessus, rien ne vaut, une fois de plus, un tour dans la doc officielle.
Un grand merci à Benjamin Rothan pour la relecture et l'ajout du paragraphe sur les classes anonymes !
II. Note de la rédaction de Developpez.com▲
Nous tenons à remercier Quentin Busuttil qui nous a aimablement autorisés à publier son tutoriel : Synthèse de la POO en PHP. Nous remercions également ced pour la relecture orthographique.