I. Introduction▲
PHP5 est arrivé ! Comme l'a dit Rasmus Ledorf, l'inventeur de PHP, PHP5 n'est pas une révolution, mais une évolution. Cette évolution introduit quelques changements majeurs tout en conservant une compatibilité totale avec la version antérieure.
Les principales nouvelles fonctionnalités sont :
- SQLite0160 : un SGBD embarqué dont nous verrons les principales forces et faiblesses ;
- SimpleXML : un nouveau parseur XML très efficace et très simple ;
- un nouveau modèle POO : le modèle objet complètement remanié, l'ancien restant correctement interprété par PHP.
Vous trouverez plus de détails sur ces fonctionnalités dans le reste de l'article.
Note : tous les exemples de code fournis dans ce tutoriel ont été testés avec PHP 5.0.0 et SQLite 2.8.14.
II. Les fonctionnalités en détail▲
II-A. SQLite▲
II-A-1. Description du produit▲
Selon moi, l'introduction de SQLite est le changement majeur entre PHP5 et PHP4.x. SQLite est un SGBD embarqué, donc compilé par défaut, ce qui signifie que le simple fait d'installer PHP vous permet de l'utiliser. Contrairement à MYSQL, il ne nécessite donc pas de processus indépendant comme MYSQL Server. SQLite est très léger et très rapide, selon le site officiel de SQLite, il serait 2 à 3 fois plus rapide que Mysql pour la plupart des opérations et parfois 10 fois plus rapide que Postgress et probablement aussi plus rapide que beaucoup de SGBD. Ceci est assez normal puisqu'il n'a pas d'architecture client-serveur, l'interface et le moteur étant en fait contenus dans le même package.
Tempérons toutefois ces résultats, il faudrait qu'un organisme indépendant effectue aussi ce genre de tests et dans des environnements plus complexes pour pouvoir tirer des conclusions définitives. Il n'empêche que ces résultats sont tout de même encourageants !
Outre le moteur compilé par défaut, SQLite fournit aussi des classes facilitant son interaction avec PHP. L'architecture de SQLite est assez simpliste puisque toute base de données réside dans un et un seul fichier. Les utilisateurs voulant accéder cette base de données sont tributaires des droits accordés au niveau du système d'exploitation. SQLite semble convenir à merveille pour des applications ne nécessitant pas trop de transactions concurrentes. En effet, ce qui différencie entre autres, SQLite des grands SGBD (Oracle, Sybase, SQL Server…) c'est qu'il ne permet pas d'insertions concurrentes. À chaque fois qu'un processus effectue un ordre DML et DDL(INSERT,UPDATE,DELETE, CREATE…), la base est entièrement verrouillée, ce qui vous en conviendrez n'est pas très pratique. Des accès concurrents et presque illimités sont par contre permis en lecture seule (SELECT). Dans un environnement où un nombre réduit de processus est chargé d'écrire et tous les autres de lire, SQLite est probablement une excellente solution. SQLite implémente la norme SQL 92.
II-A-2. Fonctionnalités▲
SQLite est doté d'atouts intéressants, voici une liste non exhaustive de ceux-ci :
- possibilité de gérer des transactions ;
- création de vues ;
- la gestion de contraintes d'intégrité dans un futur proche ;
- la gestion des contraintes NOT NULL et UNIQUE ;
- gestion des triggers ;
- gestion des exceptions ;
- possibilité d'utiliser les fonctions PHP internes directement dans les requêtes ;
- possibilité d'utiliser ses propres fonctions codées en PHP dans les requêtes ;
- possède beaucoup de fonctions internes.
Chaque ordre DML (UPDATE, INSERT, DELETE) démarre automatiquement une transaction. Si cette transaction est implicite, elle sera validée à la fin de l'exécution de l'ordre. On peut décider de démarrer une transaction explicite dont la validation ou l'annulation sera gérée par le développeur. Des exemples en section 2.1.3 vous montreront comment cela fonctionne. SQLite ne permet pas de gérer des transactions imbriquées. Donc seulement une transaction peut être démarrée à la fois.
La création de vues est possible en SQLite. Pour rappel, une vue est un « recordset » correspondant à un ordre SELECT défini par le développeur. Lorsque l'on sait que les besoins en matière de requête des utilisateurs finals sont souvent les mêmes, il est bon de créer des vues correspondant déjà à leurs aspirations. Ces vues accélèrent la lecture de la base de données.
Les triggers signifiant « déclencheurs » en français permettent de déclencher un ordre SQL lors d'un évènement précis. Nous verrons comment utiliser des triggers avec SQLite.
II-A-3. Exemples d'utilisation via la classe compilée SQLiteDatabase▲
Exemples d'ordre DDL
<?
//l'instanciation de l'objet prend en paramètre le nom de la db. L'objet va alors
//tenter de se connecter à cette db ou de la créer dans le répertoire courant si celle-ci n'existe pas.
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
$requete
=
"
//création d'une table nommée adresses_clients
CREATE TABLE ADRESSES_CLIENTS ( ID_ADRESSE INTEGER PRIMARY KEY, ID_CLIENT INTEGER , ADRESSE VARCHAR(250) );
//création d'une table clients
CREATE TABLE CLIENTS ( ID_CLIENT INTEGER, NOM_CLIENT VARCHAR(60), PRENOM_CLIENT VARCHAR(60) );
//création d'une vue faisant une jointure entre les tables clients et adresses_clients
CREATE VIEW CLIENTSADR AS SELECT NOM_CLIENT,PRENOM_CLIENT,ADRESSE FROM CLIENTS INNER JOIN ADRESSES_CLIENTS
ON ADRESSES_CLIENTS.ID_CLIENT=CLIENTS.ID_CLIENT;
//insertion de deux enregistrements
INSERT INTO CLIENTS VALUES ('1', 'Eyskens', 'Stéphane');
INSERT INTO ADRESSES_CLIENTS VALUES ('1', '1', 'démo adresse');
"
;
$db
->
query($requete
);
unset($db
);
?>
Ce code simpliste met en évidence la chose suivante : il est désormais possible d'exécuter plusieurs ordres SQL en appelant une seule fois la méthode query. Ceci est très pratique, mais peut aussi s'avérer dangereux. Si vous utilisez des données provenant d'un formulaire utilisateur, méfiez-vous des injections SQL. Avec mysql_query, il n'était pas possible d'exécuter plusieurs requêtes via un seul appel, le risque d'injection était donc moindre. Les ordres SQL doivent simplement être séparés par un point-virgule. Les seuls ordres DDL actuellement implémentés dans SQLite sont CREATE et DROP. Il n'existe pas d'instruction ALTER comme dans la plupart des SGBD. En attendant une évolution possible de SQLite, la manière la plus simple de modifier la structure d'une table est d'en créer une nouvelle avec la structure désirée, copier l'originale dans une table temporaire et de supprimer l'originale et la table temporaire en fin de traitement. Lorsque l'on crée une table, il n'est pas indispensable de spécifier le type de colonne, car SQLite n'implémente en réalité qu'un seul type qui est « INTEGER PRIMARY KEY ». Tous les autres types sont considérés comme étant des strings. Concrètement, cela signifie que si vous définissez une colonne de type SMALLINT et que vous y mettez du string, SQLite laissera faire. Le pendant de PHPMyadmin pour MYSQL a déjà été réalisé pour SQLite. Il s'appelle SQLiteManager et est téléchargeable ici
Les triggers
Comme je l'ai mentionné plus haut, les contraintes d'intégrité de type clé étrangère ne sont pas encore supportées par SQLite. Il est néanmoins possible de les remplacer par des triggers. Voici un exemple qui crée un trigger sur la table « adresses_clients ». Ce trigger vérifie que pour chaque nouvelle insertion dans cette table, le numéro de client existe bien dans la table clients. Si tel est le cas, la ligne est validée, sinon elle génère une exception.
<?
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
$requete
=
"
CREATE TRIGGER VERIF_CLIENT BEFORE INSERT ON ADRESSES_CLIENTS FOR EACH ROW
BEGIN
SELECT CASE
WHEN (SELECT COUNT(*) FROM CLIENTS WHERE ID_CLIENT=new.ID_CLIENT)=0 THEN RAISE(FAIL,
\"
Le client n'existe pas
\"
)
END;
END;"
;
$db
->
query($requete
);
unset($db
);
?>
Grâce à ce trigger, vous pouvez désormais vous assurer que les numéros de clients définis dans la table adresses_clients ont bien été préalablement enregistrés dans la table clients. Les triggers ont bien sûr une multitude de cas d'utilisation. On pourrait par exemple les utiliser pour logger ce qui se passe dans la base de données par exemple ceci :
<?
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
$requete
=
"
//on crée un trigger qui crée une ligne dans la table LOG pour chaque ligne insérée dans client. Il enregistre l'heure
CREATE TRIGGER LOG_CLI_INS AFTER INSERT ON CLIENTS
FOR EACH ROW
BEGIN
//Imaginons que nous ayons préalablement créé la table LOG
INSERT INTO LOG ( NOM_TABLE,OPERATION,DATE_HEURE) VALUES ('CLIENTS','INSERT',php('date','Y-m-d G:i:s'));
END;
CREATE TRIGGER LOG_CLI_UPD AFTER UPDATE ON CLIENTS
FOR EACH ROW
BEGIN
INSERT INTO LOG ( NOM_TABLE,OPERATION,DATE_HEURE) VALUES ('CLIENTS','UPDATE',php('date','Y-m-d G:i:s'));
END;
"
;
$db
->
query($requete
);
unset($db
);
?>
Le code ci-dessous permet donc d'enregistrer le type d'opération et l'heure à laquelle elle a été exécutée dans la table LOG. Les triggers peuvent gérer les évènements « on DELETE » « on UPDATE » « on INSERT » et peuvent spécifier la colonne sur laquelle le déclencheur doit être activé.
Les transactions
Pour comprendre à quoi servent les transactions, je vous renvoie vers un de nos tutoriels Les transactions. En attendant, voici un exemple très simple d'utilisation :
<?
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
$requete
=
"
BEGIN TRANSACTION; //on démarre une transaction
DELETE FROM CLIENTS; //on supprime tous les enregistrements de la table clients
COMMIT TRANSACTION; //On valide les changements, donc la suppression est effective
BEGIN TRANSACTION; //on démarre une autre transaction. On insère un enregistrement
INSERT INTO CLIENTS(ID_CLIENT,NOM_CLIENT,PRENOM_CLIENT) VALUES(156,'DVP NOM','DVP PRENOM');
ROLLBACK TRANSACTION;//On annule les changements, donc l'insertion n'est pas effectuée
"
;
$db
->
query($requete
);
//On exécute la requête
$result
=
$db
->
query('select NOM_CLIENT FROM CLIENTS'
);
//On selectionne tous les enreg. de la table clients
while
($result
->
valid()){
//On parcourt le recordset
$row
=
$result
->
current();
echo $row
[
'NOM_CLIENT'
];
$result
->
next();
}
unset($db
);
?>
On remarquera que l'exécution de ce script n'affichera rien. En effet, si on le décortique, on peut constater ceci :
- la 1re transaction supprimant tous les enregistrements de la table clients est validée via le COMMIT ;
- la 2e transaction insérant un enregistrement est annulée via le ROLLBACK ;
- la table est donc vide, il est normal que la deuxième requête ne récupère aucun enregistrement.
L'utilisation des transactions est importante avec SQLite, car outre les aspects relevés dans le tutoriel « Les transactions », SQLite est beaucoup plus rapide lorsqu'on les utilise.
Les fonctions personnelles
L'utilisation de fonctions personnelles peut s'avérer d'une grande utilité ! Pour vous montrer comment cela fonctionne, je vous propose un exemple tout à fait fantaisiste :
<?
function
transforme_chaine($chaine
){
if
(empty($chaine
)){
return
NULL
;}
$ch
=
NULL
;
for
($i
=
0
;
$i
<
strlen($chaine
);
$i
++
){
if
(($i
%
2
)==
0
){
$ch
.=
strtoupper($chaine
[
$i
]
);
}
else
{
$ch
.=
strtolower($chaine
[
$i
]
);
}
}
return
$ch
;
}
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
//cette méthode enregistre notre fonction dans SQLite, le nom que l'on veut donner dans SQLite
//peut être différent du nom de la fonction php. Le dernier paramètre, ici "1" est le nombre d'arguments que
//notre fonction PHP accepte
$db
->
createFunction('transforme_chaine'
,
'transforme_chaine'
,
1
);
$result
=
$db
->
query('select transforme_chaine(NOM_CLIENT) as NOM,transforme_chaine(PRENOM_CLIENT) as PRENOM FROM CLIENTS'
);
while
($result
->
valid()){
$row
=
$result
->
current();
echo 'Nom:'
.
$row
[
'NOM'
].
'Prénom:'
.
$row
[
'PRENOM'
];
$result
->
next();
}
}
unset($db
);
?>
Il est à noter que l'enregistrement de notre fonction personnelle dans SQLite ne durera que le temps d'exécution du script. L'exemple ci-dessus retournera les noms et prénoms des clients transformés. Une lettre sur deux étant transformée en majuscule et l'autre en minuscule. Ce qui donne ceci :
Nom : DvP NoM Prénom : DvP PrEnOm Nom : DvP1 nOm Prénom : DvP PrEnOm
Utilisation des fonctions internes de php
<?
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
//La syntaxe d'utilisation d'une fonction php dans une requête SQLite est php('nom de la fonction',paramètre)
$result
=
$db
->
query("select php('strtoupper',NOM_CLIENT) as NOM,php('strtolower',PRENOM_CLIENT) as PRENOM FROM CLIENTS"
);
while
($result
->
valid()){
$row
=
$result
->
current();
echo 'Nom:'
.
$row
[
'NOM'
].
' Prénom:'
.
$row
[
'PRENOM'
];
$result
->
next();
}
unset($db
);
?>
Cet exemple est assez parlant et ne nécessite guère d'explication.
SQLite supporte les requêtes imbriquées, voici un petit exemple
<?
$db
=
new
SQLiteDatabase('DEVELOPPEZ'
);
$result
=
$db
->
query("select NOM_CLIENT FROM CLIENTS WHERE ID_CLIENT IN
(SELECT ID_CLIENT FROM ADRESSES_CLIENTS GROUP BY ID_CLIENT)"
);
while
($result
->
valid()){
$row
=
$result
->
current();
echo 'Nom:'
.
$row
[
'NOM_CLIENT'
];
$result
->
next();
}
unset($db
);
?>
Cette requête affichera le nom des clients ayant une ou plusieurs adresse(s) définie(s) dans la table ADRESSES_CLIENTS. Il est bien sûr possible d'obtenir ces informations avec une jointure classique, mais c'est juste pour vous fournir un exemple.
II-A-4. Exemples d'utilisation en mode procédural▲
En mode procédural, les fonctions php attaquant une base de données SQLite sont très semblables à toutes les autres fonctions utilisées dans les accès à tous les SGBD. En effet, ces fonctions sont toutes préfixées de la chaîne « sqlite_ ». Ceci peut-être très pratique dans l'optique d'une migration de scripts procéduraux gérant des accès SGBD. Dans la plupart des cas, il suffira de remplacer mysql_… par sqlite_… ou encore mssql_… par sqlite_…
Je ne vais donc pas trop m'étendre sur le mode procédural puisqu'il est très similaire à celui utilisé avec mysql et dont vous êtes déjà familiarisé. Je vais néanmoins vous fournir deux ou trois exemples.
Exemple DDL, DML
<?
$db
=
sqlite_open('test.sqlite'
);
sqlite_query($db
,
'CREATE TABLE MODE_PROCEDURAL(COL1)'
);
sqlite_query($db
,
"INSERT INTO MODE_PROCEDURAL(COL1) VALUES('exemple')"
);
$result
=
sqlite_query($db
,
"SELECT COL1 FROM MODE_PROCEDURAL"
);
while
($row
=
sqlite_fetch_object($result
)){
echo $row
->
COL1;
}
unset($db
);
?>
Exemple avec les transactions
<?
$db
=
sqlite_open('test.sqlite'
);
sqlite_query($db
,
'BEGIN TRANSACTION;'
);
sqlite_query($db
,
'DELETE FROM MODE_PROCEDURAL'
);
sqlite_query($db
,
'COMMIT TRANSACTION;'
);
sqlite_query($db
,
'BEGIN TRANSACTION;'
);
sqlite_query($db
,
"INSERT INTO MODE_PROCEDURAL(COL1) VALUES('exemple')"
);
sqlite_query($db
,
"ROLLBACK TRANSACTION"
);
$result
=
sqlite_query($db
,
"SELECT COL1 FROM MODE_PROCEDURAL"
);
while
($row
=
sqlite_fetch_object($result
)){
echo $row
->
COL1;
}
unset($db
);
?>
Cet exemple a exactement le même effet que l'exemple d'utilisation des transactions via la classe SQLiteDatabase.
Je pense que l'utilisation du mode procédural peut surtout être utile lors des migrations. Pour tout nouveau développement, je préconise le mode POO en utilisant la classe SQLiteDatabase. Celle-ci permet en outre l'exécution de multirequête, ce qui peut s'avérer intéressant. Vous pourriez bien sûr réinventer la roue en vous créant votre propre classe !
II-B. SimpleXML▲
L'introduction de SimpleXML est certainement une belle avancée dans le domaine d'interaction entre PHP et XML. Là où l'utilisation des objets existants était parfois complexe, SimpleXML, qui porte bien son nom, améliore le confort du développeur pour interpréter des fichiers XML.
Prenons par exemple un fichier XML très simple au format UTF8 que nous nommerons nouvelles.xml :
<?xml version="1.0"?>
<nouvelles>
<nouvelle>
<contenu>
Monsieur et Madame X viennent de se marier! Félicitations</contenu>
<date>
10/10/2004</date>
</nouvelle>
<nouvelle>
<contenu>
Confirmant les tendances pessimistes, Monsieur et Madame X viennent de divorcer</contenu>
<date>
10/10/2005</date>
</nouvelle>
</nouvelles>
Voici le code php servant à le parcourir :
<?php
$nouvelles
=
simplexml_load_file('nouvelles.xml'
);
foreach
($nouvelles
->
nouvelle as
$nouvelle
) {
echo 'Contenu : '
,
utf8_decode($nouvelle
->
contenu).
'<br>'
;
echo 'Date : '
,
$nouvelle
->
date.
'<br>'
;
}
?>
Affichage généré par le script |
---|
Contenu : Monsieur et Madame X viennent de se marier ! Félicitations |
Contenu : Confirmant les tendances pessimistes, Monsieur et Madame X viennent de divorcer |
Interaction avec DOMXML
Il est possible de transférer à SimpleXML un objet DOM préalablement instancié. Cette manipulation se fait comme ceci :
$xml
=
new domDocument ;
$xml
->
load('
nouvelles.xml
'
) ;
$simpleXml
=
simplexml_import_dom($xml
);
Utilisation de la méthode xpath
La méthode xpath permet de pointer directement un nœud spécifique :
<?
$nouvelles
=
simplexml_load_file('nouvelles.xml'
);
$xpath
=
'/nouvelles/nouvelle/contenu'
;
$nouvelle
=
$nouvelles
->
xpath($xpath
) ;
foreach
( $nouvelle
as
$news
) {
echo utf8_decode($news
);}
?>
Affichage généré par le script |
---|
Monsieur et Madame X viennent de se marier ! Félicitations |
Exemple avec un fichier RSS
Soit le fichier RSS suivant :
<?xml version="1.0" encoding="UTF-8"?>
<rss
version
=
"0.91"
>
<channel>
<item>
<title>
Utilisation de PHPMailer</title>
<link>
http://stephaneey.developpez.com/tutoriel/php/phpmailer/</link>
<description>
Cet article vous expliquera comment utiliser php mailer...</description>
</item>
<item>
<tilte>
Créer son propre composant en FLASH MX2004</tilte>
<link>
http://stephaneey.developpez.com/tutoriel/flashmx2004/composant/</link>
<description>
Cet article vous montre comment créer votre propre composant en Flash MX
et vous en propose un en téléchargement</description>
</item>
</channel>
</rss>
Ce fichier RSS reprend deux de mes articles, contient les liens vers ceux-ci et une brève description.
Voici le code PHP avec SimpleXML nécessaire pour le parser :
<
html>
<
body>
<?
$xml
=
simplexml_load_file('http://stephaneey.developpez.com/tutoriel/php/php5_nouveautes/exemple.rss'
) ;
foreach
($xml
->
channel->
item as
$item
) {
echo '<h1><a href='
.
utf8_decode($item
->
link).
'>'
.
utf8_decode($item
->
title).
'</a></h1>'
.
utf8_decode($item
->
description);
}
?>
</
body>
</
html>
Visualisez le résultat ici
Conclusion
Nous avons un peu découvert le potentiel de SimpleXML. Il y a bien sûr d'autres contextes d'utilisation. Je vous laisse les découvrir. Ce qu'il faut retenir de SimpleXML est qu'il stocke dans des tableaux imbriqués tous les nœuds d'un document XML. Cela me fait penser à peu de choses près aux méthodes couramment utilisées dans d'autres langages où des collections de nœuds sont implémentées, avec SimpleXML, ces collections sont des tableaux.
II-C. Le nouveau modèle objet▲
L'introduction des constructeurs et des destructeurs
Avant PHP5, les constructeurs étaient des fonctions portant le même nom que la classe dans laquelle ils se trouvaient. Désormais, PHP5 implémente des méthodes spécialement conçues pour les constructeurs et les destructeurs.
<?
class
dvpClass {
function
__construct
() {
echo 'Constructeur déclenché<br>'
;
}
function
dvp_exemple(){
echo 'Exemple Objet<br>'
;
}
function
__destruct
() {
echo 'Destructeur déclenché'
;
}
}
//À l'instanciation de la classe, le constructeur est automatiquement déclenché
$obj
=
new
dvpClass();
//L'appel de cette méthode est le dernier endroit dans le script où l'objet $obj est référencé, après cet
//appel, le destructeur sera automatiquement déclenché.
$obj
->
dvp_exemple();
?>
Affichage généré par le script |
---|
Constructeur déclenché |
La grande nouveauté concerne surtout les destructeurs. Comme vous pouvez le constater, le destructeur est automatiquement déclenché dès lors que plus aucune référence à l'objet instancié n'est trouvée.
Note : par souci de compatibilité, l'ancienne manière pour définir un constructeur est toujours possible. À savoir, définir une méthode portant le même nom que la classe.
Lors de l'héritage de classe, le constructeur et le destructeur de la classe mère ne sont pas automatiquement déclenchés. Si vous l'estimez nécessaire, vous devez les déclencher explicitement.
<?
class
dvpClassParent{
function
__construct
(){
echo 'Constructeur père déclenché<br>'
;
}
function
__destruct
(){
echo 'Destructeur père déclenché<br>'
;
}
}
class
dvpClassFille extends
dvpClassParent {
function
__construct
() {
parent
::
__construct
();
echo 'Constructeur fille déclenché<br>'
;
}
function
dvp_exemple(){
echo 'Exemple Objet<br>'
;
}
function
__destruct
() {
echo 'Destructeur fille déclenché<br>'
;
parent
::
__destruct
();
}
}
$obj
=
new
dvpClassFille();
$obj
->
dvp_exemple();
?>
Affichage généré par le script |
---|
Constructeur père déclenché |
La portée des membres de classes
En PHP5, le modèle objet est tout à fait conforme aux grands langages classiques de la POO. L'introduction de la portée des membres d'une classe est donc implémentée comme dans tous les langages orientés objet. Nous voyons donc apparaître les mots clés suivants :
- private : propriété ou méthode uniquement accessible au sein de la classe où elle a été définie. L'objet instancié ne pourra pas référencer directement cette propriété/méthode ;
- protected : propriété ou méthode uniquement accessible au sein de la classe et au sein des classes héritant de celle-ci. L'objet instancié ne pourra pas référencer directement cette propriété/méthode ;
- public : propriété ou méthode accessible à toute ;
- interface : permet de définir des interfaces ;
- implements : permet d'implémenter une interface préalablement définie ;
- abstract : classe ne pouvant être instanciée. Toute classe contenant une méthode de type abstract doit elle-même être de type abstract ;
- le principe de surcharge est implémenté ;
- final : empêche les classes filles de reimplémenter une méthode de la classe mère ;
- static : permet de définir des propriétés/méthodes accessibles en dehors du contexte d'objet ;
- la « clonisation » des objets permet de copier un objet de manière personnalisée si le développeur a défini une méthode __clone() au sein de la classe clonée.
Voici deux exemples qui vont vous permettre de bien cerner la portée des propriétés et méthodes (private, public et protected) :
<?
class
ExempleMethodes {
public
$propriete1
;
private
$propriete2
;
protected
$propriete3
;
function
__construct
(){
$this
->
propriete1=
1
;
$this
->
propriete2=
2
;
$this
->
propriete3=
3
;
}
public
function
methode1(){
echo 'méthode publique'
;
$this
->
propriete2++;
//autorisé, car référencé au sein de la classe
$this
->
propriete3++;
//autorisé, car référencé au sein de la classe
$this
->
methode2();
//autorisé, car référencé au sein de la classe
$this
->
methode3();
//autorisé, car référencé au sein de la classe
}
protected
function
methode2(){
echo 'méthode protégée'
;
}
private
function
methode3(){
echo 'méthode privée'
;
}
}
$obj
=
new
ExempleMethodes();
$obj
->
methode1();
//autorisé
echo $obj
->
propriete1;
// autorisé
echo $obj
->
propriete2;
// non autorisé
echo $obj
->
propriete3;
// non autorisé
$obj
->
methode2();
//non autorisé
$obj
->
methode3();
//non autorisé
?>
Voici un autre exemple permettant de bien cerner la différence entre private et protected :
<?
class
ExempleMethodes {
public
$propriete1
;
protected
$propriete2
;
private
$propriete3
;
function
__construct
(){
$this
->
propriete1=
1
;
$this
->
propriete2=
2
;
$this
->
propriete3=
3
;
}
public
function
methode1(){
echo 'méthode publique'
;
$this
->
propriete2++;
$this
->
propriete3++;
$this
->
methode2();
$this
->
methode3();
}
protected
function
methode2(){
echo 'méthode protégée'
;
}
private
function
methode3(){
echo 'méthode privée'
;
}
}
class
ExempleFille extends
ExempleMethodes{
function
__construct
(){
parent
::
__construct
();
echo $this
->
propriete1;
//autorisé
echo $this
->
propriete2;
//autorisé
echo $this
->
propriete3;
//non autorisé
$this
->
methode1();
//autorisé
$this
->
methode2();
//OK car la classe fille hérite des prop. et mét. protégées implémentées dans la classe mère
$this
->
methode3();
//non autorisé, private réserve un accès exclusif à la classe contenant la prop/méthode
}
}
$obj
=
new
ExempleFille();
?>
Note : par souci de compatibilité avec les versions antérieures, la déclaration explicite de private, public et protected n'est pas obligatoire. Toutes les propriétés et méthodes dont la portée n'est pas déclarée explicitement seront considérées comme publiques.
interface et implements
Une interface est un modèle contenant le prototype des méthodes qui doivent être obligatoirement implémentées par la classe implémentant l'interface. La déclaration des méthodes doit être strictement identique à celle définie dans l'interface :
<?
interface
dvpInterface
{
//Prototypes des méthodes
//Aucune propriété ne peut être définie dans une interface
public
function
methode1($p1
);
public
function
methode2($p1
);
}
class
dvpImplements implements
dvpInterface
{
private
$prop
;
//On DOIT implémenter les méthodes définies au niveau de l'interface
public
function
methode1($p1
)
{
$this
->
prop =
$p1
;
}
public
function
methode2($p1
)
{
$this
->
prop =
$p1
;
}
//On peut bien sûr définir des méthodes n'étant pas définies dans l'interface
private
function
methode3($p1
){
$this
->
prop =
$p1
;
}
}
?>
abstract
Une classe définie via abstract ne peut-être instanciée. Si une classe contient une méthode de type abstract, la classe doit l'être aussi. Une classe héritant d'une classe abstract doit implémenter les prototypes de méthodes définies au niveau de la classe abstract, ces prototypes étant eux-mêmes préfixés du mot clé abstract. C'est le même principe que pour les interfaces. La différence avec les interfaces est qu’une classe abstract peut contenir autre chose que des prototypes de méthodes :
<?
abstract
class
dvpAbstract{
abstract
public
function
methode1();
//Prototype de méthode
protected
function
notAbstract(){
//Méthode propre à la classe abstract.
echo "ceci n'est pas un prototype, mais une méthode ordinaire<br>"
;
}
}
class
dvpClass extends
dvpAbstract{
public
function
methode1(){
echo 'Implémentation du prototype hérité de la classe dvpAbstract<br>'
;
$this
->
notAbstract();
}
}
$obj
=
new
dvpClass();
$obj
->
methode1();
?>
static
static permet de définir des propriétés et méthodes accessibles en dehors du contexte d'instanciation :
<?php
class
dvpClass {
public
static
$propriete
=
1
;
}
echo dvpClass::
$propriete
;
//autorisé, affiche 1
$obj
=
new
dvpClass();
echo $obj
->
propriete;
//non autorisé
?>
final
final permet de prévenir une classe fille de réimplémenter une méthode définie au niveau de la classe mère. Ce petit exemple est très parlant :
<?
class
dvpClass {
public
function
methode1(){
echo 'méthode 1'
;
}
final
public
function
methode2() {
echo 'méthode 2 de type final'
;
}
}
class
dvpClassFille extends
dvpClass {
public
function
methode1() {
echo 'tentative de réimplémentation de la méthode methode1'
;
}
public
function
methode2(){
echo 'tentative de réimplémentation de la méthode methode2 -> INVALIDE'
;
}
}
?>
clone
L'instruction clone vous permet de copier un objet, soit entièrement, soit en personnalisant la copie. Si vous désirez copier un objet dans son intégralité, vous ne devez pas définir de méthode __clone au sein de la classe clonée. Par contre, si vous désirez qu'un comportement spécifique intervienne lors de la copie, vous devez définir une méthode __clone afin que les instructions codées dans cette méthode soient exécutées. Voici un exemple très clair :
<?
class
dvpClass{
public
$propriete
;
function
__construct
($val
){
$this
->
propriete=
$val
;
}
}
$obj
=
new
dvpClass(1
);
echo $obj
->
propriete;
$clone
=
clone
$obj
;
echo $clone
->
propriete;
?>
Affichage généré par le script |
---|
11 |
<?
class
dvpClass{
public
$propriete
;
function
__construct
($val
){
$this
->
propriete=
$val
;
}
function
__clone(){
$this
->
propriete=
2
;
}
}
$obj
=
new
dvpClass(1
);
echo $obj
->
propriete;
$clone
=
clone
$obj
;
echo $clone
->
propriete;
?>
Affichage généré par le script |
---|
12 |
On voit très clairement que lorsqu'une méthode __clone est définie, les instructions qui y sont contenues sont exécutées lors de l'appel à l'instruction clone permettant de copier un objet.
La gestion des exceptions via la classe Exception
Les célèbres « try » et « catch » sont enfin implémentés en PHP5. Voici un petit exemple d'utilisation :
<?
class
dvpExempleException {
public
function
genererException($probleme
) {
if
(!
is_int($probleme
)) {
throw
new
Exception
("L'argument -
$probleme
- n'est pas numérique"
);
}
}
}
$Obj
=
new
dvpExempleException();
try
{
$Obj
->
genererException('chaîne au lieu d
\'
un numérique'
);
}
catch
(Exception
$exception
) {
echo $exception
->
getMessage().
','
.
$exception
->
getLine().
','
.
$exception
->
getFile();
}
?>
Affichage généré par le script |
---|
L'argument -chaîne au lieu d'un numérique- n'est pas numérique,7,c:\webroot\dvpscript.php |
Les méthodes getMessage(), getLine() et getFile() sont des méthodes appartenant à la classe Exception. Elles affichent respectivement le message d'erreur, le numéro de ligne où l'exception est générée (throw) et enfin le nom du script où l'exception s'est produite.
La classe Exception étant assez basique, vous pouvez bien sûr l'étendre avec une de vos propres classes (maclasse extends Exception…). Je vous laisse le soin de l'étudier en détail.
Voilà, nous avons à peu près fait le tour du nouveau modèle objet. Il y a encore quelques autres concepts que je n'aborderai pas dans ce tutoriel, car je dois moi-même encore les approfondir.