Credit : Logo officiel
PHP 8.3 : les nouveautes qui changent tout
PHP 8.3, deux ans apres : pourquoi je migre tous mes clients
PHP 8.3 est sorti fin 2023. J'en parle vraiment maintenant, en 2026, parce qu'on a passe le cap : les CMS suivent (WordPress 6.4+, PrestaShop 8.2, Symfony 7), les hebergeurs poussent par defaut, et surtout PHP 8.1 est officiellement EOL depuis novembre 2025. Migrer n'est plus une option.
Le mois dernier j'ai migre le PrestaShop d'un client e-commerce de PHP 8.1 vers 8.3 sur un VPS Debian 12. La bascule a pris 35 minutes, dont 25 a verifier les modules tiers. Une seule fonction depreciee a corriger (utf8_encode). Performance : -18% sur le temps de generation de la home grace au JIT plus mature et a mt_rand optimise. Voici ce qui change vraiment et pourquoi vous devriez l'envisager rapidement.
Les constantes de classe enfin typees
Avant PHP 8.3, les constantes de classe c'etait le far west : aucun type, n'importe qui pouvait surcharger avec n'importe quoi. Maintenant :
class Config
{
const string VERSION = '2.1.0';
const int MAX_RETRIES = 3;
const float TIMEOUT = 30.5;
const bool DEBUG = false;
const array ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
}
Si vous mettez le mauvais type, erreur fatale a la compilation. Le typage marche aussi avec les interfaces et les enums :
interface CacheInterface
{
const int DEFAULT_TTL = 3600;
const string PREFIX = 'cache:';
}
enum Status: string
{
const string PREFIX = 'status_';
case Active = 'active';
case Inactive = 'inactive';
}
Pourquoi ca compte vraiment
Dans une grosse base de code, j'ai deja vu une constante const TIMEOUT = '30'; (string) utilisee dans un contexte mathematique. Bug subtil pendant 8 mois. Avec const int TIMEOUT = 30;, le bug devient impossible. Pour les value objects et les enums, c'est un gain enorme de robustesse.
json_validate() : enfin une fonction native
Franchement, c'est le truc que j'attendais depuis des annees. Avant pour verifier qu'une chaine est du JSON valide, il fallait la decoder integralement :
// L'ancienne maniere, lente et moche
$data = json_decode($json);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException('JSON invalide');
}
// PHP 8.3 : direct, sans allouer
if (!json_validate($json)) {
throw new InvalidArgumentException('JSON invalide');
}
$data = json_decode($json, true);
json_validate() est environ 3x plus rapide sur des gros payloads parce qu'elle ne construit pas la structure en memoire. Sur une API qui valide des webhooks Stripe, j'ai gagne 12ms par requete en moyenne. C'est pas enorme mais sur 200k requetes/jour, c'est concret.
json_validate('{"name":"Jean"}'); // true
json_validate('{name: invalid}'); // false
json_validate(''); // false
json_validate('null'); // true
json_validate('[1,2,3]'); // true
Cas concret : valider un payload entrant avant de le passer a un decodeur strict ou de le stocker tel quel en base.
L'attribut #[Override] : la fin des bugs silencieux
C'est un attribut tout simple mais redoutable. Il indique au compilateur qu'une methode surcharge bien une methode parente :
class ParentClass
{
public function process(): void { }
}
class ChildClass extends ParentClass
{
#[Override]
public function process(): void
{
// OK, ca surcharge bien le parent
}
#[Override]
public function prosess(): void // ERREUR FATALE : faute de frappe detectee
{
// Sans #[Override], ce code aurait juste cree une methode jamais appelee
}
}
Le bug que j'ai chasse pendant 3 jours
Dans une stack legacy, un dev avait surcharge getCacheKey() mais une mise a jour avait renomme la methode parente en getCacheIdentifier(). Resultat : la classe enfant continuait de fonctionner mais utilisait la methode du parent. Le cache etait cale sur les mauvaises cles. 3 jours pour comprendre. Avec #[Override], PHP aurait gueule des le deploiement.
Le Randomizer : nouvelles methodes utiles
Random\Randomizer introduit en PHP 8.2 recoit des methodes pratiques pour generer des donnees aleatoires controlees :
$randomizer = new Random\Randomizer();
$float = $randomizer->nextFloat(); // entre 0 et 1
$price = $randomizer->getFloat(10.0, 99.99);
// Generer un code promo lisible (sans 0/O/I/1)
$code = $randomizer->getBytesFromString(
'ABCDEFGHJKLMNPQRSTUVWXYZ23456789',
8
);
// Ex: 'K7N3XP2M'
// Token API
$token = bin2hex($randomizer->getBytes(32));
// Choix pondere
$choice = $randomizer->pickArrayKeys(['A', 'B', 'C', 'D'], 2);
J'utilise getBytesFromString pour generer des references de commande dans une boutique : 0 et O ne se confondent plus, les clients copient la ref sans erreur.
Readonly + clone : enfin compatibles
PHP 8.3 permet de modifier les proprietes readonly lors du clonage via le hook __clone(). C'etait l'enorme limitation des value objects depuis PHP 8.2 :
class User
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly DateTimeImmutable $createdAt,
) {}
public function withEmail(string $newEmail): static
{
$clone = clone $this;
// Plus d'erreur en PHP 8.3 dans __clone
return $clone;
}
public function __clone(): void
{
// Reset d'une propriete readonly autorise dans __clone
}
}
Pourquoi c'est important
Les value objects immutables sont une bonne pratique en DDD. Avant 8.3, soit on lachait readonly, soit on utilisait des hacks avec la reflection. Maintenant le pattern "with" fonctionne nativement.
Nouvelles fonctions et changements pratiques
mb_str_pad et mb_str_split
Les fonctions multibyte qui manquaient :
echo mb_str_pad('cafe', 10, '*', STR_PAD_BOTH);
// '***cafe***'
print_r(mb_str_split('Bonjour'));
// ['B', 'o', 'n', 'j', 'o', 'u', 'r']
Date errors plus parlantes
try {
new DateTime('pas une date');
} catch (DateMalformedStringException $e) {
// Avant : Exception generique
// 8.3 : Exception specifique
}
Deprecations a corriger avant migration
get_class()etget_parent_class()sans argument : remplacer parstatic::classouself::class- Constantes
MT_RAND_PHPdeprecie - Assignations implicites a une propriete dynamique : passer par
#[AllowDynamicProperties]
Migrer en pratique sur Debian 12 / Ubuntu 22.04
Installation
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install -y php8.3 php8.3-fpm php8.3-cli \
php8.3-mysql php8.3-curl php8.3-xml php8.3-mbstring \
php8.3-zip php8.3-redis php8.3-gd php8.3-intl \
php8.3-bcmath php8.3-imagick php8.3-opcache
Bascule des services
sudo systemctl stop php8.2-fpm
sudo systemctl disable php8.2-fpm
sudo systemctl enable --now php8.3-fpm
# Verifier le socket
ls -la /run/php/php8.3-fpm.sock
Mise a jour de la config Nginx
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 60;
}
Puis sudo nginx -t && sudo systemctl reload nginx.
Configurer OPcache et JIT pour la prod
Dans /etc/php/8.3/fpm/conf.d/10-opcache.ini :
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.jit_buffer_size=128M
opcache.jit=tracing
validate_timestamps=0 en prod uniquement : sinon vos modifs de code ne seront pas vues sans systemctl reload php8.3-fpm.
Erreurs courantes et leur fix
1. "Class not found" apres la migration
Certaines extensions ne sont pas installees pour la nouvelle version. Le plus oublie : php8.3-xml, php8.3-intl, php8.3-imagick. Verifier :
php -m | sort
dpkg -l | grep php8.3
2. Nginx renvoie un 502 apres bascule
90% du temps c'est le socket pointe sur l'ancien PHP-FPM. Verifier fastcgi_pass dans tous les vhosts :
sudo grep -r "fastcgi_pass" /etc/nginx/
3. WordPress affiche un ecran blanc
Un plugin utilise une fonction depreciee silencieusement. Activer le mode debug :
// wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Details dans le guide activer le mode debug WordPress.
4. "Cannot use object of type DateTime as array" sur du legacy
PHP 8.3 a renforce la rigueur sur les conversions implicites. Caster proprement :
$ts = $dateTime->getTimestamp();
$formatted = $dateTime->format('Y-m-d');
5. Une fonction depreciee bloque le deploiement
Lancer Rector ou PHPCompatibility avant la mise en prod :
composer require --dev rector/rector
vendor/bin/rector process src --set=php83
Rector reecrit automatiquement la majorite des deprecations. Indispensable sur du code de plus de 5 ans.
Tester sa migration sans risque
# Snapshot LVM avant migration (cf article LVM)
sudo lvcreate -L 5G -s -n preupgrade /dev/vg0/root
# Tester en CLI sans toucher FPM
php8.3 -v
php8.3 -l index.php
# Bench rapide avant/apres
ab -n 1000 -c 10 https://monsite.fr/
Gardez les deux versions de PHP installees pendant une semaine en cas de rollback rapide.
Bascule progressive multi-vhosts
Sur un serveur qui heberge plusieurs sites, je teste site par site plutot que de tout basculer d'un coup. Configurer plusieurs pools FPM, un par version :
# /etc/php/8.3/fpm/pool.d/site1.conf
[site1]
user = www-data
group = www-data
listen = /run/php/site1-php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
Dans Nginx, pointer chaque vhost sur le bon socket. Bascule en 30 secondes par site, rollback instantane si probleme.
Comparer les performances avant/apres
Un benchmark concret sur le serveur d'un client (PrestaShop 8.1, 5000 produits) :
| Metrique | PHP 8.1 | PHP 8.3 | Gain |
|---|---|---|---|
| Temps de generation home | 285 ms | 234 ms | -18% |
| Memoire moyenne / requete | 32 Mo | 28 Mo | -12% |
| Throughput (req/s) | 142 | 178 | +25% |
| Time to first byte | 312 ms | 261 ms | -16% |
Le gain principal vient de l'OPcache plus mature et du JIT en mode tracing qui compile les hot paths a chaud. Pour des sites avec beaucoup de calcul (Symfony, Laravel applis lourdes), c'est encore plus marque.
Outils complementaires que j'utilise au quotidien
La migration n'est pas qu'une question de apt install. Voici les outils qui m'aident a maintenir un code PHP sain sur 8.3 :
PHPStan en niveau 8
L'analyse statique trouve des bugs que les tests rateraient :
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=8
En passant de niveau 5 a 8, j'ai corrige 47 cas de nullables non geres dans une appli legacy. Aucun test ne les avait jamais detectes.
Psalm pour le typage strict
Alternative a PHPStan, plus strict sur les annotations :
composer require --dev vimeo/psalm
vendor/bin/psalm --init
vendor/bin/psalm
Composer audit
Depuis Composer 2.4, scan natif des CVE :
composer audit
Integrer dans CI : tout PR qui introduit une dependance vulnerable est bloque. Sur un site WordPress avec 30 plugins, j'utilise aussi wpscan mensuel.
Pour aller plus loin
- Activer le mode debug WordPress
- Configurer la compression gzip pour PHP
- Configurer Redis comme cache WordPress
- Optimiser les performances WordPress
- LVM Linux : gerer ses disques et snapshots
Migrer maintenant ou rester bloque
PHP 8.3 n'est pas une revolution, c'est une consolidation : du typage plus strict, des fonctions natives qui manquaient, un Randomizer plus complet, des bugs subtils evites. Et surtout, c'est la version qui sera supportee jusqu'en novembre 2027. Si vous etes encore sur 8.1 ou 8.2, planifiez la migration ce trimestre. C'est rarement complique sur du code recent, et les gains de perf grace au JIT mature valent le coup. PHP 8.4 est sorti, mais perso j'attends encore quelques mois avant de la pousser en prod chez les clients : laissons les gros CMS finir leur compat.