Chapitre 2.1 — Le PHP que vous ne connaissez pas
Partie 2 : PHP moderne — Chapitre 1/8 Version de référence : PHP 8.4 (déc. 2024), pour Symfony 7.4 (PHP 8.2+ requis).
⏱️ TL;DR
- Le PHP de 2026 n’a presque plus rien à voir avec le PHP « spaghetti » d’il y a 10 ans : il est typé, objet, avec enums, attributs,
readonly,match, promotion de constructeur… bref, très proche de TypeScript.- Depuis votre Symfony 5, PHP a gagné (8.0) types union, arguments nommés, attributs, promotion de constructeur,
match, nullsafe ; (8.1) enums,readonly, fibers, first-class callables ; (8.2) classesreadonly; (8.3) constantes de classe typées,#[\Override]; (8.4) property hooks, visibilité asymétrique.- Kit de survie syntaxique : variables préfixées
$,->pour accéder aux membres d’un objet,::pour le statique,.pour concaténer des chaînes (pas+!),arraysert à la fois de liste et de map, fonctions fléchéesfn() => ….- Ce chapitre est le panorama : chaque brique est approfondie dans les chapitres suivants.
- Objectif : que du code Symfony comme
#[Route] public function show(readonly Booking $b): Responsevous paraisse familier, pas hostile.
🎯 Objectifs
- Faire le reset mental : arrêter d’imaginer PHP comme en 2010.
- Connaître, version par version, les nouveautés PHP 8.0 → 8.4 qui comptent pour Symfony.
- Maîtriser le kit de survie syntaxique pour lire du PHP sans buter sur
$,->,::,.. - Situer ce qui sera approfondi dans les 7 chapitres suivants.
1. Le reset mental
Si votre image de PHP, c’est mysql_query(), des <?php echo ?> partout et zéro type, oubliez-la. Le PHP moderne ressemble à ceci :
<?php
// src/Domain/Booking.php
declare(strict_types=1);
namespace App\Domain;
enum BookingStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
case Cancelled = 'cancelled';
}
final readonly class Booking
{
public function __construct(
public int $id,
public string $customerEmail,
public \DateTimeImmutable $start,
public BookingStatus $status = BookingStatus::Pending,
) {}
public function isActive(): bool
{
return $this->status !== BookingStatus::Cancelled;
}
}Regardez bien : types partout, une enum, une classe readonly (immuable), une promotion de constructeur (les propriétés déclarées directement dans les paramètres), des valeurs par défaut typées. Un développeur TypeScript lit ça sans peine — c’est presque du TS avec $ devant les variables.
💡 Pour un dev Next.js : l’équivalent TypeScript de cette classe serait un
type+ uneclassavec desreadonly, une union de littéraux pour le statut, et un constructeur. La distance conceptuelle est minime. La vraie différence est syntaxique (le$, le.de concaténation) et d’exécution (chaque requête repart d’un état vierge — pas de state partagé entre requêtes).
2. Kit de survie syntaxique (10 minutes qui débloquent tout)
Avant les nouveautés, réglons la syntaxe de base qui fait trébucher un dev JS :
| Élément | PHP | JS/TS | Note |
|---|---|---|---|
| Variable | $name | name | Toujours un $ devant. |
| Accès membre objet | $obj->prop / $obj->method() | obj.prop | Flèche ->, pas le point. |
| Accès statique / constante | Classe::CONST / Classe::method() | Classe.method() | Double deux-points ::. |
| Concaténation de chaînes | 'a' . 'b' | 'a' + 'b' | Point, pas +. + = addition uniquement. |
| Tableau (liste) | [1, 2, 3] | [1, 2, 3] | Type array. |
| Tableau associatif (map) | ['a' => 1, 'b' => 2] | { a: 1, b: 2 } | Aussi un array ! (pas de type objet littéral distinct). |
| Fonction fléchée | fn($x) => $x * 2 | (x) => x * 2 | 1 expression, capture auto des variables. |
| Fonction anonyme | function ($x) use ($y) { … } | function (x) { … } | Capture explicite via use. |
| Interpolation | "Bonjour $name" / "{$obj->prop}" | `Bonjour ${name}` | Guillemets doubles ; simples = littéral. |
| Null | null | null | Idem. |
| Égalité stricte | === | === | == fait de la coercition (à éviter). |
| Ternaire / coalescence | $a ?? $b, $a ?: $b | a ?? b | ?? = null-coalescing (identique). |
⚠️ Piège n°1 du dev JS en PHP : utiliser
+pour coller deux chaînes.'Total : ' + $nne concatène pas —+tente une addition numérique (et peut produire une erreur ou un résultat absurde). Le bon opérateur est le point :'Total : ' . $n.
⚠️ Piège n°2 : confondre
->et::.->accède à un membre d’instance ($booking->status) ;::accède à un membre statique ou une constante/enum (BookingStatus::Confirmed,self::MAX). Se tromper donne une erreur explicite, mais autant l’intégrer tout de suite.
💡 Pour un dev Next.js : le point-clé mental — en PHP,
arrayest un type unique qui sert à la fois de tableau indexé ([1,2,3]) et de dictionnaire (['id' => 1]). Il n’y a pas la dualitéArrayvsObjectde JS. Un « objet littéral » JS ({ a: 1 }) devient unarrayassociatif PHP (['a' => 1]) — ou, quand on veut de la structure, une classe (souvent un DTO, cf. chapitre 2.5).
3. Ce que PHP a gagné depuis votre Symfony 5
Voici la frise des nouveautés qui comptent pour Symfony, version par version. On ne détaille pas ici (chapitres suivants) : c’est une carte pour savoir « qu’est-ce qui est arrivé quand ».
3.1 PHP 8.0 (novembre 2020) — la bascule
C’est la version qui rapproche PHP de TypeScript :
- Types union :
int|string,Booking|null. - Arguments nommés :
new Booking(status: BookingStatus::Confirmed). - Attributs
#[...]: la métadonnée native qui remplacera les annotations Symfony/Doctrine. - Promotion de constructeur : déclarer les propriétés directement dans les paramètres du constructeur.
match: unswitchtypé, sans fall-through, qui retourne une valeur.- Opérateur nullsafe
?->:$booking?->getCustomer()?->getEmail(). - Petites fonctions attendues depuis 20 ans :
str_contains(),str_starts_with(),str_ends_with(). - JIT (compilation à la volée) : gains de perf.
3.2 PHP 8.1 (novembre 2021) — enums & immutabilité
- Enums (
enum) : de vrais types énumérés (chapitre 2.3). Fin desconststring bricolées. - Propriétés
readonly: immuables après initialisation. - First-class callable syntax :
$fn = strlen(...);(référencer une fonction/méthode proprement). - Types intersection :
Countable&Traversable. never: type de retour d’une fonction qui ne revient jamais (throw/exit).- Fibers : la primitive de concurrence (chapitre 2.7).
3.3 PHP 8.2 → 8.4 — le raffinement
- 8.2 : classes
readonly(toute la classe immuable d’un coup), types DNF (formes normales disjonctives),null/false/truecomme types autonomes. - 8.3 : constantes de classe typées (
const int MAX = 10;), attribut#[\Override](dit « cette méthode surcharge un parent » — vérifié par le moteur),json_validate(). - 8.4 : property hooks (des getters/setters natifs, façon C#), visibilité asymétrique (
public private(set): lisible partout, modifiable seulement en interne),new Foosans parenthèses pour chaîner, attribut#[\Deprecated].
⚡ Depuis Symfony 5 : à votre époque, vous écriviez sans doute des annotations Doctrine (
@ORM\Column) et peu de types. Le passage clé à intégrer : les annotations ont été remplacées par les attributs (8.0), et les enums remplacent les constantes de statut (8.1). Ces deux changements structurent tout le code Symfony 7.4 que vous allez lire.
4. Un exemple « avant / après » pour mesurer la marche
Une même intention — un objet « ligne de réservation » avec un statut — écrite à l’ancienne puis en PHP 8.4 :
Avant (PHP 7, style Symfony 5 débutant) :
<?php
class Booking
{
const STATUS_PENDING = 'pending';
const STATUS_CONFIRMED = 'confirmed';
private $id;
private $status;
public function __construct($id, $status = self::STATUS_PENDING)
{
$this->id = $id;
$this->status = $status;
}
public function getStatus()
{
return $this->status;
}
}Après (PHP 8.4) :
<?php
declare(strict_types=1);
enum BookingStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
}
final readonly class Booking
{
public function __construct(
public int $id,
public BookingStatus $status = BookingStatus::Pending,
) {}
}Moins de code, typé, immuable, statut impossible à écrire faux (une enum, pas une chaîne libre). C’est tout l’esprit du PHP moderne — et exactement ce que Symfony encourage.
💡 Pour un dev Next.js : la version « après » est quasiment la traduction 1:1 d’une classe TypeScript avec un
enum/union et desreadonly. Vous n’apprenez pas un langage exotique : vous retrouvez vos réflexes TS, avec une autre syntaxe.
5. Ce qui reste différent de TypeScript (à ne pas oublier)
Pour être honnête, tout n’est pas identique :
- Le typage PHP est vérifié à l’exécution, pas seulement à la compilation. Passer une
stringlà où unintest attendu lève uneTypeErrorau runtime (avecstrict_types=1). En TS, l’erreur est à la compilation et disparaît à l’exécution (types effacés). PHP, lui, fait respecter ses types quand le code tourne. - Pas de génériques natifs dans le langage (un
arrayn’est pas typéarray<int, Booking>par le moteur). On comble ça avec des annotations PHPStan (@var Booking[]) analysées statiquement — on en reparle en Partie 14. - Modèle d’exécution : chaque requête HTTP repart d’un état vierge (rien ne persiste en mémoire entre deux requêtes, sauf mode worker). Fini les fuites d’état global des serveurs Node long-running — mais aussi : pas de « singleton en mémoire » qui traverse les requêtes.
- La concurrence ne passe pas par
async/await(chapitre 2.7).
📚 Aller plus loin : la doc PHP officielle (php.net/manual ) est excellente et en français. Les pages « migration » (
migration80,migration81…) listent exhaustivement les nouveautés de chaque version.
✏️ Exercices
Exercice 1. Traduisez ce TypeScript en PHP 8.4 (une classe immuable avec un statut énuméré) :
enum Status { Draft = "draft", Published = "published" }
class Article {
constructor(public readonly id: number, public readonly status: Status = Status.Draft) {}
}✅ Solution
<?php
declare(strict_types=1);
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
final readonly class Article
{
public function __construct(
public int $id,
public Status $status = Status::Draft,
) {}
}Correspondances : enum TS → enum … : string PHP ; public readonly du constructeur → promotion de constructeur + classe readonly ; valeur par défaut identique.
Exercice 2. Trouvez les trois erreurs de ce code écrit par un dev JS fraîchement arrivé en PHP :
$name = "Ada";
echo "Bonjour " + $name;
$user.name = "Bob";
if ($status == BookingStatus.Confirmed) { }✅ Solution
"Bonjour " + $name→ la concaténation utilise.en PHP, pas+:"Bonjour " . $name.$user.name→ l’accès à une propriété d’objet utilise->:$user->name. (Ici$user.nameserait interprété comme une concaténation de$useret de la constantename.)BookingStatus.Confirmed→ un cas d’enum s’accède avec:::BookingStatus::Confirmed. (Et l’égalité stricte===est préférable à==.)
Version correcte :
$name = "Ada";
echo "Bonjour " . $name;
$user->name = "Bob";
if ($status === BookingStatus::Confirmed) { }Exercice 3. Pour chacune de ces fonctionnalités, dites en quelle version de PHP elle est arrivée : (a) les enums ; (b) les attributs #[...] ; (c) les property hooks ; (d) l’opérateur match.
✅ Solution
(a) Enums → PHP 8.1. (b) Attributs → PHP 8.0. (c) Property hooks → PHP 8.4. (d) match → PHP 8.0.
🧠 Quiz de révision
1. Quel opérateur concatène deux chaînes en PHP ?
Le point . ('a' . 'b'). + sert uniquement à l’addition numérique.
2. Quelle est la différence entre -> et :: ?
-> et :: ?-> accède à un membre d’instance ($obj->prop) ; :: accède à un membre statique, une constante ou un cas d’enum (Classe::CONST, Status::Draft).
3. En PHP, quel type sert à la fois de liste et de dictionnaire ?
Le type array : [1, 2, 3] (indexé) et ['a' => 1] (associatif) sont tous deux des array.
4. Citez deux nouveautés majeures de PHP 8.0 qui structurent le code Symfony 7.4.
Au choix : attributs #[...] (remplacent les annotations), promotion de constructeur, types union, match, arguments nommés, nullsafe ?->.
5. En quoi le typage PHP diffère-t-il fondamentalement de celui de TypeScript ?
Le typage PHP est vérifié à l’exécution (une TypeError est levée au runtime avec strict_types=1), alors que les types TypeScript sont effacés à la compilation et n’existent plus à l’exécution.
Prochain chapitre : 2.2 — Le système de types — types scalaires, nullable, union, intersection, never, strict_types, et la comparaison frontale avec TypeScript.