Apprendre la POO en PHP : de la théorie au code

Tu connais déjà PHP. Tu écris des fonctions, tu boucles sur des tableaux, tu sors des pages qui marchent. Puis un jour tu ouvres un projet Symfony, ou juste le code d'un collègue, et tout est en classes, en interfaces, avec des $this partout et des use App\Service\Truc; en haut de chaque fichier. Tu comprends vaguement, mais tu ne saurais pas l'écrire toi-même proprement.

C'est le moment de passer à la POO. Et la bonne nouvelle, c'est que tu n'as pas à tout réapprendre : la POO en PHP, c'est surtout une syntaxe précise posée sur une poignée de concepts. Si tu attaques cette syntaxe dans le bon ordre, après avoir compris les concepts, la transition est rapide. Si tu l'attaques dans le désordre, tu vas recopier des classes sans savoir pourquoi elles sont faites comme ça.

Comprendre les concepts d'abord (sinon la syntaxe ne sert à rien)

Première erreur classique : apprendre la POO par PHP. On lit la doc des classes, on note la syntaxe du mot-clé class, des extends, des interface, et on croit avoir compris la POO. En réalité on a juste mémorisé du vocabulaire PHP.

La POO est d'abord une façon de penser, indépendante du langage. Un objet regroupe des données et le comportement qui agit dessus. L'encapsulation cache l'intérieur pour qu'on ne dépende que de l'extérieur. L'héritage et le polymorphisme permettent de traiter différentes choses de la même façon. Ces idées sont les mêmes en PHP, en Java, en Python. Tant qu'elles ne sont pas claires, la syntaxe PHP n'est qu'une suite de mots-clés que tu copies.

Avant de toucher à la syntaxe PHP, je conseille donc de poser les concepts au propre, langage à part. C'est exactement l'objet du cours de POO agnostique de ce site : il explique le pourquoi des objets, de l'encapsulation, de l'héritage et des interfaces sans s'attacher à un langage. Une fois ça en tête, PHP devient simplement la mise en oeuvre concrète, et le reste de cet article prend tout son sens.

Le bon ordre en PHP

La POO en PHP a une progression naturelle, où chaque brique s'appuie sur la précédente. Une interface n'a aucun sens tant qu'on n'a pas compris une classe ; un trait résout un problème qu'on ne voit qu'après avoir buté sur l'héritage. Voici l'ordre qui évite de tourner en rond.

Le parcours d'apprentissage de la POO en PHP en six étapes : classes et objets, visibilité et encapsulation, héritage et classes abstraites, interfaces et polymorphisme, traits, puis namespaces et autoloading PSR-4 avec Composer. 1. Classes & objets $this, propriétés typées 2. Visibilité encapsulation 3. Héritage classes abstraites 4. Interfaces polymorphisme 5. Traits réutilisation 6. Namespaces autoloading PSR-4
Six étapes, chacune appuyée sur la précédente. On ne saute pas une marche.

1. Classes et objets ($this, propriétés typées)

Le point de départ. Une classe est un moule, un objet est ce qu'on coule dedans. En PHP moderne, on type les propriétés et on initialise dans le constructeur, souvent avec la promotion de propriétés qui évite la répétition.

<?php

class Compte
{
    public function __construct(
        private string $titulaire,
        private float $solde = 0.0,
    ) {}

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

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

$c = new Compte('Odilon', 100.0);
$c->crediter(50.0);
echo $c->solde(); // 150

$this désigne l'objet courant, celui sur lequel la méthode a été appelée. Tant que ce mot-clé n'est pas limpide, rien d'autre ne le sera : c'est lui qui relie le comportement aux données de l'instance.

2. Visibilité et encapsulation

Tu as remarqué le private au-dessus. C'est la visibilité : public est accessible de partout, private uniquement depuis la classe, protected depuis la classe et ses enfants. Ce n'est pas une décoration. C'est ce qui garantit qu'on ne triche pas avec le solde de l'extérieur.

L'idée concrète : si $solde est private, personne ne peut écrire $c->solde = -1000;. On passe forcément par crediter() ou un futur debiter(), qui peuvent valider. C'est l'encapsulation en pratique : la classe protège ses propres règles. Apprends à mettre private par défaut et à n'exposer que ce qui doit l'être.

3. Héritage et classes abstraites

Quand deux classes partagent du comportement, l'héritage permet à l'une d'étendre l'autre avec extends. Une classe abstraite, elle, définit un squelette qu'on ne peut pas instancier seul : elle impose à ses enfants d'implémenter certaines méthodes.

<?php

abstract class Notification
{
    public function __construct(protected string $destinataire) {}

    abstract public function envoyer(string $message): void;
}

class NotificationEmail extends Notification
{
    public function envoyer(string $message): void
    {
        // envoi réel de l'e-mail à $this->destinataire
    }
}

Attention au piège classique : on abuse vite de l'héritage pour partager du code. Si la relation n'est pas un vrai « est un » (un email est une notification), il vaut souvent mieux une interface ou un trait. L'héritage profond se paie cher en maintenabilité.

4. Interfaces et polymorphisme

Une interface est un contrat : une liste de méthodes qu'une classe s'engage à fournir, sans dire comment. C'est le coeur du polymorphisme : du code qui dépend d'une interface fonctionne avec n'importe quelle classe qui la respecte, sans savoir laquelle.

<?php

interface MoyenPaiement
{
    public function payer(float $montant): bool;
}

class Carte implements MoyenPaiement
{
    public function payer(float $montant): bool { /* ... */ return true; }
}

class Paypal implements MoyenPaiement
{
    public function payer(float $montant): bool { /* ... */ return true; }
}

function encaisser(MoyenPaiement $moyen, float $montant): void
{
    $moyen->payer($montant); // peu importe la classe concrète
}

C'est l'étape qui transforme ta façon d'écrire du code. Tu cesses de dépendre de classes précises pour dépendre de contrats. C'est ce qui rend le code testable et remplaçable, et c'est exactement ce que fait l'injection de dépendances de Symfony sous le capot.

5. Traits

PHP ne permet pas l'héritage multiple : une classe ne peut extends qu'une seule autre. Le trait résout ça. C'est un bloc de méthodes qu'on injecte dans plusieurs classes sans relation d'héritage, avec use à l'intérieur de la classe.

<?php

trait Horodatable
{
    public ?\DateTimeImmutable $creeLe = null;

    public function horodater(): void
    {
        $this->creeLe = new \DateTimeImmutable();
    }
}

class Article
{
    use Horodatable;
}

class Commentaire
{
    use Horodatable;
}

Le trait arrive en cinquième parce qu'il ne fait sens qu'une fois l'héritage compris : c'est précisément la réponse à ses limites. Utilisé avec mesure, il évite la duplication. Utilisé à tort et à travers, il devient un fourre-tout qui cache d'où vient chaque méthode.

6. Namespaces et autoloading (PSR-4, Composer)

Dernière marche, et celle qui sépare le code jouet du code de projet réel. Un namespace range les classes pour éviter les collisions de noms ; l'autoloading PSR-4 charge automatiquement le bon fichier quand tu utilises une classe, sans aucun require manuel.

<?php
// fichier : src/Paiement/Carte.php

namespace App\Paiement;

class Carte implements MoyenPaiement
{
    // ...
}

Avec un composer.json qui mappe le namespace au dossier, Composer génère l'autoloader :

{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

La convention PSR-4 est simple : le namespace suit l'arborescence des dossiers, une classe par fichier portant le même nom. Une fois ça en place, tu écris use App\Paiement\Carte; et la classe est là, sans te soucier du chemin du fichier. C'est ce mécanisme qui fait tourner Symfony, Laravel et tout l'écosystème Composer.

Pratiquer avec du code exécutable

Lire des classes ne suffit pas, pas plus que lire du SQL n'apprend à écrire des requêtes. La POO se comprend en la faisant tourner : modifier une visibilité et voir l'erreur apparaître, implémenter une interface et constater que le polymorphisme marche, casser un héritage pour comprendre à quoi sert parent::.

C'est ce que j'ai construit dans le cours PHP orienté objet, avec du code exécutable de ce site : chaque étape ci-dessus a sa leçon, avec des exemples que tu exécutes directement dans la page, des exercices et des quiz. Tu écris des classes dès la première leçon, dans l'ordre qui marche. Si les bases de PHP elles-mêmes sont encore fragiles, commence par le cours PHP de base avant d'attaquer l'objet.

Conclusion

Passer du PHP procédural à la POO n'est pas un changement de langage, c'est un changement de regard. Le piège n'est pas la syntaxe, qui est courte et régulière : c'est de l'apprendre avant d'avoir compris ce qu'elle exprime. Pose les concepts d'abord, déroule la syntaxe PHP dans l'ordre, et chaque mot-clé deviendra l'évidence d'une idée que tu as déjà comprise.

Le jour où tu rouvriras ce projet Symfony, les $this, les interfaces et les use ne seront plus du bruit. Ce sera juste de la POO, écrite en PHP.

Commentaires (0)