Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Les classes

Une classe PHP moderne est courte, typĂ©e et presque entiĂšrement immuable. Le constructeur dĂ©clare les propriĂ©tĂ©s, les types sont vĂ©rifiĂ©s Ă  l’exĂ©cution, et l’essentiel du code rĂ©pĂ©titif que vous connaissez de Java, ou de PHP 5, a disparu. L’exemple ci-dessous montre une classe complĂšte :

<?php
declare(strict_types=1);

namespace App\Billing;

final class Invoice
{
    private array $lines = [];

    public function __construct(
        public readonly string $number,
        public readonly \DateTimeImmutable $issuedAt,
    ) {
    }

    public function addLine(string $label, int $cents): static
    {
        $this->lines[] = ['label' => $label, 'cents' => $cents];

        return $this;
    }

    public function total(): int
    {
        return array_sum(array_column($this->lines, 'cents'));
    }
}

$invoice = new Invoice('2026-0042', new \DateTimeImmutable('2026-09-14'));
$invoice->addLine('Hosting', 1200)->addLine('Support', 800);

echo $invoice->number, ': ', $invoice->total(), PHP_EOL; // 2026-0042: 2000

En la lisant de haut en bas, vous rencontrez d’abord namespace, qui place la classe dans App\Billing, de sorte que son nom complet est App\Billing\Invoice (le chapitre Namespaces, Composer et autoloading explique comment ce nom correspond Ă  un fichier). final interdit l’hĂ©ritage, et le code PHP moderne rend les classes final par dĂ©faut pour ne les ouvrir qu’à dessein. Le constructeur n’a pas de corps, parce qu’écrire public readonly string $number dans la liste des paramĂštres dĂ©clare la propriĂ©tĂ©, la type et l’affecte en une seule ligne. Cette promotion de propriĂ©tĂ©s dans le constructeur (PHP 8.0) a retirĂ© l’essentiel de la cĂ©rĂ©monie des classes PHP. readonly (PHP 8.1) rend la propriĂ©tĂ© affectable une seule fois, dans le constructeur, et plus jamais ensuite. Enfin, static comme type de retour signifie « la classe de l’objet sur lequel la mĂ©thode a Ă©tĂ© appelĂ©e », ce qui est exactement ce qu’une mĂ©thode fluide veut promettre.

L’accĂšs aux membres passe par deux symboles : -> atteint un membre d’instance, :: atteint un membre statique ou une constante. $this est l’objet courant, et vous l’écrivez toujours puisqu’il n’y a pas de this implicite.

Les objets circulent par identifiant

Les tableaux se copient quand vous les affectez, comme le chapitre Les tableaux l’explique, mais les objets suivent la rĂšgle inverse. Affecter ou passer un objet copie un identifiant qui dĂ©signe le mĂȘme objet, comme le font Java, Python et JavaScript :

<?php
declare(strict_types=1);

final class Cart
{
    public array $items = [];
}

function addApple(Cart $cart): void
{
    $cart->items[] = 'apple';
}

$cart = new Cart();
addApple($cart);
echo count($cart->items), PHP_EOL; // 1, the caller sees the change

Ce comportement ne doit rien au & des paramĂštres par rĂ©fĂ©rence, qui sont un mĂ©canisme diffĂ©rent, rĂ©servĂ© aux variables, et dont vous n’avez presque jamais besoin avec des objets.

clone $cart fait une copie superficielle, c’est-Ă -dire un nouvel objet dont les propriĂ©tĂ©s contiennent les mĂȘmes valeurs, de sorte qu’un objet rangĂ© Ă  l’intĂ©rieur est partagĂ© entre les deux copies. DĂ©finissez __clone() si la copie a besoin de ses propres objets internes.

Pour comparer deux objets, == compare l’état propriĂ©tĂ© par propriĂ©tĂ©, tandis que === demande si les deux cĂŽtĂ©s sont le mĂȘme objet, ce qui explique que $a === clone $a vaille false.

À gauche : deux variables qui tiennent deux boĂźtes sĂ©parĂ©es Ă©tiquetĂ©es array, chacune avec son propre contenu. À droite : deux variables qui tiennent des ficelles attachĂ©es Ă  une seule boĂźte Ă©tiquetĂ©e object, de sorte que les deux tirent sur la mĂȘme chose

L’immuabilitĂ© sans accesseurs

La classe PHP 5 classique avait une propriété privée, un getter, et parfois un setter. Ce motif a été remplacé par des fonctionnalités récentes que vous verrez dans toutes les bases de code écrites aprÚs 2024.

La premiĂšre est readonly, que vous avez dĂ©jĂ  rencontrĂ©. Quand toutes les propriĂ©tĂ©s d’une classe sont readonly, marquez plutĂŽt la classe elle-mĂȘme (PHP 8.2) :

<?php
declare(strict_types=1);

final readonly class Money
{
    public function __construct(
        public int $amount,
        public string $currency,
    ) {
    }

    public function add(Money $other): self
    {
        return new self($this->amount + $other->amount, $this->currency);
    }
}

Chaque propriété est publique et personne ne peut la modifier, ce qui vous donne un objet valeur sans le moindre getter.

La visibilitĂ© asymĂ©trique (PHP 8.4) laisse l’extĂ©rieur lire une propriĂ©tĂ© que seule la classe peut Ă©crire :

// PHP 8.4
final class Counter
{
    public private(set) int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
}

$c = new Counter();
$c->increment();
echo $c->count; // 1
$c->count = 5;  // Error: Cannot modify private(set) property Counter::$count

Le getter n’a plus de raison d’ĂȘtre. LĂ  oĂč il vous faut de la logique Ă  la lecture ou Ă  l’écriture, les hooks de propriĂ©tĂ© (PHP 8.4) l’attachent Ă  la propriĂ©tĂ© elle-mĂȘme, comme une propriĂ©tĂ© C# ou le @property de Python :

// PHP 8.4
final class User
{
    public string $email {
        set(string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new \InvalidArgumentException("Invalid email: $value");
            }
            $this->email = strtolower($value);
        }
    }

    public string $domain {
        get => substr($this->email, strpos($this->email, '@') + 1);
    }
}

$u = new User();
$u->email = 'Ada@Example.org';
echo $u->domain; // example.org

Pour l’appelant, ce sont des propriĂ©tĂ©s ordinaires, alors qu’à l’intĂ©rieur set valide et normalise pendant que get calcule. Une propriĂ©tĂ© avec un seul hook get et sans stockage est simplement une propriĂ©tĂ© calculĂ©e.

Interfaces, classes abstraites, traits

Les interfaces et les classes abstraites fonctionnent comme en Java et en C# : une interface liste des signatures de mĂ©thodes et des constantes, une classe abstraite peut porter de l’implĂ©mentation, une classe implĂ©mente plusieurs interfaces et Ă©tend un seul parent. #[\Override] (PHP 8.3) sur une mĂ©thode fait vĂ©rifier Ă  PHP que le parent la possĂšde vraiment, ce qui attrape les fautes de frappe dans les redĂ©finitions.

Les traits, eux, n’ont pas d’équivalent exact dans la plupart des langages. Un trait est un bloc de mĂ©thodes et de propriĂ©tĂ©s que le compilateur copie dans chaque classe qui l’utilise avec use. Les mixins de Ruby en sont le repĂšre le plus proche, alors que les traits de Rust, malgrĂ© le nom, dĂ©crivent tout autre chose.

<?php
declare(strict_types=1);

trait HasTimestamps
{
    private ?\DateTimeImmutable $createdAt = null;

    public function touch(): void
    {
        $this->createdAt ??= new \DateTimeImmutable();
    }
}

final class Article
{
    use HasTimestamps;
}

$a = new Article();
$a->touch();

Les traits sont pratiques pour les utilitaires transversaux et faciles Ă  surutiliser, parce qu’une classe qui en utilise cinq vous impose cinq fichiers Ă  lire pour savoir ce qu’elle fait. PrĂ©fĂ©rez la composition quand vous le pouvez.

La diffĂ©rence entre static et self tient en quelques lignes : self nomme la classe oĂč le code est Ă©crit, tandis que static nomme la classe de l’objet Ă  l’exĂ©cution. Dans une fabrique statique dĂ©finie dans une classe parente, new static() construit donc la sous-classe rĂ©ellement appelĂ©e, alors que new self() construit toujours le parent.

Construction

PHP n’a qu’un constructeur par classe et ne connaĂźt pas la surcharge de mĂ©thodes. Le manque est comblĂ© par les arguments nommĂ©s (PHP 8.0) pour les paramĂštres optionnels, et par les fabriques statiques pour les autres façons de construire :

<?php
declare(strict_types=1);

final readonly class Period
{
    private function __construct(
        public \DateTimeImmutable $start,
        public \DateTimeImmutable $end,
    ) {
    }

    public static function fromStrings(string $start, string $end): self
    {
        return new self(new \DateTimeImmutable($start), new \DateTimeImmutable($end));
    }

    public static function year(int $year): self
    {
        return self::fromStrings("$year-01-01", "$year-12-31");
    }
}

$fy = Period::year(2026);
echo $fy->end->format('Y-m-d'), PHP_EOL; // 2026-12-31

Un constructeur privĂ© plus des fabriques publiques se lit aussi bien qu’un jeu de constructeurs surchargĂ©s, et chaque variante porte un nom.

Depuis PHP 8.4, vous pouvez chaĂźner un appel sur un objet fraĂźchement créé sans l’entourer de parenthĂšses : new Period(...)->start exigeait auparavant (new Period(...))->start.

Pour les objets immuables, « modifier » signifie « fabriquer une copie modifiĂ©e ». PHP 8.5 lui donne sa propre syntaxe : clone($money, ['amount' => 500]) renvoie une copie avec les propriĂ©tĂ©s listĂ©es remplacĂ©es. Les rĂšgles de visibilitĂ© habituelles s’appliquent, et une propriĂ©tĂ© readonly ne s’écrit que depuis l’intĂ©rieur de sa classe, donc l’appel vit dans une mĂ©thode withAmount() plutĂŽt que chez l’appelant. Avant 8.5, cette mĂȘme mĂ©thode clonait puis affectait dans __clone(), ce que PHP 8.3 a autorisĂ© pour les propriĂ©tĂ©s readonly.

Chaßnes, magie et métadonnées

Tout objet peut s’afficher lui-mĂȘme en implĂ©mentant __toString(). Le dĂ©clarer fait automatiquement implĂ©menter Stringable (PHP 8.0) Ă  la classe, de sorte que vous pouvez typer un paramĂštre string|Stringable et accepter les deux.

Les autres mĂ©thodes Ă  double tiret bas sont des crochets que le moteur appelle : __get et __set s’exĂ©cutent quand le code touche une propriĂ©tĂ© qui n’existe pas, __call quand il appelle une mĂ©thode qui n’existe pas. Les frameworks et les ORM s’en servent pour construire des API fluides et des modĂšles paresseux, et il suffit de savoir les reconnaĂźtre, sans les utiliser dans le code applicatif. Dans le mĂȘme esprit, crĂ©er une propriĂ©tĂ© jamais dĂ©clarĂ©e ($obj->foo = 1 sans $foo dans la classe) est dĂ©prĂ©ciĂ© depuis PHP 8.2 et doit devenir une erreur dans la prochaine version majeure, ce qui est une raison de plus de dĂ©clarer toutes vos propriĂ©tĂ©s.

Les attributs (PHP 8.0) sont des métadonnées structurées sur une classe, une méthode, une propriété ou un paramÚtre, lues par réflexion. Les annotations Java et les attributs C# sont le repÚre direct :

<?php
declare(strict_types=1);

#[\Attribute(\Attribute::TARGET_METHOD)]
final readonly class Route
{
    public function __construct(public string $path) {}
}

final class HomeController
{
    #[Route('/')]
    public function index(): string
    {
        return 'Hello';
    }
}

$method = new \ReflectionMethod(HomeController::class, 'index');
foreach ($method->getAttributes(Route::class) as $attribute) {
    echo $attribute->newInstance()->path, PHP_EOL; // /
}

Un attribut est lui-mĂȘme une classe marquĂ©e #[\Attribute], et rien ne se passe Ă  l’exĂ©cution tant que personne n’appelle getAttributes(), la mĂ©tadonnĂ©e restant inerte jusqu’à sa lecture. Le routage, la validation, la sĂ©rialisation et les frameworks de test reposent tous sur ce mĂ©canisme.

Invoice::class donne le nom pleinement qualifiĂ© sous forme de chaĂźne, ce que vous faites circuler au lieu d’écrire 'App\Billing\Invoice' en dur. $x instanceof Invoice vĂ©rifie le type Ă  l’exĂ©cution et vaut false, sans erreur, quand $x n’est pas un objet. Les objets paresseux (PHP 8.4) permettent d’instancier une classe sans exĂ©cuter son constructeur tant qu’une propriĂ©tĂ© n’est pas touchĂ©e, un outil pour les conteneurs d’injection de dĂ©pendances et les ORM plutĂŽt que pour le code de tous les jours.

Le langage n’a pas de gĂ©nĂ©riques, et une classe Collection contient du mixed aux yeux du moteur ; les docblocks @template dĂ©crits dans Le systĂšme de types donnent Ă  PHPStan et Psalm ce qui manque au moteur.

Le piĂšge

Une fonction qui reçoit un objet et « l’ajuste juste un peu » ajuste aussi l’objet de l’appelant. Si vous vouliez une modification locale, faites d’abord un clone, ou concevez la classe en readonly et renvoyez une nouvelle instance.

Gardez aussi en tĂȘte que readonly est superficiel : il gĂšle la case de la propriĂ©tĂ©, pas ce vers quoi elle pointe. Un readonly array $items ne peut pas ĂȘtre rĂ©affectĂ©, mais un readonly Cart $cart laisse quiconque tient $cart y pousser des articles. L’immuabilitĂ© de tout le graphe reste une dĂ©cision de conception, que le mot-clĂ© ne prend pas Ă  votre place.

Une fois les classes en main, reste Ă  voir comment PHP reprĂ©sente un ensemble fermĂ© de choix, ce qui est le sujet du chapitre ÉnumĂ©rations et match.