Informatique · PHP

La programmation orientée objet en PHP

Un élève, un compte en banque, une connexion à une base de données : en programmation orientée objet, on décrit une sorte de chose une fois, dans une classe, puis on crée autant d'objets qu'il en faut. Chacun garde ses propres données et sait faire ce que sa classe prévoit. PHP fournit d'ailleurs ses propres classes, comme PDO, qui parle aux bases de données.

class Eleve $prenom · $notes moyenne() new 'Léa' [5.5, 5] 'Noah' [3] 'Zoé' [6, 5.5]
Si tu écris $b = $a; avec un objet, puis que tu changes $b->prenom, est-ce que $a change aussi ? Garde ta réponse : la notion 2 la vérifie.

Après cette fiche, tu sais

  • écrire une classe avec propriétés typées, constructeur et méthodes, et créer des objets avec new ;
  • prévoir ce que fait $b = $a avec un objet, et quand utiliser clone ;
  • protéger les données avec private, protected et des méthodes qui valident ;
  • employer l'héritage et les interfaces, et interroger une base avec PDO et des requêtes préparées.
1Notion 1 · Classe et objet

Une classe décrit, les objets existent

Une classe commence par le mot-clé class, puis son nom, puis des accolades qui contiennent ses propriétés (les données) et ses méthodes (les fonctions). new crée un objet, une instance de la classe ; dans une méthode, $this désigne l'objet sur lequel elle est appelée.1

La classe ElevePHP
<?php
class Eleve
{
    public string $prenom;
    public array $notes = [];

    public function __construct(string $prenom)
    {
        $this->prenom = $prenom;
    }

    public function ajouterNote(float $note): void
    {
        $this->notes[] = $note;
    }

    public function moyenne(): float
    {
        return round(array_sum($this->notes) / count($this->notes), 2);
    }
}

$lea = new Eleve('Léa');
$lea->ajouterNote(5.5);
$lea->ajouterNote(5);
$noah = new Eleve('Noah');
$noah->ajouterNote(3);
echo $lea->prenom, ' : ', $lea->moyenne(), "\n";
echo $noah->prenom, ' : ', $noah->moyenne(), "\n";
Léa : 5.25
Noah : 3
Scène 1

Le plan et les objets

Fais défiler : le code ci-dessus s'exécute ligne après ligne. Deux objets naissent du même plan, et chacun garde ses notes.

Scène 1 · Classe et objets

Un plan, pas encore d'élève

class Eleve le plan string $prenomarray $notes = [] __construct()ajouterNote()moyenne() $this

Propriété

Une variable propre à chaque objet, déclarée dans la classe ; on peut la typer (string, array…) depuis PHP 7.4.1

Méthode

Une fonction de la classe, appelée sur un objet avec -> : $lea->moyenne().1

Constructeur

__construct() est appelée automatiquement sur chaque nouvel objet : l'endroit pour l'initialiser.1

Promotion de constructeur

Depuis PHP 8.0, public function __construct(public string $prenom) {} déclare la propriété et l'initialise en une ligne.1

Piège : oublier $this->

Dans une méthode, $notes[] = $note; crée une variable locale $notes, qui disparaît à la fin de la méthode : la propriété de l'objet ne change pas. Pour toucher la propriété, il faut $this->notes[] = $note;.

Piège : la moyenne sans note

Appelée sur un élève sans note, moyenne() divise par count([]), soit 0 : DivisionByZeroError. Le corrigé 2 montre comment l'éviter.

2Notion 2 · Références

$b = $a ne copie pas l'objet

Une variable « objet » ne contient pas l'objet lui-même, seulement un identifiant qui permet de le retrouver. Affecter l'objet à une autre variable, le passer à une fonction ou le renvoyer copie cet identifiant : les deux variables désignent alors le même objet.3 Pour un objet indépendant, on écrit clone.3

Deux variables, un objetPHP
<?php
class Eleve
{
    public function __construct(public string $prenom) {}
}

$a = new Eleve('Léa');
$b = $a;                  // le même objet
$b->prenom = 'Lina';
echo $a->prenom, "\n";

$c = clone $a;            // un nouvel objet, copie de $a
$c->prenom = 'Zoé';
echo $a->prenom, "\n";
var_dump($a === $b, $a === $c);
Lina
Lina
bool(true)
bool(false)
Scène 2

Des flèches vers les objets

Fais défiler : chaque variable est une étiquette qui pointe vers un objet numéroté, comme dans var_dump (object(Eleve)#1).

Scène 2 · Identifiants d'objets

Un objet, une variable

variables objets

Réponse à ma question : oui. $b = $a copie l'identifiant, pas l'objet : les deux variables mènent au même élève. Pour une vraie copie, clone.
=== entre objets

Vrai seulement s'il s'agit de la même instance.

Objet passé à une fonction

La fonction reçoit une copie de l'identifiant : si elle change une propriété, l'objet de l'appelant change.3

clone

Copie superficielle : les propriétés sont copiées, mais un objet rangé dans une propriété reste partagé, sauf si la méthode __clone() le clone à son tour.3

Tableaux, eux

Rappel de la fiche précédente : un tableau affecté ou passé à une fonction est copié. Les objets se comportent autrement.

Exercice · à toi

Que va afficher ce code ?

Quatre extraits, tous exécutés avec PHP 8.4. La classe Eleve est celle de l'exemple ci-dessus.

3Notion 3 · Encapsulation

public, protected, private : qui a le droit d'y toucher

Un membre public est accessible partout ; protected, seulement dans la classe, ses classes enfants et ses parents ; private, seulement dans la classe qui le déclare.1 On garde les données private et on n'expose que des méthodes qui les vérifient : c'est l'encapsulation.

Un compte qui se protègePHP
<?php
class Compte
{
    private float $solde = 0;

    public function deposer(float $montant): void
    {
        if ($montant <= 0) {
            throw new InvalidArgumentException('Montant positif attendu');
        }
        $this->solde += $montant;
    }

    public function retirer(float $montant): void
    {
        if ($montant > $this->solde) {
            throw new RuntimeException('Solde insuffisant');
        }
        $this->solde -= $montant;
    }

    public function solde(): float
    {
        return $this->solde;
    }
}

$compte = new Compte();
$compte->deposer(200);
$compte->retirer(50);
echo $compte->solde(), "\n";
try {
    $compte->retirer(500);
} catch (RuntimeException $e) {
    echo 'Refusé : ', $e->getMessage(), "\n";
}
$compte->solde = 1000000;   // interdit depuis l'extérieur
150
Refusé : Solde insuffisant
Fatal error: Uncaught Error: Cannot access private property Compte::$solde
Scène 3

Trois portes, trois points de vue

Fais défiler : on regarde la même classe depuis l'extérieur, depuis une classe enfant, puis depuis la classe elle-même.

Scène 3 · Visibilité

Depuis l'extérieur

class Compte

throw

Une méthode refuse une valeur impossible en lançant une exception ; l'appelant la rattrape avec try … catch.

readonly

Depuis PHP 8.1, une propriété readonly s'initialise une seule fois, dans sa classe ; toute modification ensuite lève Cannot modify readonly property.1

Une subtilitéLa visibilité vaut par classe, pas par objet : une méthode de Compte peut lire le private $solde d'un autre objet Compte.1
Exercice · classer

Accepté ou refusé ?

Compte a un private $solde, un protected $titulaire et une méthode publique solde() ; CompteEpargne extends Compte ; Forme est une interface. Chaque ligne a été testée avec PHP 8.4.

4Notion 4 · Héritage

extends : hériter et spécialiser

Une classe enfant hérite de toutes les méthodes et propriétés public et protected de son parent ; elle peut en ajouter, et redéfinir une méthode héritée. Les membres private du parent lui restent inaccessibles.2

Un compte d'épargnePHP
<?php
class Compte
{
    protected float $solde = 0;

    public function deposer(float $montant): void
    {
        $this->solde += $montant;
    }

    public function solde(): float
    {
        return $this->solde;
    }
}

class CompteEpargne extends Compte
{
    public function __construct(private float $taux) {}

    public function ajouterInterets(): void
    {
        $this->deposer(round($this->solde * $this->taux, 2));
    }
}

$e = new CompteEpargne(0.0125);   // taux fictif de 1,25 %
$e->deposer(1000);
$e->ajouterInterets();
echo $e->solde(), "\n";
var_dump($e instanceof Compte);
1012.5
bool(true)
Scène 4

La classe enfant éclatée

Fais défiler : les membres hérités descendent de Compte dans CompteEpargne, puis servent.

Scène 4 · Héritage

Deux classes

class Compte extends class CompteEpargne

parent::

Une méthode redéfinie peut appeler la version du parent : parent::deposer($montant). Si l'enfant a son propre constructeur, celui du parent n'est appelé que par parent::__construct().1

instanceof

$e instanceof Compte vaut true : un compte d'épargne est un compte.

final

Une classe final ne peut pas avoir d'enfant ; PHP refuse class Fille extends Base si Base est final.

Visibilité héritée

Un enfant peut assouplir une visibilité (protected vers public), jamais la restreindre.2

Piège : private chez le parent

Si $solde était private dans Compte, $this->solde dans CompteEpargne ne la verrait pas : Warning: Undefined property: CompteEpargne::$solde. C'est à cela que sert protected.2

5Notion 5 · Interfaces

Un contrat, plusieurs façons de le remplir

Une interface dit quelles méthodes une classe doit avoir, sans dire comment elles marchent. Une classe qui l'implements doit les écrire toutes, sinon PHP refuse la classe ; elle peut implémenter plusieurs interfaces.2 Une fonction qui attend l'interface accepte alors n'importe quel objet qui la respecte : c'est le polymorphisme.

Des formes et leur airePHP
<?php
interface Forme
{
    public function aire(): float;
}

class Rectangle implements Forme
{
    public function __construct(private float $l, private float $h) {}

    public function aire(): float
    {
        return $this->l * $this->h;
    }
}

class Cercle implements Forme
{
    public function __construct(private float $r) {}

    public function aire(): float
    {
        return round(M_PI * $this->r ** 2, 2);
    }
}

function aireTotale(array $formes): float
{
    $total = 0;
    foreach ($formes as $f) {
        $total += $f->aire();   // chaque objet répond à sa façon
    }
    return $total;
}

echo aireTotale([new Rectangle(3, 4), new Cercle(1)]);
15.14

Fais défiler : la même ligne $f->aire() exécute deux méthodes différentes.

interface Forme : aire() Rectangle(3, 4) aire() : l × h Cercle(1) aire() : π × r²
Étape 1 sur 4 · le contrat

Rectangle et Cercle implements Forme : chacune a sa méthode aire(), écrite à sa façon.2

Étape 2 sur 4 · premier tour

$f est un Rectangle : $f->aire() exécute la méthode de Rectangle, 3 × 4 = 12.

Étape 3 sur 4 · second tour

$f est un Cercle : la même ligne exécute la méthode de Cercle, π × 1², arrondi à 3,14.

Étape 4 sur 4 · le total

aireTotale renvoie 15,14 sans jamais savoir quelles classes existent : il lui suffit que chaque objet respecte le contrat Forme.

interfaceclasse abstraite
Contient du codenon, seulement des signatures de méthodesoui, des méthodes complètes et des méthodes abstraites
Instanciablenonnon
Une classe peut en avoirplusieurs (implements A, B)une seule (extends)
Quand l'utiliserpour un contrat commun à des classes sans lienpour partager du code entre classes proches

Une classe abstraite ne peut pas être instanciée, et toute classe qui contient une méthode abstraite doit l'être ; ses enfants doivent écrire toutes les méthodes abstraites.2 PHP répond Cannot instantiate interface Forme à new Forme().

6Notion 6 · PDO

Un objet de tous les jours : PDO

PDO (PHP Data Objects) est une classe fournie par PHP pour parler aux bases de données : MySQL, PostgreSQL, SQLite… On crée un objet PDO, puis on prépare des requêtes. Depuis PHP 8.0, une erreur SQL lève par défaut une PDOException.4

Une requête préparée contient des marqueurs (:classe ou ?) ; les valeurs sont transmises à part, avec execute(). La base distingue alors toujours le code SQL des données : une saisie ne peut pas changer la structure de la requête.45

Lire les élèves d'une classePHP
<?php
$pdo = new PDO('sqlite::memory:');   // MySQL : 'mysql:host=localhost;dbname=ecole;charset=utf8mb4'
$pdo->exec("CREATE TABLE eleves (id INTEGER PRIMARY KEY, prenom TEXT, classe TEXT)");
$pdo->exec("INSERT INTO eleves (prenom, classe) VALUES ('Léa', '2M3'), ('Noah', '2M3'), ('Elias', '2M1')");

$classe = $_GET['classe'] ?? '2M3';   // vient de l'URL : jamais collé dans le SQL
$stmt = $pdo->prepare('SELECT prenom FROM eleves WHERE classe = :classe ORDER BY prenom');
$stmt->execute(['classe' => $classe]);
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $ligne) {
    echo htmlspecialchars($ligne['prenom']), "\n";
}
Léa
Noah
Scène 5

La saisie qui change le sens de la requête

Fais défiler : la même saisie malveillante, d'abord collée dans le SQL, puis passée à une requête préparée. Tout a été exécuté avec PDO et SQLite.

Scène 5 · Injection SQL

La saisie

saisie : ?classe= ' OR '1'='1 lignes renvoyées

La faille, puis la paradePHP
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE eleves (id INTEGER PRIMARY KEY, prenom TEXT, classe TEXT)");
$pdo->exec("INSERT INTO eleves (prenom, classe) VALUES ('Léa', '2M3'), ('Noah', '2M3'), ('Elias', '2M1')");
$classe = "' OR '1'='1";

// DANGER : la saisie est collée dans la requête
$sql = "SELECT prenom FROM eleves WHERE classe = '$classe' ORDER BY prenom";
foreach ($pdo->query($sql) as $ligne) {
    echo $ligne['prenom'], "\n";
}

// Requête préparée : la saisie reste une valeur
$stmt = $pdo->prepare('SELECT prenom FROM eleves WHERE classe = :classe ORDER BY prenom');
$stmt->execute(['classe' => $classe]);
echo count($stmt->fetchAll()), " ligne(s)\n";
Elias
Léa
Noah
0 ligne(s)
Piège : ce qu'un marqueur ne peut pas remplacer

Un marqueur remplace une valeur, jamais un nom de table, de colonne ou un ordre de tri (ASC, DESC). Si l'utilisateur choisit la colonne de tri, on compare son choix à une liste de valeurs autorisées.5

Les deux réflexes de sécurité se répondent : requête préparée pour envoyer une donnée à la base, htmlspecialchars() pour l'afficher dans la page.
Exercice · choisir

Requête sûre ?

Trois questions sur PDO et l'injection SQL.

Habitudes de pro

Sept bonnes pratiques

  • une classe, une responsabilité ; un nom au singulier (Eleve, Compte) ;
  • propriétés private et typées par défaut, readonly quand elles ne doivent plus changer ;
  • valider dans les méthodes et lancer une exception pour une valeur impossible ;
  • protected seulement quand un enfant en a vraiment besoin ;
  • se souvenir que $b = $a partage l'objet, et utiliser clone pour une copie ;
  • typer les paramètres par une interface (Forme $f) plutôt que par une classe précise ;
  • avec PDO : toujours prepare() et execute() pour les valeurs, jamais de concaténation.
Entraînement

Trois exercices corrigés

Tous les corrigés ont été exécutés avec PHP 8.4.

1Écris une classe Rectangle avec un constructeur promu (largeur, hauteur) et les méthodes aire() et perimetre().Voir le corrigé
<?php
class Rectangle
{
    public function __construct(
        private float $largeur,
        private float $hauteur,
    ) {}

    public function aire(): float
    {
        return $this->largeur * $this->hauteur;
    }

    public function perimetre(): float
    {
        return 2 * ($this->largeur + $this->hauteur);
    }
}

$r = new Rectangle(3, 4.5);
echo $r->aire(), ' / ', $r->perimetre();
13.5 / 15

Les deux propriétés sont private : on ne peut plus fausser une mesure depuis l'extérieur. La virgule après le dernier paramètre est permise depuis PHP 8.0.

2Améliore Eleve : notes privées, refus d'une note hors de l'échelle 1 à 6, et une moyenne qui vaut null quand il n'y a encore aucune note.Voir le corrigé
<?php
class Eleve
{
    private array $notes = [];

    public function __construct(public string $prenom) {}

    public function ajouterNote(float $note): void
    {
        if ($note < 1 || $note > 6) {
            throw new InvalidArgumentException("Note $note hors de l'échelle 1 à 6");
        }
        $this->notes[] = $note;
    }

    public function moyenne(): ?float
    {
        if (count($this->notes) === 0) {
            return null;
        }
        return round(array_sum($this->notes) / count($this->notes), 2);
    }
}

$zoe = new Eleve('Zoé');
var_dump($zoe->moyenne());
$zoe->ajouterNote(6);
$zoe->ajouterNote(5.5);
var_dump($zoe->moyenne());
try {
    $zoe->ajouterNote(7);
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}
NULL
float(5.75)
Note 7 hors de l'échelle 1 à 6

?float annonce « un float ou null ». La division par zéro ne peut plus arriver, et une note impossible n'entre jamais dans l'objet.

3Cette ligne cherche la classe d'un élève à partir d'un prénom saisi. Pourquoi est-elle dangereuse, et comment la corriger ?Voir le corrigé
$ligne = $pdo->query("SELECT classe FROM eleves WHERE prenom = '$prenom'")->fetch();

La saisie est collée dans le SQL : une apostrophe suffit à changer la requête. Version corrigée, avec un marqueur positionnel :

<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec("CREATE TABLE eleves (id INTEGER PRIMARY KEY, prenom TEXT, classe TEXT)");
$pdo->exec("INSERT INTO eleves (prenom, classe) VALUES ('Léa', '2M3'), ('Noah', '2M3'), ('Elias', '2M1')");
$prenom = 'Noah';

$stmt = $pdo->prepare('SELECT classe FROM eleves WHERE prenom = ?');
$stmt->execute([$prenom]);
$ligne = $stmt->fetch(PDO::FETCH_ASSOC);
echo $ligne ? $ligne['classe'] : 'introuvable';
2M3
À retenir

La fiche en huit lignes

Classe et objetclass décrit, new crée une instance ; $this est l'objet sur lequel la méthode est appelée.
Constructeur__construct() initialise ; la promotion (PHP 8.0) déclare et remplit les propriétés en une ligne.
Références$b = $a copie l'identifiant : même objet. clone crée une copie (superficielle).
Visibilitépublic partout, protected dans la famille de classes, private dans la seule classe qui déclare.
Encapsulationdonnées private, méthodes qui valident et lancent une exception si besoin ; readonly pour figer.
Héritageextends transmet public et protected ; parent:: appelle la version du parent.
Interfacesun contrat de méthodes ; implements oblige à toutes les écrire ; une fonction qui attend l'interface accepte toute classe qui la respecte.
PDOprepare() avec :marqueurs, puis execute() avec les valeurs ; erreurs en PDOException depuis PHP 8.0.
Dernière étape : le quiz. Douze questions tirées au hasard, différentes à chaque partie. Si tu te trompes, lis bien l'explication.
Quiz final

Teste-toi

Sources

  1. The PHP Group, PHP Manual, « Classes and Objects » : « The Basics », « Properties » (types, readonly), « Constructors and Destructors » (promotion, parent::__construct), « Visibility ». bases, propriétés, constructeurs, visibilité (php.net)
  2. The PHP Group, PHP Manual : « Object Inheritance », « Object Interfaces », « Class Abstraction ». héritage, interfaces, classes abstraites (php.net)
  3. The PHP Group, PHP Manual : « Objects and references », « Object Cloning ». références, clone (php.net)
  4. The PHP Group, PHP Manual, PDO : « Prepared statements », « Errors and error handling » (ERRMODE_EXCEPTION par défaut depuis PHP 8.0). requêtes préparées, erreurs (php.net)
  5. OWASP, SQL Injection Prevention Cheat Sheet (requêtes paramétrées, limites pour les noms de tables et de colonnes). cheatsheetseries.owasp.org

Fiche écrite et sourcée en octobre 2026. Tout le code a été exécuté avec PHP 8.4 ; les requêtes, avec PDO et SQLite.

Glisser pour continuer vers Les bases de PL/pgSQL
Collège 2