Bases
Variables, types et constantes
echo est une construction de langage (pas une fonction) — le plus rapide pour la sortie. print retourne 1 donc peut être utilisé dans des expressions. printf/sprintf utilisent les spécificateurs de format style C (%s chaîne, %d entier, %f flottant, %x hex). var_dump() est l'outil de débogage principal — montre les types et valeurs. error_log() écrit dans le log d'erreurs PHP ou syslog. En production, n'exposez jamais la sortie de débogage aux utilisateurs.
<?php
$name = "Alice"; // string
$age = 30; // integer
$price = 19.99; // float (double)
$active = true; // boolean
$items = [1, 2, 3]; // array
$null = null; // null
// constants
define("MAX_USERS", 100); // runtime constant
const PI = 3.14159; // compile-time constant
echo MAX_USERS; // no $ prefix
// PHP 8.1+ enums
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
}
$s = Status::Active;Echo, print et débogage
echo is a language construct (not a function) — fastest for output. print returns 1 so can be used in expressions. printf/sprintf use C-style format specifiers (%s string, %d int, %f float, %x hex). var_dump() is the primary debugging tool — shows types and values. error_log() writes to the PHP error log or syslog. In production, never expose debug output to users.
<?php
// echo: no return, multiple args (fastest)
echo "Hello", " ", "World";
// print: returns 1, single arg
print "Hello World";
// printf: formatted output
printf("Name: %s, Age: %d, Price: %.2f", "Alice", 30, 19.99);
// debugging output
print_r($array); // human-readable
var_dump($variable); // type + value (detailed)
var_export($array, true); // valid PHP code (for caching)
// sprintf: return formatted string (don't print)
$log = sprintf("[%s] %s", date('H:i:s'), "Started");
error_log($log); // log to error logOpérateurs et comparaisons
Utilisez toujours === (comparaison stricte) pour éviter les bugs de coercition de type. == convertit les types avant de comparer, menant à des résultats surprenants (0 == 'abc' était vrai en PHP 7). L'opérateur spaceship (<=>) retourne -1/0/1 — utile pour usort. L'opérateur de coalescence null (??) est la façon idiomatique de fournir des valeurs par défaut. L'opérateur null-safe (?->) (PHP 8+) court-circuite les chaînes de méthodes sur null, remplaçant les vérifications isset() verbeuses.
<?php
// arithmetic
$sum = 10 + 3; // 13
$mod = 10 % 3; // 1
$pow = 2 ** 3; // 8 (PHP 5.6+)
// comparison: == vs ===
echo (0 == "abc"); // true (loose, PHP 7-); false (PHP 8+)
echo (0 === "abc"); // false (strict — type + value)
echo ("1" == 1); // true (loose)
echo ("1" === 1); // false (strict)
// spaceship operator (PHP 7+)
echo 1 <=> 2; // -1 (less than)
echo 2 <=> 2; // 0 (equal)
echo 3 <=> 2; // 1 (greater than)
// null coalescing
$name = $input ?? "default"; // if $input is null
$deep = $data['user']['name'] ?? "Anonymous";
// null safe operator (PHP 8+)
$country = $user?->getAddress()?->country; // null if any step is nullSuperglobales et web
Les superglobales sont des tableaux associatifs intégrés disponibles dans toutes les portées. $_GET et $_POST contiennent l'entrée utilisateur — ASSAINISSEZ/VALIDEZ TOUJOURS avant usage. filter_input() est plus sûr que l'accès direct. Ne faites jamais confiance aux valeurs $_SERVER qui peuvent être usurpées par les clients (comme HTTP_USER_AGENT). Appelez toujours exit après header('Location:') — PHP continue à exécuter sinon. Démarrez les sessions avec session_start() avant toute sortie.
<?php
// superglobals available everywhere
$_GET['name']; // query string params
$_POST['email']; // form POST data
$_REQUEST['x']; // GET + POST + COOKIE
$_SERVER['HTTP_HOST']; // server/env info
$_SERVER['REQUEST_METHOD']; // GET, POST, etc.
$_COOKIE['session']; // cookies
$_FILES['upload']; // file uploads
$_SESSION['user_id']; // session data (after session_start())
// get client IP
$ip = $_SERVER['REMOTE_ADDR'];
// check request method
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
}
// redirect
header("Location: /dashboard");
exit; // always exit after redirectInclude et require
include/require exécutent le fichier spécifié. require cause une erreur fatale si le fichier manque (utilisez pour les dépendances critiques) ; include avertit seulement (utilisez pour les templates optionnels). Les variantes _once suivent les fichiers inclus pour empêcher la double inclusion — essentiel pour les définitions de fonctions/classes. L'autoloader de Composer (require_once 'vendor/autoload.php') élimine la gestion manuelle des include. Les fichiers peuvent retourner des valeurs, les rendant utiles pour la configuration.
<?php
// include: warning on failure, continues
include 'header.php';
include_once 'config.php'; // only once
// require: fatal error on failure, stops
require 'database.php';
require_once 'vendor/autoload.php'; // Composer autoload
// _once variants prevent re-declaration errors
// use require for critical files (DB config, autoloaders)
// use include for optional files (templates)
// return values from included files
$config = include 'config.php';
// config.php: return ['db' => 'mysql://...', 'debug' => true];Chaînes
Fonctions de chaîne
PHP a plus de 100 fonctions de chaîne. strpos() retourne false si non trouvé — utilisez === false pour vérifier (0 est une position valide). str_replace() peut prendre des tableaux pour recherche/remplacement. substr() supporte les offsets négatifs (depuis la fin). Pour les chaînes multioctets (UTF-8), utilisez les équivalents mb_* (mb_strlen, mb_substr) — strlen compte les octets, pas les caractères. Définissez toujours default_charset='UTF-8' et utilisez les fonctions mb_* pour le texte non-ASCII.
<?php
$s = "Hello, World";
// length and case
echo strlen($s); // 12
echo str_word_count($s); // 2
echo strtoupper($s); // HELLO, WORLD
echo strtolower($s); // hello, world
echo ucfirst("hello"); // Hello
echo ucwords("hello world"); // Hello World
// search and replace
echo strpos($s, "World"); // 7 (false if not found)
echo str_replace("o", "0", $s); // Hell0, W0rld
echo substr($s, 0, 5); // Hello
echo substr($s, -5); // World
echo strrev($s); // dlroW ,olleH
// trim
echo trim(" hi "); // "hi"
echo ltrim(" hi"); // "hi"
echo rtrim("hi "); // "hi"Interpolation de chaîne et heredoc
Les chaînes à double quote interpolent les variables ; les chaînes à simple quote non (utilisez les simples quotes pour le texte littéral — légèrement plus rapide). Utilisez {$var} pour les expressions complexes (propriétés d'objet, accès tableau, appels de méthode). Heredoc (<<<ID) est idéal pour les chaînes multi-lignes comme SQL ou HTML — il interpole les variables. Nowdoc (<<<'ID') est la version non interprétée, utile pour les motifs regex avec backslashes.
<?php
$name = "Alice";
$age = 30;
// double quotes: variable interpolation
echo "Hello, $name!"; // Hello, Alice!
echo "Hello, {$name}!"; // Hello, Alice! (braces for clarity)
echo "Age: {$age}"; // Age: 30
// single quotes: no interpolation (faster)
echo 'Hello, $name'; // Hello, $name (literal)
// complex expressions need braces
echo "Result: {$obj->method()}";
echo "Item: {$array['key']}";
// heredoc: multi-line, interpolates
$sql = <<<SQL
SELECT * FROM users
WHERE name = '$name'
AND age > $age
SQL;
// nowdoc: multi-line, NO interpolation (like single quotes)
$regex = <<<'REGEX'
\d{3}-\d{4}
REGEX;sprintf et formatage
sprintf() est essentiel pour construire des chaînes formatées sûrement — contrairement à l'interpolation de chaîne, il gère les conversions de type et le padding. Utilisez %d pour les entiers (pas %s) pour assurer le formatage numérique. number_format() formate les nombres avec des séparateurs de milliers — critique pour l'affichage des devises. Utilisez toujours sprintf pour les fragments SQL dans les requêtes préparées (bien que les requêtes préparées soient toujours requises pour l'entrée utilisateur).
<?php
// sprintf: format string (returns, doesn't print)
$formatted = sprintf(
"%-10s | %5d | %8.2f",
"Alice", 42, 19.99
);
echo $formatted; // "Alice | 42 | 19.99"
// format specifiers:
// %s string, %d int, %f float, %x hex, %b binary, %c char
// %5d right-padded to width 5
// %-5d left-padded
// %05d zero-padded
// %.2f 2 decimal places
// %8.2f width 8, 2 decimals
// numbered placeholders
echo sprintf("Hi %1$s, bye %1$s", "Alice"); // Hi Alice, bye Alice
// number_format
echo number_format(1234567.891, 2); // 1,234,567.89
echo number_format(1234567.89, 2, ',', '.'); // European formatRegex (PCRE)
PHP utilise PCRE (Expressions Régulières Compatibles Perl) avec les délimiteurs /pattern/. preg_match retourne 1 si correspondance, 0 sinon (utilisez ===, pas ==, puisque 0 est falsy). Validez toujours l'entrée utilisateur avec regex mais ne comptez pas dessus seul — utilisez filter_var() pour les emails, URLs. preg_replace est puissant mais peut être lent sur les grandes chaînes. Utilisez [^...] pour la liste blanche de caractères plutôt que la liste noire. Le flag i rend la correspondance insensible à la casse.
<?php
// preg_match: test pattern (returns 0 or 1)
if (preg_match('/^[a-z]+$/i', "Hello")) {
echo "alphabetic";
}
// capture groups
preg_match('/(d{4})-(d{2})-(d{2})/', "2024-01-15", $matches);
// $matches[0] = "2024-01-15" (full match)
// $matches[1] = "2024" (group 1)
// $matches[2] = "01"
// $matches[3] = "15"
// preg_match_all: all matches
preg_match_all('/d+/', "a1 b22 c333", $matches);
// $matches[0] = ["1", "22", "333"]
// preg_replace: substitute
$clean = preg_replace('/[^a-z0-9]/i', '', "Hello, World!"); // HelloWorld
// preg_split: split by pattern
$parts = preg_split('/[s,]+/', "one, two,three four");
// ["one", "two", "three", "four"]Multioctet et encodage
Les fonctions de chaîne par défaut de PHP sont orientées octets, pas orientées caractères — elles cassent sur les caractères multioctets (UTF-8, chinois, emoji). Utilisez toujours les fonctions mb_* (mb_strlen, mb_substr, mb_strpos, mb_strtoupper) pour le texte non-ASCII. Définissez mb_internal_encoding('UTF-8') au début de votre application. Utilisez JSON_UNESCAPED_UNICODE pour garder le chinois/emoji lisible dans la sortie JSON. C'est une source courante de bugs dans les applications internationales.
<?php
// UTF-8 strings: use mb_* functions
$text = "Café"; // 4 chars, 5 bytes (é = 2 bytes)
echo strlen($text); // 5 (bytes!)
echo mb_strlen($text); // 4 (characters!)
echo substr($text, 0, 3); // "Caf" (might break UTF-8!)
echo mb_substr($text, 0, 3); // "Caf" (safe)
// case conversion
echo strtoupper("straße"); // "STRAßE" (wrong!)
echo mb_strtoupper("straße"); // "STRAßE" (correct, locale-aware)
// encoding detection
$encoding = mb_detect_encoding($text);
$utf8 = mb_convert_encoding($text, 'UTF-8', 'auto');
// set internal encoding
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
// JSON with UTF-8
$json = json_encode($data, JSON_UNESCAPED_UNICODE);Tableaux
Tableaux indexés et associatifs
Les tableaux PHP sont en réalité des hash maps ordonnés — ils fonctionnent à la fois comme listes et dictionnaires. Les tableaux indexés assignent automatiquement des clés numériques ; les tableaux associatifs utilisent des clés chaînes. isset() retourne false pour les valeurs null ; array_key_exists() retourne true même pour null. unset() supprime un élément mais ne réindexe pas. Pour une vraie liste (sans trous), utilisez array_values() pour réindexer après suppression. PHP 8.1+ a un type tableau readonly.
<?php
// indexed array (numeric keys)
$nums = [10, 20, 30];
$nums[] = 40; // append
echo $nums[0]; // 10
echo count($nums); // 4
// associative array (string keys)
$user = [
"name" => "Alice",
"age" => 30,
"email" => "[email protected]",
];
echo $user["name"]; // Alice
$user["phone"] = "555-1234"; // add key
// mixed keys
$mixed = [0 => "a", "name" => "b", 5 => "c"];
// check key existence
if (isset($user["email"])) { /* ... */ }
if (array_key_exists("name", $user)) { /* ... */ }
// remove element
unset($user["phone"]);Multidimensionnel et itération
Les tableaux multidimensionnels sont des tableaux de tableaux. foreach est la façon idiomatique d'itérer — c'est plus rapide et plus lisible que les boucles for. Utilisez &$value pour modifier les éléments en place (désactivez toujours la référence après la boucle pour éviter les bugs). array_column() extrait une seule colonne d'un tableau 2D — extrêmement utile pour transformer des jeux de résultats de base de données. Les tableaux PHP maintiennent l'ordre d'insertion.
<?php
$users = [
["name" => "Alice", "age" => 30],
["name" => "Bob", "age" => 25],
["name" => "Carol", "age" => 35],
];
// iterate with key + value
foreach ($users as $index => $user) {
echo "$index: {$user['name']} ({$user['age']})\n";
}
// modify by reference
foreach ($users as &$user) {
$user['age'] += 1; // increment each age
}
unset($user); // break reference!
// nested iteration
$matrix = [[1, 2], [3, 4], [5, 6]];
foreach ($matrix as $row) {
foreach ($row as $cell) {
echo $cell . " ";
}
echo "\n";
}
// extract column
$names = array_column($users, 'name'); // ["Alice", "Bob", "Carol"]Fonctions de tableau : map, filter, reduce
array_map, array_filter, et array_reduce sont le trio de programmation fonctionnelle pour les tableaux. Les fonctions fléchées (fn() =>) les rendent concis. array_filter préserve les clés — utilisez array_values() pour réindexer si besoin. array_merge réindexe les clés numériques mais préserve les clés chaînes (les valeurs ultérieures écrasent). array_column, array_chunk, et array_slice sont essentiels pour la manipulation de données. Ces fonctions sont l'épine dorsale du traitement de données en PHP.
<?php
$nums = [1, 2, 3, 4, 5];
// map: transform each element
$doubled = array_map(fn($n) => $n * 2, $nums); // [2, 4, 6, 8, 10]
// filter: keep elements matching condition
$evens = array_filter($nums, fn($n) => $n % 2 === 0); // [2, 4]
// reduce: accumulate to single value
$sum = array_reduce($nums, fn($carry, $n) => $carry + $n, 0); // 15
// walk: like map but modifies in place (by reference)
array_walk($nums, fn(&$n) => $n *= 2);
// combining arrays
$merged = array_merge([1, 2], [3, 4]); // [1, 2, 3, 4]
$combined = array_combine(['a', 'b'], [1, 2]); // ['a' => 1, 'b' => 2]
$sliced = array_slice($nums, 1, 2); // [2, 3]
$chunked = array_chunk($nums, 2); // [[1,2], [3,4], [5]]Trier les tableaux
Les fonctions de tri PHP modifient le tableau en place (passage par référence). sort/rsort réindexent ; asort/arsort préservent les clés. usort avec une fonction de comparaison (utilisant <=>) trie par logique personnalisée. natsort() fait le tri naturel (img2 avant img10) — essentiel pour les noms de fichiers. Pour les tableaux multidimensionnels, utilisez usort avec une closure qui compare le champ souhaité. L'opérateur spaceship (<=>) simplifie les fonctions de comparaison.
<?php
$nums = [3, 1, 4, 1, 5, 9, 2, 6];
// sort by value (reindexes)
sort($nums); // [1, 1, 2, 3, 4, 5, 6, 9]
rsort($nums); // descending
// sort preserving keys
asort($nums); // ascending, preserve keys
arsort($nums); // descending, preserve keys
// sort by key
ksort($nums); // by key ascending
krsort($nums); // by key descending
// custom sort with callback
$users = [["name" => "Bob", "age" => 25], ["name" => "Alice", "age" => 30]];
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
// sorted by age ascending
// natural sort (for strings with numbers)
$files = ["img10.jpg", "img2.jpg", "img1.jpg"];
natsort($files); // ["img1.jpg", "img2.jpg", "img10.jpg"]
sort($files); // ["img1.jpg", "img10.jpg", "img2.jpg"] (wrong!)Inspection et manipulation de tableau
in_array avec strict=true (troisième paramètre) empêche les bugs de coercition de type. array_search retourne la clé (utilisez === false pour vérifier). array_push/pop implémentent LIFO (pile) ; array_shift/unshift implémentent FIFO (file) — mais shift est O(n). Pour les grandes files, utilisez SplQueue ou SplDoublyLinkedList. array_unique préserve les clés. array_diff/intersect comparent les valeurs ; utilisez array_diff_key/intersect_key pour la comparaison basée sur les clés.
<?php
$arr = [1, 2, 3, 4, 5];
// inspection
echo count($arr); // 5
echo in_array(3, $arr); // true
echo in_array("3", $arr, true); // false (strict)
echo array_search(3, $arr); // 2 (key, false if not found)
print_r(array_keys($arr)); // [0, 1, 2, 3, 4]
print_r(array_values($arr)); // [1, 2, 3, 4, 5]
// stack/queue operations
array_push($arr, 6); // push to end
$last = array_pop($arr); // pop from end
$first = array_shift($arr); // remove from front
array_unshift($arr, 0); // add to front
// set operations
$unique = array_unique([1, 2, 2, 3]); // [1, 2, 3]
$diff = array_diff([1, 2, 3], [2, 3, 4]); // [1] (in first, not second)
$intersect = array_intersect([1, 2, 3], [2, 3, 4]); // [2, 3]
// flip and reverse
$flipped = array_flip(['a' => 1, 'b' => 2]); // [1 => 'a', 2 => 'b']
$reversed = array_reverse([1, 2, 3]); // [3, 2, 1]Flux de contrôle
If / Else / Elseif
PHP utilise elseif (un mot) — pas « else if » avec un espace (bien que cela fonctionne aussi). La syntaxe alternative (if: ... endif;) est utile dans les templates HTML pour éviter la confusion de correspondance d'accolades. L'opérateur ternaire est associatif à droite — évitez l'imbrication. L'opérateur d'assignation de coalescence null (??=) définit une valeur seulement si elle est actuellement null — parfait pour l'initialisation paresseuse des valeurs par défaut de config.
<?php
$score = 85;
if ($score >= 90) {
$grade = "A";
} elseif ($score >= 80) {
$grade = "B";
} elseif ($score >= 70) {
$grade = "C";
} else {
$grade = "F";
}
// alternative syntax (for templates)
if ($score >= 90):
echo "Excellent";
elseif ($score >= 80):
echo "Good";
else:
echo "Try harder";
endif;
// ternary
$status = $age >= 18 ? "adult" : "minor";
// null coalescing assignment (PHP 7.4+)
$config['timeout'] ??= 30; // set if not setSwitch et match
switch utilise la comparaison lâche (==) et nécessite break pour empêcher le fall-through — une source courante de bugs. match (PHP 8+) utilise la comparaison stricte (===), retourne une valeur directement, et lève une exception si aucune branche ne correspond (pas d'échec silencieux). match est le remplaçant moderne de switch quand vous avez besoin d'une valeur. Utilisez switch pour les cas complexes multi-instructions ; utilisez match pour la simple sélection de valeur. Incluez toujours un cas default.
<?php
// switch: loose comparison (==)
$day = "Mon";
switch ($day) {
case "Mon":
case "Tue":
case "Wed":
echo "Weekday";
break;
case "Sat":
case "Sun":
echo "Weekend";
break;
default:
echo "Unknown";
}
// match (PHP 8+): strict comparison (===), returns value
$status = 404;
$message = match($status) {
200, 201 => "Success",
301, 302 => "Redirect",
404 => "Not Found",
500 => "Server Error",
default => "Unknown",
};
// match throws UnhandledMatchError if no match and no defaultBoucles : for, while, foreach, do-while
foreach est la boucle idiomatique pour les tableaux — c'est plus rapide et plus sûr que for avec count(). Utilisez continue pour sauter les itérations et break pour sortir. PHP n'a pas de break/continue étiqueté (contrairement à Java/Rust). Pour les tableaux associatifs, foreach ($arr as $key => $value) est le motif standard. do-while s'exécute au moins une fois — utile pour la validation d'entrée. Évitez de modifier le tableau pendant foreach (utilisez un tableau séparé pour les résultats).
<?php
// for loop
for ($i = 0; $i < 5; $i++) {
echo $i; // 01234
}
// foreach (most common in PHP)
$fruits = ["apple", "banana", "cherry"];
foreach ($fruits as $fruit) {
echo $fruit;
}
// foreach with key
foreach ($fruits as $index => $fruit) {
echo "$index: $fruit";
}
// while
$count = 0;
while ($count < 3) {
echo $count++;
}
// do-while (runs at least once)
do {
$line = readline("> ");
} while ($line !== "quit");
// break and continue
for ($i = 0; $i < 10; $i++) {
if ($i === 3) continue; // skip 3
if ($i === 7) break; // stop at 7
echo $i;
}Flux de contrôle dans les templates
La syntaxe de contrôle alternative de PHP (if:/elseif:/else:/endif;, foreach:/endforeach;) est conçue pour les templates HTML. <?= $var ?> est un raccourci pour <?php echo $var; ?> — utilisez-le toujours dans les templates pour la lisibilité. Échappez toujours la sortie avec htmlspecialchars() pour empêcher XSS. La séparation de la logique PHP et de la présentation HTML est la base des systèmes de templating comme Twig et Blade, qui offrent une syntaxe plus propre et l'échappement automatique.
<?php // template file ?>
<?php if ($user->isAdmin()): ?>
<div class="admin-panel">Admin Tools</div>
<?php elseif ($user->isEditor()): ?>
<div class="editor-tools">Edit Tools</div>
<?php else: ?>
<div class="user-view">Read Only</div>
<?php endif; ?>
<?php foreach ($products as $p): ?>
<div class="product">
<?= htmlspecialchars($p['name']) ?>
- $<?= number_format($p['price'], 2) ?>
</div>
<?php endforeach; ?>
<?php // shorthand echo ?>
<h1><?= $title ?></h1>
<?php // ternary in templates ?>
<span class="<?= $active ? 'on' : 'off' ?>"><?= $active ? 'Active' : 'Inactive' ?></span>Exceptions et gestion d'erreurs
PHP 7+ utilise les exceptions pour la plupart des erreurs. Attrapez toujours les types d'exception spécifiques (pas juste Exception) pour gérer différents échecs de manière appropriée. finally s'exécute toujours — utilisez-le pour le nettoyage (fermer les fichiers, connexions). Les exceptions personnalisées étendent Exception et ajoutent du contexte de domaine. PHP 8+ permet d'attraper plusieurs types d'exception avec |. Définissez PDO en mode exception pour une gestion d'erreur cohérente. N'attrapez jamais d'exceptions sans logger — les échecs silencieux cachent les bugs.
<?php
// try / catch / finally
try {
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
error_log("DB connection failed: " . $e->getMessage());
die("Service unavailable");
} finally {
// always runs, even after return/throw
echo "Cleanup";
}
// custom exception
class ValidationException extends Exception {
public function __construct(string $field, string $message = "") {
parent::__construct("$field: $message");
}
}
// throw
if (empty($email)) {
throw new ValidationException("email", "is required");
}
// catch multiple types (PHP 8+)
try {
riskyOperation();
} catch (PDOException | RuntimeException $e) {
// catches either type
log_error($e);
}Fonctions
Définir des fonctions
PHP 7+ supporte les paramètres typés et les types de retour (int, string, array, ?Type pour nullable). PHP 8+ ajoute les arguments nommés (sauter les défauts, réordonner les params), les types union (int|string), et le type mixed. Les params variadiques (...$nums) collectent les arguments supplémentaires dans un tableau. L'opérateur spread (...$arr) dépaquette un tableau en arguments. Le passage par référence (&) modifie l'original — utilisez-le avec parcimonie car cela rend le code plus difficile à raisonner.
<?php
// basic function with return type
function add(int $a, int $b): int {
return $a + $b;
}
// default parameters
function greet(string $name, string $greeting = "Hello"): string {
return "$greeting, $name!";
}
// named arguments (PHP 8+)
echo greet(name: "Alice", greeting: "Hi");
// variadic functions
function sum(int ...$nums): int {
return array_sum($nums);
}
echo sum(1, 2, 3, 4); // 10
// spread operator
$nums = [1, 2, 3];
echo sum(...$nums); // 6
// pass by reference
function increment(int &$n): void {
$n++;
}
$x = 5;
increment($x);
echo $x; // 6Fonctions fléchées et closures
Les fonctions fléchées (fn() =>) sont des closures concises à expression unique qui capturent automatiquement les variables externes par valeur. Les closures traditionnelles (function() use ($var)) sont nécessaires pour les corps multi-lignes ou la capture par référence (&$var). Les closures sont essentielles pour array_map, array_filter, usort, et les gestionnaires d'événements. Les fonctions fléchées ne peuvent pas avoir d'instructions (pas de if, for) — utilisez les closures traditionnelles pour la logique complexe. Les closures sont des objets de première classe (classe Closure).
<?php
// arrow function (PHP 7.4+): single expression, auto-capture
$square = fn($x) => $x * $x;
echo $square(5); // 25
// auto-captures outer variables by value
$multiplier = 3;
$multiply = fn($x) => $x * $multiplier;
echo $multiply(5); // 15
// traditional closure (multi-line, explicit capture)
$factor = 10;
$scale = function ($x) use ($factor) {
return $x * $factor + 1;
};
// capture by reference
$count = 0;
$increment = function () use (&$count) {
$count++;
};
$increment();
echo $count; // 1
// closures as callbacks
$nums = [1, 2, 3, 4];
$evens = array_filter($nums, fn($n) => $n % 2 === 0);
$doubled = array_map(fn($n) => $n * 2, $nums);Portée des variables et globales
PHP a une portée au niveau fonction — les variables définies hors d'une fonction ne sont PAS accessibles à l'intérieur sans « global » ou $GLOBALS. Évitez « global » — cela crée des dépendances cachées et rend les tests difficiles. Utilisez l'injection de dépendances à la place. Les variables static persistent à travers les appels de fonction mais sont à portée de la fonction — utiles pour le caching/memoization mais peuvent causer des problèmes dans les processus longs. Les closures doivent capturer explicitement les variables avec « use ».
<?php
$global = "I'm global";
function testScope(): void {
// echo $global; // ERROR: not in scope
global $global; // import global
echo $global; // OK
// $GLOBALS superglobal (alternative)
echo $GLOBALS['global'];
}
// static variables: persist across calls
function counter(): int {
static $count = 0;
return ++$count;
}
echo counter(); // 1
echo counter(); // 2
echo counter(); // 3
// closures don't see outer scope by default
$outer = "hello";
$closure = function () {
// echo $outer; // ERROR
};
$closure2 = function () use ($outer) {
echo $outer; // OK, captured
};Déclarations de type et types stricts
declare(strict_types=1) doit être la première instruction — il applique la vérification stricte de type (pas de coercition) pour tout le fichier. Sans cela, PHP coerce les types (int 5 passé à un paramètre string devient « 5 »). Utilisez toujours les types stricts dans le nouveau code. PHP 8+ ajoute les types union, mixed, never (la fonction ne retourne jamais), et static (retourne la classe). La syntaxe callable de première classe (func(...)) crée des closures depuis n'importe quel callable — plus propre que les références de fonction.
<?php
// declare strict types (FIRST line of file)
declare(strict_types=1);
function divide(float $a, float $b): float {
if ($b === 0.0) {
throw new DivisionByZeroError();
}
return $a / $b;
}
// nullable types (?Type or Type|null)
function findUser(?int $id): ?string {
if ($id === null) return null;
return "User $id";
}
// union types (PHP 8+)
function process(int|string $input): int|string {
return is_int($input) ? $input * 2 : strtoupper($input);
}
// return types: void, never, mixed, static
function log(string $msg): void { /* no return */ }
function redirect(): never { header("Location: /"); exit; }
// first-class callable syntax (PHP 8.1+)
$func = strlen(...); // creates Closure from function
echo $func("hello"); // 5Générateurs et yield
Les générateurs (fonctions avec yield) produisent des valeurs paresseusement — ils ne calculent pas toutes les valeurs à l'avance, économisant la mémoire. C'est essentiel pour traiter de gros fichiers ou datasets. yield met en pause la fonction, retournant une valeur ; la fonction reprend quand la valeur suivante est demandée. Les générateurs implémentent Iterator, donc ils fonctionnent avec foreach. Utilisez les générateurs pour : le traitement de fichiers, l'itération de lignes de base de données, les séquences infinies, et les pipelines. Ils peuvent aussi yield des paires clé=>valeur et accepter des valeurs via send().
<?php
// generator: memory-efficient iteration
function readLines(string $file): Generator {
$handle = fopen($file, 'r');
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}
foreach (readLines("large.txt") as $line) {
echo $line; // one line at a time, low memory
}
// infinite generator
function fibonacci(): Generator {
[$a, $b] = [0, 1];
while (true) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
// take first 10
$fib = fibonacci();
for ($i = 0; $i < 10; $i++) {
echo $fib->current() . " ";
$fib->next();
}
// yield with key
function pairs(): Generator {
yield 'a' => 1;
yield 'b' => 2;
yield 'c' => 3;
}POO et classes
Classe, propriétés et constructeur
La promotion de constructeur PHP 8 élimine le boilerplate — déclarez les propriétés comme paramètres de constructeur. Visibilité des propriétés : public (n'importe où), protected (classe + sous-classes), private (classe uniquement). readonly (PHP 8.1) empêche la modification après initialisation. self fait référence à la classe courante ; static fait référence à la classe appelante (pour la liaison statique tardive). Utilisez static:: au lieu de self:: dans les hiérarchies d'héritage pour un polymorphisme correct.
<?php
class Person {
// typed properties (PHP 7.4+)
public string $name;
protected int $age;
private string $email;
public readonly string $id; // PHP 8.1+
// constructor promotion (PHP 8+)
public function __construct(
string $name,
int $age,
string $email = "",
string $id = ""
) {
$this->name = $name;
$this->age = $age;
$this->email = $email;
$this->id = $id;
}
// methods
public function greet(): string {
return "Hi, I'm {$this->name}";
}
// static method
public static function create(string $name): self {
return new self($name, 0);
}
}
$p = new Person("Alice", 30);
echo $p->greet();
echo Person::create("Bob")->name;Héritage et classes abstraites
Les classes abstract ne peuvent pas être instanciées — elles définissent un template pour les sous-classes. Les méthodes abstraites doivent être implémentées par les sous-classes concrètes. PHP supporte l'héritage simple uniquement (un seul extends). Utilisez final pour empêcher l'héritage/le remplacement quand l'implémentation ne devrait pas changer. Les membres protected sont accessibles dans les sous-classes — utilisez-les pour les API internes. Appelez toujours parent::__construct() si le parent a un constructeur. instanceof vérifie le type : if ($dog instanceof Animal).
<?php
abstract class Animal {
protected string $name;
public function __construct(string $name) {
$this->name = $name;
}
// abstract method: must be implemented by subclasses
abstract public function speak(): string;
// concrete method: inherited as-is
public function describe(): string {
return "{$this->name} says {$this->speak()}";
}
}
class Dog extends Animal {
public function speak(): string {
return "Woof";
}
}
class Cat extends Animal {
public function speak(): string {
return "Meow";
}
}
$dog = new Dog("Rex");
echo $dog->describe(); // Rex says Woof
// final class/method: cannot be extended/overridden
final class Singleton { /* ... */ }Interfaces et traits
Les interfaces définissent des contrats — les classes peuvent implémenter plusieurs interfaces (contrairement à l'héritage simple). Toutes les méthodes d'interface doivent être public. Les traits fournissent la réutilisation de code sans héritage — ce sont du « copier-coller » au niveau du langage. Les traits peuvent avoir des propriétés, méthodes, et même des méthodes abstraites. Utilisez les traits pour les préoccupations transversales (timestamps, logging, soft deletes). Résolution de conflit : utilisez TraitA::method insteadof TraitB quand les traits ont les mêmes noms de méthode.
<?php
// interface: contract (no implementation)
interface Comparable {
public function compareTo(object $other): int;
}
interface JsonSerializable {
public function jsonSerialize(): mixed;
}
// a class can implement multiple interfaces
class Product implements Comparable, JsonSerializable {
public function __construct(public float $price) {}
public function compareTo(object $other): int {
return $this->price <=> $other->price;
}
public function jsonSerialize(): mixed {
return ['price' => $this->price];
}
}
// trait: reusable code (horizontal reuse)
trait Timestampable {
public DateTime $createdAt;
public function setCreatedAt(): void {
$this->createdAt = new DateTime();
}
public function age(): DateInterval {
return $this->createdAt->diff(new DateTime());
}
}
class Article {
use Timestampable; // use trait
}Méthodes magiques
Les méthodes magiques sont des méthodes spéciales qui interceptent les opérations sur les objets. __get/__set implémentent la surcharge de propriétés (propriétés dynamiques). __toString permet le cast en chaîne. __invoke rend les objets appelables. __clone s'exécute au clonage (clone $obj). Utilisez-les avec parcimonie — ils ajoutent de la « magie » difficile à tracer. __get/__set sont utiles pour les data transfer objects ou le lazy loading. Documentez toujours clairement le comportement magique. PHP 8.2 déprécie les propriétés dynamiques — utilisez __get/__set ou #[AllowDynamicProperties].
<?php
class Magic {
private array $data = [];
// called when accessing undefined property
public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
// called when setting undefined property
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}
// called when isset() or empty() on undefined property
public function __isset(string $name): bool {
return isset($this->data[$name]);
}
// called when object is used as string
public function __toString(): string {
return json_encode($this->data);
}
// called when object is called as function
public function __invoke(string $arg): string {
return "Called with: $arg";
}
// called on clone
public function __clone(): void {
$this->data = []; // reset on clone
}
}
$m = new Magic();
$m->foo = "bar"; // __set
echo $m->foo; // __get -> "bar"
echo $m; // __toString -> {"foo":"bar"}
echo $m("test"); // __invokeEspaces de noms et autoloading
Les espaces de noms empêchent les collisions de noms de classes — comme les packages en Java. Le namespace doit être la première instruction. use importe les classes (avec alias optionnels : use Foo\Bar as B). L'autoloading PSR-4 mappe les espaces de noms aux chemins de fichiers : App\Models\User → src/Models/User.php. L'autoloader de Composer (require 'vendor/autoload.php') gère cela automatiquement. Utilisez toujours les espaces de noms en PHP moderne. Le \\ dans les chaînes est un backslash échappé (séparateur d'espace de noms).
<?php
// file: src/Models/User.php
namespace App\Models;
use App\Database\Connection;
use App\Exceptions\UserNotFoundException;
class User {
private Connection $db;
public function __construct(Connection $db) {
$this->db = $db;
}
public function find(int $id): ?self {
// ...
throw new UserNotFoundException("User $id not found");
}
}
// composer.json (PSR-4 autoloading)
// {
// "autoload": {
// "psr-4": { "App\\": "src/" }
// }
// }
// usage
use App\Models\User;
$user = new User($db);Web, formulaires et E/S fichiers
Gestion de formulaire et validation
Validez toujours côté serveur — la validation côté client est pour l'UX, pas la sécurité. filter_input/filter_var avec FILTER_VALIDATE_* retournent false sur entrée invalide. Trim les chaînes avant validation. Utilisez des requêtes préparées pour les insertions en base de données. Les tokens CSRF empêchent la falsification de requête intersite — générez par session et vérifiez sur POST. bin2hex(random_bytes(32)) génère un token cryptographiquement sûr. Ne faites jamais confiance à l'entrée utilisateur — validez, assainissez, et échappez.
<?php
// process POST form
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$age = filter_var($_POST['age'] ?? 0, FILTER_VALIDATE_INT);
$errors = [];
if (empty($name)) {
$errors[] = "Name is required";
} elseif (strlen($name) > 100) {
$errors[] = "Name too long";
}
if ($email === false) {
$errors[] = "Valid email required";
}
if ($age === false || $age < 18) {
$errors[] = "Must be 18+";
}
if (empty($errors)) {
// process valid data
// save to database, redirect, etc.
header("Location: /success");
exit;
}
}
// CSRF token
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['csrf'] = $token;
// in form: <input type="hidden" name="csrf" value="<?= $token ?>">Sessions et cookies
Les sessions stockent les données côté serveur (identifiées par un cookie d'ID de session). session_start() doit être appelé avant toute sortie (ou utilisez ob_start()). Stockez un minimum de données en sessions — elles consomment de la mémoire serveur. Pour les cookies, définissez toujours secure (HTTPS uniquement), httponly (empêche l'accès XSS), et samesite (protection CSRF). Détruisez les sessions correctement : unset les variables, détruisez la session, effacez le cookie. Pour les applications scalables, utilisez un gestionnaire de session soutenu par Redis/base de données au lieu de fichiers.
<?php
// start session (must be before any output)
session_start();
// set session data
$_SESSION['user_id'] = 42;
$_SESSION['username'] = "Alice";
$_SESSION['login_time'] = time();
// read session data
$userId = $_SESSION['user_id'] ?? null;
// check if logged in
function isLoggedIn(): bool {
return isset($_SESSION['user_id']);
}
// destroy session
session_unset(); // clear variables
session_destroy(); // destroy session
setcookie(session_name(), '', time() - 3600, '/'); // clear cookie
// cookies
setcookie("theme", "dark", [
'expires' => time() + 86400 * 30, // 30 days
'path' => '/',
'secure' => true, // HTTPS only
'httponly' => true, // not accessible via JS
'samesite' => 'Strict', // CSRF protection
]);
// read cookie
$theme = $_COOKIE['theme'] ?? 'light';E/S fichiers
file_get_contents/file_put_contents sont pratiques pour les petits fichiers. Pour les gros fichiers, utilisez fopen/fread/fwrite avec les streams. fgetcsv/fputcsv gèrent le format CSV (y compris quoting/échappement). json_decode avec true retourne des tableaux associatifs (objets par défaut). Vérifiez toujours file_exists et gérez les erreurs (permissions, disque plein). Pour les uploads de fichiers, utilisez move_uploaded_file() pour la sécurité. Verrouillez les fichiers avec flock() à l'écriture concurrente.
<?php
// read entire file
$content = file_get_contents("data.txt");
$lines = file("data.txt", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// write file
file_put_contents("output.txt", "Hello World");
file_put_contents("log.txt", "entry\n", FILE_APPEND); // append
// CSV
$csv = fopen("data.csv", "r");
while (($row = fgetcsv($csv)) !== false) {
print_r($row); // array of column values
}
fclose($csv);
// write CSV
$out = fopen("export.csv", "w");
fputcsv($out, ["Name", "Email", "Age"]);
fputcsv($out, ["Alice", "[email protected]", 30]);
fclose($out);
// JSON
$data = json_decode(file_get_contents("config.json"), true); // assoc array
file_put_contents("config.json", json_encode($data, JSON_PRETTY_PRINT));
// file info
file_exists("data.txt"); // bool
filesize("data.txt"); // bytes
filemtime("data.txt"); // modification timestamp
is_dir("folder"); // boolUploads de fichiers
Les uploads de fichiers arrivent via $_FILES, pas $_POST. Validez toujours : vérifiez le code d'erreur, vérifiez le type MIME avec finfo (pas $_FILES['type'] qui est fourni par le client et falsifiable), appliquez des limites de taille, et générez des noms de fichiers sûrs (ne faites jamais confiance au nom original). move_uploaded_file() est une fonction de sécurité — elle vérifie que le fichier a été uploadé via HTTP POST. Stockez les uploads hors de la racine web ou servez-les via PHP pour empêcher l'accès direct. Envisagez de scanner les uploads pour les malwares.
<?php
// HTML: <form method="POST" enctype="multipart/form-data">
// <input type="file" name="document">
// <input type="submit">
// </form>
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['document'];
// $file contains:
// ['name'] => original filename
// ['type'] => MIME type (unreliable!)
// ['tmp_name'] => temporary path
// ['error'] => UPLOAD_ERR_OK (0) on success
// ['size'] => file size in bytes
if ($file['error'] !== UPLOAD_ERR_OK) {
die("Upload error: " . $file['error']);
}
// validate
$allowedTypes = ['application/pdf', 'image/jpeg', 'image/png'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (!in_array($mimeType, $allowedTypes)) {
die("Invalid file type");
}
if ($file['size'] > 5 * 1024 * 1024) { // 5MB
die("File too large");
}
// move to permanent location
$dest = "uploads/" . uniqid() . "_" . $file['name'];
move_uploaded_file($file['tmp_name'], $dest);
echo "Uploaded to: $dest";
}cURL et requêtes HTTP
cURL est le client HTTP standard en PHP — il gère HTTPS, les redirections, les cookies, et l'authentification. Définissez toujours CURLOPT_RETURNTRANSFER pour obtenir la réponse comme chaîne (sinon elle est imprimée). Définissez des timeouts pour éviter les blocages. Pour les requêtes simples, file_get_contents avec stream_context fonctionne mais manque de fonctionnalités. Pour la production, utilisez Guzzle (composer require guzzlehttp/guzzle) ou Symfony HTTP Client — ils offrent de meilleures API, logique de retry, et conformité PSR-18.
<?php
// GET request
$ch = curl_init("https://api.example.com/users");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
print_r($data);
}
// POST request with JSON
$ch = curl_init("https://api.example.com/users");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['name' => 'Alice']),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
],
]);
$response = curl_exec($ch);
curl_close($ch);
// simpler: file_get_contents with context
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode(['name' => 'Alice']),
],
]);
$result = file_get_contents("https://api.example.com/users", false, $context);Base de données (PDO)
Connexion PDO et bases
PDO (PHP Data Objects) est la couche d'abstraction standard de base de données — supporte MySQL, PostgreSQL, SQLite, et plus. Définissez toujours ERRMODE_EXCEPTION pour une gestion d'erreur correcte et ATTR_EMULATE_PREPARES=false pour de vraies requêtes préparées (meilleure sécurité). FETCH_ASSOC retourne des tableaux associatifs (utilisez FETCH_OBJ pour les objets, FETCH_CLASS pour mapper vers des classes). Utilisez toujours le charset utf8mb4 pour un support Unicode complet (y compris emoji). Stockez la connexion dans un singleton ou conteneur DI.
<?php
// connect (always use exception mode)
$dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4";
$pdo = new PDO($dsn, "username", "password", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // use real prepared statements
]);
// simple query
$stmt = $pdo->query("SELECT * FROM users LIMIT 5");
$users = $stmt->fetchAll(); // array of associative arrays
// fetch one row
$user = $pdo->query("SELECT * FROM users WHERE id = 1")->fetch();
// fetch column
$count = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
// execute (no params)
$pdo->exec("DELETE FROM logs WHERE created_at < '2023-01-01'");
$deleted = $pdo->rowCount(); // affected rowsRequêtes préparées (prévention injection SQL)
Les requêtes préparées sont OBLIGATOIRES pour toute requête avec entrée utilisateur — elles séparent la structure SQL des données, rendant l'injection impossible. Utilisez ? pour les paramètres positionnels ou :name pour les paramètres nommés. Pour les clauses IN, vous devez construire la chaîne de placeholders dynamiquement (mais les valeurs sont toujours paramétrées). lastInsertId() retourne la dernière valeur d'auto-incrémentation. Ne concaténez jamais l'entrée utilisateur dans SQL — même avec des fonctions d'échappement. C'est la règle de sécurité n°1 en PHP.
<?php
// prepared statements: THE way to prevent SQL injection
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND status = ?");
$stmt->execute([$email, 'active']);
$user = $stmt->fetch();
// named parameters (more readable)
$stmt = $pdo->prepare(
"INSERT INTO users (name, email, age) VALUES (:name, :email, :age)"
);
$stmt->execute([
':name' => 'Alice',
':email' => '[email protected]',
':age' => 30,
]);
$id = $pdo->lastInsertId(); // get auto-increment ID
// IN clause with prepared statements
$ids = [1, 2, 3, 4];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
$users = $stmt->fetchAll();
// NEVER do this (SQL injection!):
// $pdo->query("SELECT * FROM users WHERE name = '$_GET[name]'");Transactions et gestion d'erreurs
Les transactions garantissent l'atomicité — toutes les opérations réussissent ou toutes échouent. beginTransaction/commit/rollBack enveloppent l'unité de travail. Enveloppez toujours les transactions dans try/catch et rollback sur toute exception. PDO lève PDOException sur les erreurs (avec ERRMODE_EXCEPTION). Gardez les transactions courtes pour réduire la contention de verrous. Pour les transactions imbriquées, utilisez des savepoints ou un gestionnaire de transactions. Ne laissez jamais une transaction ouverte — commitez toujours ou rollback.
<?php
try {
$pdo->beginTransaction();
$pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?")
->execute([$amount, $fromId]);
$pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
->execute([$amount, $toId]);
// log transaction
$pdo->prepare("INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)")
->execute([$fromId, $toId, $amount]);
$pdo->commit();
echo "Transfer complete";
} catch (PDOException $e) {
$pdo->rollBack(); // undo all changes
error_log("Transfer failed: " . $e->getMessage());
throw new RuntimeException("Transfer failed", 0, $e);
}
// check if in transaction
if ($pdo->inTransaction()) {
$pdo->commit();
}Motifs de récupération de données
Choisissez le bon mode de fetch pour votre cas d'usage. FETCH_ASSOC est le plus courant (tableau avec noms de colonnes). FETCH_CLASS mappe les lignes vers des objets — génial pour les modèles de domaine. FETCH_KEY_PAIR crée des maps id=>valeur (pour les dropdowns). FETCH_GROUP groupe les lignes par la première colonne — utile pour les relations un-à-plusieurs. Pour les grands jeux de résultats, utilisez fetch() dans une boucle au lieu de fetchAll() pour économiser la mémoire. Fermez toujours les curseurs avec $stmt->closeCursor() quand terminé.
<?php
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE active = 1");
$stmt->execute();
// fetch modes
$row = $stmt->fetch(PDO::FETCH_ASSOC); // ['id' => 1, 'name' => 'Alice']
$row = $stmt->fetch(PDO::FETCH_NUM); // [1, 'Alice', '[email protected]']
$row = $stmt->fetch(PDO::FETCH_BOTH); // both (default)
$obj = $stmt->fetch(PDO::FETCH_OBJ); // stdClass with properties
// fetch all
$all = $stmt->fetchAll(PDO::FETCH_ASSOC);
// fetch into class
class User {
public int $id;
public string $name;
}
$stmt->setFetchMode(PDO::FETCH_CLASS, User::class);
$users = $stmt->fetchAll(); // array of User objects
// fetch key-value pairs
$pairs = $pdo->query("SELECT id, name FROM users")
->fetchAll(PDO::FETCH_KEY_PAIR); // [1 => 'Alice', 2 => 'Bob']
// fetch grouped
$grouped = $pdo->query("SELECT dept, name FROM employees")
->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_ASSOC);
// ['IT' => [['name' => 'Alice']], 'HR' => [['name' => 'Bob']]]Bonnes pratiques de base de données
Le motif repository sépare l'accès aux données de la logique métier — rendant le code testable (mockez le PDO) et maintenable. Utilisez l'injection de dépendances pour passer la connexion PDO. Ne créez jamais de nouvelles connexions PDO par requête — réutilisez une seule connexion (ou pool). Pour les applications à fort trafic, envisagez un pooler de connexions (ProxySQL pour MySQL, PgBouncer pour PostgreSQL). Profilez toujours les requêtes lentes avec EXPLAIN et ajoutez les index appropriés. Envisagez un ORM (Doctrine, Eloquent) pour les domaines complexes.
<?php
// 1. Connection singleton (or use DI container)
class Database {
private static ?PDO $instance = null;
public static function conn(): PDO {
if (self::$instance === null) {
self::$instance = new PDO(
"mysql:host=localhost;dbname=app;charset=utf8mb4",
"user", "pass",
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
}
return self::$instance;
}
}
// 2. Repository pattern
class UserRepository {
public function __construct(private PDO $db) {}
public function findById(int $id): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
return $user ?: null;
}
}
// 3. Always close statements (or let them go out of scope)
// 4. Use LIMIT for queries that might return huge results
// 5. Index columns used in WHERE, JOIN, ORDER BY
// 6. Use EXPLAIN to analyze slow queriesDate/Heure et sécurité
Date et heure
Les fonctions de date de PHP utilisent le fuseau horaire du serveur par défaut — définissez toujours date_default_timezone_set('Asia/Shanghai') ou utilisez DateTimeZone explicitement. La classe DateTime est orientée objet et gère les fuseaux horaires, les intervalles, et le formatage mieux que les fonctions procédurales. strtotime() analyse les descriptions de date en anglais (« next Monday », « +1 month ») — pratique mais peut surprendre aux limites de mois. Pour les maths de date, utilisez DateTime::diff() et DateInterval. Stockez toujours les dates en UTC en base de données.
<?php
// current time
$now = date('Y-m-d H:i:s'); // 2024-06-15 14:30:00
$timestamp = time(); // Unix timestamp
$dt = new DateTime(); // DateTime object
// formatting
echo date('Y-m-d'); // 2024-06-15
echo date('d/m/Y H:i:s'); // 15/06/2024 14:30:00
echo date('l, F j, Y'); // Saturday, June 15, 2024
// create from string
$dt = new DateTime('2024-01-15');
$dt = DateTime::createFromFormat('d/m/Y', '15/01/2024');
// modify dates
$dt->modify('+1 month');
$dt->modify('-2 days');
$tomorrow = date('Y-m-d', strtotime('tomorrow'));
$nextWeek = date('Y-m-d', strtotime('+1 week'));
// difference
$start = new DateTime('2024-01-01');
$end = new DateTime('2024-12-31');
$diff = $start->diff($end);
echo $diff->days; // 365
// timezone
$dt = new DateTime('now', new DateTimeZone('Asia/Shanghai'));
$dt->setTimezone(new DateTimeZone('UTC'));Hachage de mots de passe et sécurité
password_hash() utilise bcrypt (ou Argon2 si disponible) avec génération automatique de sel — ne roulez jamais votre propre hachage. password_verify() vérifie sûrement les mots de passe contre les hashes (comparaison à temps constant pour empêcher les attaques temporelles). password_needs_rehash() vous permet de mettre à niveau les hashes quand vous augmentez le facteur de coût. Pour les tokens aléatoires (CSRF, clés API, réinitialisations de mot de passe), utilisez toujours random_bytes() — pas rand() ou mt_rand() qui sont prévisibles. Utilisez hash_hmac pour l'authentification de message.
<?php
// hash password (bcrypt by default)
$hash = password_hash("mypassword", PASSWORD_DEFAULT);
// $2y$10$... (includes algorithm, cost, salt)
// verify password
if (password_verify($input, $hash)) {
echo "Valid password";
}
// check if hash needs rehash (algorithm upgrade)
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash($input, PASSWORD_DEFAULT);
// update stored hash
}
// NEVER use md5() or sha1() for passwords!
// NEVER store plaintext passwords!
// generate secure random
$token = bin2hex(random_bytes(32)); // 64-char hex string
$bytes = random_bytes(16); // raw bytes
$int = random_int(1, 1000); // cryptographically secure
// hash for integrity (not passwords)
$checksum = hash('sha256', $data);
$hmac = hash_hmac('sha256', $data, $secretKey);Échappement de sortie et prévention XSS
XSS est la vulnérabilité web n°1 — échappez toujours la sortie selon le contexte. htmlspecialchars() pour HTML (ENT_QUOTES échappe les simples et doubles quotes). urlencode() pour les URLs. json_encode() avec flags hex pour les contextes JavaScript. Ne faites jamais confiance à l'entrée utilisateur — échappez à la sortie, pas à l'entrée (vous pourriez avoir besoin des données brutes ailleurs). Définissez les en-têtes Content-Security-Policy comme défense en profondeur. Envisagez un moteur de template (Twig, Blade) qui auto-échappe par défaut.
<?php
// XSS: Cross-Site Scripting — always escape output!
$name = $_GET['name']; // could be: <script>alert('xss')</script>
// HTML context
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
// converts < > " ' & to HTML entities
// in HTML template
?>
<p>Hello, <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></p>
<input value="<?= htmlspecialchars($value, ENT_QUOTES, 'UTF-8') ?>">
<?php
// URL context
$url = "https://example.com/search?q=" . urlencode($query);
// JavaScript context
$json = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<script>
var data = <?= $json ?>;
</script>
<?php
// Content Security Policy header
header("Content-Security-Policy: default-src 'self'; script-src 'self'");JSON et réponses API
json_encode/decode sont les fonctions JSON standard. Définissez toujours Content-Type: application/json pour les réponses API. Utilisez JSON_UNESCAPED_UNICODE pour garder le chinois/emoji lisible (sinon ils deviennent \uXXXX). json_decode avec true retourne des tableaux associatifs (plus courant en PHP). Vérifiez toujours json_last_error() après décodage de JSON non fiable. Pour les APIs REST, définissez les codes de statut HTTP appropriés (200, 201, 400, 404, 500) et utilisez une structure de réponse cohérente.
<?php
// encode PHP array/object to JSON
$data = ['name' => 'Alice', 'age' => 30, 'hobbies' => ['reading', 'coding']];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// {"name": "Alice", "age": 30, "hobbies": ["reading", "coding"]}
// decode JSON to PHP
$obj = json_decode($json); // stdClass object
$arr = json_decode($json, true); // associative array
// handle errors
$data = json_decode($badJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException("JSON error: " . json_last_error_msg());
}
// API response
header('Content-Type: application/json; charset=utf-8');
http_response_code(200);
echo json_encode([
'status' => 'success',
'data' => $users,
'meta' => ['page' => 1, 'total' => 100],
], JSON_UNESCAPED_UNICODE);
// API request handling
$input = json_decode(file_get_contents('php://input'), true);Composer et gestion de dépendances
Composer est le gestionnaire de paquets de PHP — essentiel pour le PHP moderne. require spécifie les dépendances de production ; require-dev pour le développement (tests, etc.). L'autoloading PSR-4 mappe les espaces de noms aux répertoires. Commitez toujours composer.json et composer.lock (verrouille les versions exactes). Utilisez composer install (depuis lock) en production, composer update pour obtenir les dernières. Paquets populaires : Monolog (logging), Guzzle (HTTP), PHPUnit (tests), composants Symfony, framework Laravel. Ne commitez jamais le répertoire vendor/.
<?php
// composer.json
// {
// "require": {
// "monolog/monolog": "^3.0",
// "guzzlehttp/guzzle": "^7.0"
// },
// "autoload": {
// "psr-4": {"App\\": "src/"}
// }
// }
// install: composer install
// update: composer update
// add: composer require monolog/monolog
// autoload (at the top of your app)
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('app.log', Logger::WARNING));
$log->warning('User not found', ['user_id' => 42]);
// environment variables (vlucas/phpdotenv)
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$dbHost = $_ENV['DB_HOST'];Développement d'API REST
Gestion des requêtes et réponses JSON
Les APIs REST échangent du JSON. Contrairement aux soumissions de formulaire (qui peuplent $_POST), les requêtes JSON doivent être lues depuis php://input et décodées avec json_decode. Définissez toujours Content-Type: application/json pour les réponses et utilisez http_response_code() pour les codes de statut HTTP appropriés. Validez toute entrée — json_decode ne garantit pas la structure attendue. Utilisez l'opérateur de coalescence null (??) pour un accès sûr. JSON_PRETTY_PRINT est utile pour le débogage mais omettez-le en production pour des payloads plus petits.
// Get JSON from request body (not $_POST for JSON!)
$json = file_get_contents('php://input');
$data = json_decode($json, true); // true = associative array
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
// Process the data
$name = $data['name'] ?? '';
$email = filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL);
// Return JSON response
header('Content-Type: application/json');
http_response_code(200); // 200 OK, 201 Created, 400 Bad Request, etc.
echo json_encode([
'success' => true,
'data' => ['id' => 1, 'name' => $name],
'message' => 'User created'
], JSON_PRETTY_PRINT);Routage et méthodes HTTP
Les APIs REST mappent les méthodes HTTP aux opérations CRUD : GET (lire), POST (créer), PUT/PATCH (mettre à jour), DELETE (supprimer). Le routage correspond la méthode + le chemin URL à un gestionnaire. Utilisez preg_match pour les routes paramétrées (par ex., /api/users/42). En production, utilisez une bibliothèque de routeur (FastRoute, Symfony Routing) ou un framework (Laravel, Slim) pour un routage plus propre, des middlewares, et l'injection de dépendances. Retournez toujours les codes de statut HTTP appropriés : 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 404 (Not Found), 500 (Server Error).
// Simple REST router based on method + path
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = trim($path, '/');
// Route table: method => [pattern => handler]
switch (true) {
case ($method === 'GET' && $path === 'api/users'):
echo json_encode(getAllUsers());
break;
case ($method === 'GET' && preg_match('#^api/users/(\d+)$#', $path, $m)):
echo json_encode(getUser((int)$m[1]));
break;
case ($method === 'POST' && $path === 'api/users'):
$data = json_decode(file_get_contents('php://input'), true);
echo json_encode(createUser($data));
http_response_code(201);
break;
case ($method === 'PUT' && preg_match('#^api/users/(\d+)$#', $path, $m)):
$data = json_decode(file_get_contents('php://input'), true);
echo json_encode(updateUser((int)$m[1], $data));
break;
case ($method === 'DELETE' && preg_match('#^api/users/(\d+)$#', $path, $m)):
deleteUser((int)$m[1]);
http_response_code(204); // No Content
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Not found']);
}Authentification API (JWT)
JWT permet l'authentification sans état — le serveur n'a pas besoin de stocker de sessions. Le token contient un payload (ID utilisateur, expiration) signé avec une clé secrète. Le client envoie le token dans l'en-tête Authorization (Bearer token). Le serveur vérifie la signature pour s'assurer que le token n'a pas été altéré. JWT est génial pour les APIs et microservices (pas de magasin de session partagé nécessaire). Cependant, les JWT ne peuvent pas être révoqués avant expiration — utilisez des temps d'expiration courts et une stratégie de token de rafraîchissement. En production, utilisez la bibliothèque firebase/php-jwt plutôt que d'implémenter la crypto vous-même. Ne stockez jamais de données sensibles dans le payload JWT — il est seulement encodé en base64, pas chiffré.
// JWT (JSON Web Token) authentication flow
// 1. Login: verify credentials, issue JWT
function login($email, $password) {
$user = findUserByEmail($email);
if ($user && password_verify($password, $user['password_hash'])) {
// Create JWT: header.payload.signature
$header = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
$payload = base64url_encode(json_encode([
'user_id' => $user['id'],
'exp' => time() + 3600 // expires in 1 hour
]));
$signature = hash_hmac('sha256', "$header.$payload", SECRET_KEY, true);
$jwt = "$header.$payload." . base64url_encode($signature);
return $jwt;
}
return null;
}
// 2. Verify JWT on protected routes
function verifyJWT($token) {
$parts = explode('.', $token);
if (count($parts) !== 3) return false;
[$header, $payload, $signature] = $parts;
$expected = base64url_encode(
hash_hmac('sha256', "$header.$payload", SECRET_KEY, true)
);
if (!hash_equals($expected, $signature)) return false;
$data = json_decode(base64url_decode($payload), true);
return ($data['exp'] ?? 0) > time() ? $data : false;
}
// Use firebase/php-jwt library in production!Validation et assainissement d'entrée
La validation d'entrée est critique pour la sécurité API. filter_var de PHP fournit des validateurs intégrés (FILTER_VALIDATE_EMAIL, FILTER_VALIDATE_INT, FILTER_VALIDATE_URL) avec des options comme plage min/max. Validez toujours côté serveur — la validation côté client est pour l'UX, pas la sécurité. Assainissez les chaînes avec htmlspecialchars pour empêcher XSS à la sortie HTML. Pour les APIs JSON, retournez 422 (Unprocessable Entity) pour les erreurs de validation avec des messages descriptifs. Envisagez une bibliothèque de validation (Respect/Validation, Symfony Validator) pour les règles complexes. Ne faites jamais confiance à l'entrée utilisateur — validez le type, la longueur, le format, et les règles métier.
// Validate and sanitize API input
function validateUserInput(array $data): array {
$errors = [];
// Required fields
if (empty($data['name'])) {
$errors[] = 'Name is required';
}
// Email validation
$email = filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL);
if (!$email) {
$errors[] = 'Valid email is required';
}
// Age: integer between 18 and 120
$age = filter_var($data['age'] ?? null, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 18, 'max_range' => 120]
]);
if ($age === false) {
$errors[] = 'Age must be between 18 and 120';
}
// String sanitization (remove tags, trim)
$name = htmlspecialchars(trim($data['name'] ?? ''), ENT_QUOTES, 'UTF-8');
// URL validation
$website = filter_var($data['website'] ?? '', FILTER_VALIDATE_URL);
return ['errors' => $errors, 'data' => compact('email', 'age', 'name')];
}
$result = validateUserInput($input);
if (!empty($result['errors'])) {
http_response_code(422); // Unprocessable Entity
echo json_encode(['errors' => $result['errors']]);
exit;
}CORS (Cross-Origin Resource Sharing)
CORS contrôle quels domaines peuvent accéder à votre API depuis un navigateur. Les navigateurs envoient une requête preflight OPTIONS pour les requêtes non simples (PUT/DELETE, en-têtes personnalisés). Votre serveur doit répondre avec les en-têtes Access-Control-Allow-* appropriés. Pour la sécurité, spécifiez des origines exactes plutôt que « * » (surtout avec les credentials). Access-Control-Allow-Credentials: true est nécessaire si l'API utilise des cookies ou des en-têtes Authorization. Vary: Origin indique aux caches que la réponse varie par origine. Un CORS mal configuré peut exposer votre API à n'importe quel site — listez toujours blanche les origines de confiance.
// Enable CORS for API requests from browsers
header('Access-Control-Allow-Origin: https://example.com'); // specific origin
// OR: header('Access-Control-Allow-Origin: *'); // any origin (less secure)
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true'); // for cookies/auth
header('Access-Control-Max-Age: 86400'); // cache preflight for 24h
// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
// Dynamic origin (check against whitelist)
$allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Vary: Origin'); // important for caching
}Sécurité (XSS, CSRF, injection SQL)
Prévention XSS (Cross-Site Scripting)
XSS survient quand des données non fiables sont insérées dans HTML sans échappement, permettant aux attaquants d'exécuter JavaScript dans les navigateurs des victimes. La solution : échappez toujours la sortie avec htmlspecialchars (convertit <, >, &, ", ' en entités HTML). Différents contextes nécessitent différents échappements : corps HTML (htmlspecialchars), attributs HTML (htmlspecialchars avec ENT_QUOTES), JavaScript (json_encode), URLs (urlencode). Les en-têtes Content Security Policy (CSP) ajoutent une défense en profondeur en restreignant d'où les scripts peuvent se charger. N'utilisez jamais eval(), innerHTML, ou document.write() avec l'entrée utilisateur. Les frameworks comme Twig et Blade auto-échappent par défaut.
// XSS: attacker injects malicious JavaScript into your page
// BAD: outputting user input without escaping
echo "<p>" . $_GET['name'] . "</p>";
// If name = <script>alert('xss')</script>, it executes!
// GOOD: escape output with htmlspecialchars
echo "<p>" . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8') . "</p>";
// & < > " ' are converted to HTML entities
// For HTML attributes:
echo '<input value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
// For JavaScript context (JSON encode):
echo '<script>var name = ' . json_encode($name) . ';</script>';
// For URL parameters:
echo '<a href="?q=' . urlencode($query) . '">Search</a>';
// Content Security Policy (CSP) header — defense in depth
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
// Use prepared statements (prevents SQL injection too)Prévention CSRF (Cross-Site Request Forgery)
CSRF trompe le navigateur d'un utilisateur authentifié pour envoyer une requête à votre site (par ex., un transfert d'argent) sans son consentement. La défense : incluez un token imprévisible dans les formulaires que l'attaquant ne peut pas deviner. Le token est stocké dans la session et vérifié à la soumission. Utilisez hash_equals() pour la comparaison temporellement sûre (empêche les attaques temporelles). Pour les appels AJAX/API, les cookies SameSite=Strict et l'exigence d'en-têtes personnalisés (comme X-Requested-With) fournissent une protection. Les requêtes GET ne devraient jamais modifier de données (elles peuvent être déclenchées par des balises d'image ou des liens). Les frameworks comme Laravel et Symfony ont un middleware CSRF intégré.
// CSRF: attacker tricks a logged-in user into submitting a form
// to your site (using their session cookie)
// Defense: anti-CSRF tokens
session_start();
// Generate a token (once per session)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Include token in forms as a hidden field
echo '<form method="POST" action="/transfer">';
echo '<input type="hidden" name="csrf_token" value="' . $_SESSION['csrf_token'] . '">';
echo '<input type="text" name="amount">';
echo '<button type="submit">Transfer</button>';
echo '</form>';
// Verify token on POST/PUT/DELETE requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $token)) {
http_response_code(403);
die('CSRF token validation failed');
}
// Process the form...
}
// For APIs: use SameSite cookies + custom headers
// (browsers block cross-origin requests without explicit CORS permission)Prévention de l'injection SQL
L'injection SQL est la vulnérabilité web n°1 — elle laisse les attaquants lire/modifier/supprimer toute votre base de données. La solution universelle : les requêtes préparées (requêtes paramétrées). La structure de la requête et les données sont envoyées séparément, donc l'entrée utilisateur ne peut jamais être interprétée comme SQL. Ne concaténez jamais l'entrée utilisateur dans les requêtes. prepare/execute de PDO gère l'échappement automatiquement. Bind les paramètres avec leurs types (PDO::PARAM_INT, PDO::PARAM_STR). Pour les clauses IN avec items variables, générez les placeholders dynamiquement. Définissez PDO::ATTR_EMULATE_PREPARES à false pour de vraies requêtes préparées côté serveur (meilleure sécurité).
// SQL injection: attacker manipulates queries via unescaped input
// BAD: string concatenation
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
// If id = "1 OR 1=1", returns ALL users!
// If id = "1; DROP TABLE users; --", deletes the table!
// GOOD: prepared statements with parameter binding
$pdo = new PDO('mysql:host=localhost;dbname=myapp', $user, $pass);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // real prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Multiple parameters:
$stmt = $pdo->prepare('INSERT INTO users (name, email, age) VALUES (:name, :email, :age)');
$stmt->execute([
':name' => $name,
':email' => $email,
':age' => $age
]);
// IN clause with variable arguments:
$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);Hachage de mots de passe et authentification
password_hash() utilise bcrypt (ou Argon2) avec un sel aléatoire — le standard de l'industrie pour le stockage de mots de passe. Le sel est intégré dans le hash, donc vous ne le gérez pas séparément. password_verify() compare sûrement l'entrée contre le hash stocké (temporellement sûr). password_needs_rehash() vous permet de mettre à niveau les hashes quand vous augmentez les facteurs de coût ou changez d'algorithme — il vérifie si le hash correspond aux paramètres courants et re-hache au prochain login. N'utilisez jamais MD5, SHA1, ou texte en clair pour les mots de passe — ils sont trivialement cassables. Appliquez des politiques de mot de passe fort mais préférez la longueur à la complexité (NIST recommande 8+ caractères minimum).
// NEVER store plain-text or MD5/SHA1 passwords!
// Use password_hash() (bcrypt/argon2 by default)
// Hash a password (on registration)
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_DEFAULT);
// PASSWORD_DEFAULT = bcrypt (or argon2id in PHP 7.3+)
// Store $hash in the database
// Verify a password (on login)
if (password_verify($inputPassword, $storedHash)) {
// Password is correct
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
} else {
echo "Invalid credentials";
}
// Rehash if algorithm was upgraded (migration)
if (password_verify($input, $hash) &&
password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash($input, PASSWORD_DEFAULT);
// Update database with $newHash
}
// Password requirements validation
if (strlen($password) < 8 ||
!preg_match('/[A-Z]/', $password) ||
!preg_match('/[a-z]/', $password) ||
!preg_match('/[0-9]/', $password)) {
echo "Password must be 8+ chars with upper, lower, and number";
}Sécurité des uploads de fichiers
Les uploads de fichiers sont un vecteur d'attaque majeur. Ne faites jamais confiance à $_FILES['type'] (défini par le navigateur, facilement falsifié) — utilisez finfo pour détecter le vrai type MIME. N'utilisez jamais le nom de fichier fourni par l'utilisateur (il pourrait contenir du path traversal comme ../../script.php) — générez un nom aléatoire. Stockez les uploads hors de la racine web ou dans un répertoire avec l'exécution PHP désactivée. Pour les images, ré-encodez-les (imagecreatefromjpeg + imagejpeg) pour retirer le code PHP embarqué caché dans les données EXIF. Limitez la taille des fichiers pour empêcher le déni de service. Validez l'extension, le type MIME, et les magic bytes. Envisagez de scanner les uploads avec un antivirus (ClamAV) pour une sécurité supplémentaire.
// Secure file upload handling
$upload = $_FILES['avatar'];
// 1. Check for upload errors
if ($upload['error'] !== UPLOAD_ERR_OK) {
die('Upload failed: error ' . $upload['error']);
}
// 2. Validate file size
$maxSize = 2 * 1024 * 1024; // 2MB
if ($upload['size'] > $maxSize) {
die('File too large (max 2MB)');
}
// 3. Validate MIME type (don't trust $_FILES['type']!)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($upload['tmp_name']);
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mimeType, $allowedTypes)) {
die('Invalid file type');
}
// 4. Generate a safe filename (never use user-supplied name)
$safeName = bin2hex(random_bytes(16)) . '.jpg';
// 5. Store OUTSIDE the web root (or in a non-executable dir)
$dest = __DIR__ . '/uploads/' . $safeName;
if (!move_uploaded_file($upload['tmp_name'], $dest)) {
die('Failed to save file');
}
// 6. For images: re-encode to strip malicious metadata
$img = imagecreatefromjpeg($dest);
imagejpeg($img, $dest, 90); // re-encode strips embedded PHP/scriptscURL et requêtes HTTP
cURL GET et POST de base
cURL est le client HTTP le plus puissant de PHP, supportant GET, POST, méthodes personnalisées, en-têtes, cookies, et SSL. Définissez toujours CURLOPT_RETURNTRANSFER pour obtenir la réponse comme chaîne (sinon elle est echoée directement). CURLOPT_TIMEOUT empêche le blocage sur les serveurs lents. Pour POST avec JSON, définissez les en-têtes Content-Type et Content-Length explicitement. Vérifiez curl_errno() pour les erreurs de connexion et curl_getinfo(CURLINFO_HTTP_CODE) pour le statut HTTP. Fermez toujours les handles cURL avec curl_close() pour libérer les ressources. Pour un code plus simple, envisagez Guzzle (un wrapper cURL avec une API plus propre).
// GET request
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
}
curl_close($ch);
$data = json_decode($response, true);
// POST request with JSON body
$ch = curl_init('https://api.example.com/users');
$payload = json_encode(['name' => 'Alice', 'email' => '[email protected]']);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
'Content-Length: ' . strlen($payload)
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);cURL avec authentification et cookies
cURL supporte plusieurs méthodes d'authentification. CURLOPT_USERPWD définit l'HTTP Basic Auth. Les tokens Bearer vont dans l'en-tête Authorization. Pour les sessions basées sur cookies (comme se logger à un site), utilisez CURLOPT_COOKIEJAR pour sauvegarder les cookies et CURLOPT_COOKIEFILE pour les envoyer sur les requêtes suivantes — cela maintient une session à travers plusieurs appels cURL. Utilisez un fichier temporaire pour les cookies et nettoyez-le avec unlink(). Pour les appels API, préférez l'auth basée sur token (Bearer) aux cookies. Utilisez toujours HTTPS (cURL vérifie SSL par défaut — ne désactivez pas CURLOPT_SSL_VERIFYPEER en production).
// Basic Auth
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_USERPWD, 'username:password');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
// Bearer token (API key)
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Accept: application/json'
]);
// Cookie-based session (login then reuse cookies)
$cookieFile = tempnam(sys_get_temp_dir(), 'cookie');
// Step 1: Login (saves cookies)
$ch = curl_init('https://example.com/login');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['user' => 'alice', 'pass' => 'secret']),
CURLOPT_COOKIEJAR => $cookieFile, // save cookies here
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);
// Step 2: Access protected page (sends saved cookies)
$ch = curl_init('https://example.com/dashboard');
curl_setopt_array($ch, [
CURLOPT_COOKIEFILE => $cookieFile, // send cookies from here
CURLOPT_RETURNTRANSFER => true,
]);
$dashboard = curl_exec($ch);
curl_close($ch);
unlink($cookieFile); // cleanupTéléchargements de fichiers et streaming
Pour les gros téléchargements de fichiers, utilisez CURLOPT_FILE pour écrire directement vers un handle de fichier — cela évite de charger toute la réponse en mémoire. CURLOPT_FOLLOWLOCATION suit les redirections HTTP (301, 302). Pour le streaming (par ex., données en temps réel), utilisez CURLOPT_WRITEFUNCTION pour traiter les chunks au fur et à mesure — utile pour les APIs qui strement des données. CURLOPT_PROGRESSFUNCTION surveille la progression download/upload. Définissez un CURLOPT_TIMEOUT généreux pour les gros fichiers. Pour les très gros uploads, utilisez CURLOPT_INFILE pour streamer depuis un fichier au lieu de charger en mémoire. Fermez toujours les handles de fichier et handles cURL pour empêcher les fuites de ressources.
// Download a file to disk
$ch = curl_init('https://example.com/large-file.zip');
$fp = fopen('downloaded.zip', 'w');
curl_setopt_array($ch, [
CURLOPT_FILE => $fp, // write directly to file
CURLOPT_FOLLOWLOCATION => true, // follow redirects
CURLOPT_TIMEOUT => 300, // 5 min for large files
]);
curl_exec($ch);
fclose($fp);
curl_close($ch);
// Stream response with callback (progress monitoring)
$ch = curl_init('https://example.com/stream');
curl_setopt_array($ch, [
CURLOPT_WRITEFUNCTION => function($ch, $chunk) {
echo $chunk; // stream to output
ob_flush();
flush();
return strlen($chunk);
},
CURLOPT_PROGRESSFUNCTION => function($ch, $dlTotal, $dlNow, $ulTotal, $ulNow) {
if ($dlTotal > 0) {
printf("
Progress: %.1f%%", ($dlNow / $dlTotal) * 100);
}
return 0;
},
]);
curl_exec($ch);
curl_close($ch);Requêtes concurrentes (Multi cURL)
curl_multi_exec exécute plusieurs requêtes HTTP en parallèle — considérablement plus rapide que les requêtes séquentielles quand vous avez besoin de données de plusieurs endpoints. Le motif : créez un handle multi, ajoutez les handles cURL individuels, exécutez le handle multi dans une boucle (curl_multi_exec + curl_multi_select pour l'efficacité), puis collectez les résultats. C'est utile pour agréger des données de plusieurs APIs, préfetcher des ressources, ou des opérations par lots. Pour une concurrence plus avancée, envisagez ReactPHP ou Amp (frameworks PHP async). Notez que multi-cURL bloque toujours le processus PHP — pour du vrai async, utilisez des event loops ou des files de messages.
// Fetch multiple URLs in parallel (much faster than sequential)
$urls = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments'
];
$multi = curl_multi_init();
$handles = [];
// Create individual handles
foreach ($urls as $i => $url) {
$handles[$i] = curl_init($url);
curl_setopt($handles[$i], CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($multi, $handles[$i]);
}
// Execute all requests concurrently
$active = null;
do {
$status = curl_multi_exec($multi, $active);
if ($active) {
curl_multi_select($multi); // wait for activity
}
} while ($active && $status === CURLM_OK);
// Collect results
$responses = [];
foreach ($handles as $i => $ch) {
$responses[$i] = json_decode(curl_multi_getcontent($ch), true);
curl_multi_remove_handle($multi, $ch);
curl_close($ch);
}
curl_multi_close($multi);
// $responses[0], [1], [2] now contain all three API responsesUtiliser Guzzle (client HTTP moderne)
Guzzle est le client HTTP standard pour le PHP moderne — beaucoup plus propre que le cURL brut. Il fournit une API fluide, des objets requête/réponse conformes PSR-7, des middlewares (logging, retry), et des requêtes async via Promises. L'option « json » auto-encode le corps et définit Content-Type. getAsync/postAsync retournent des Promises pour les requêtes concurrentes sans la complexité du multi-cURL. La gestion des exceptions est intégrée : RequestException attrape les erreurs HTTP (4xx, 5xx). Guzzle est utilisé par la plupart des frameworks (le client HTTP de Laravel enveloppe Guzzle). Installez via Composer : composer require guzzlehttp/guzzle.
// Guzzle is the most popular PHP HTTP client (composer require guzzlehttp/guzzle)
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://api.example.com',
'timeout' => 30,
'headers' => ['Accept' => 'application/json'],
]);
// GET request
$response = $client->get('/users/42');
$body = json_decode($response->getBody(), true);
echo $response->getStatusCode(); // 200
// POST with JSON
$response = $client->post('/users', [
'json' => ['name' => 'Alice', 'email' => '[email protected]'],
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
// Concurrent requests (Promise-based)
$promises = [
'users' => $client->getAsync('/users'),
'posts' => $client->getAsync('/posts'),
];
$results = GuzzleHttp\Promise\Utils::settle($promises)->wait();
// Error handling with try/catch
try {
$response = $client->get('/nonexistent');
} catch (RequestException $e) {
echo $e->getResponse()->getStatusCode(); // 404
}Plongée DateTime
Créer et manipuler DateTime
DateTime est la classe date/heure robuste de PHP. DateTimeImmutable est préféré à DateTime — il retourne un nouvel objet à la modification, empêchant les bugs de mutation accidentelle (critique quand la même date est utilisée à plusieurs endroits). createFromFormat analyse les formats personnalisés. modify() accepte les expressions relatives comme « +1 week » ou « last day of next month ». Spécifiez toujours les fuseaux horaires explicitement pour éviter un comportement dépendant de la config serveur. Pour les maths de date (ajout d'intervalles), utilisez DateInterval (« P1D » = 1 jour, « P2W » = 2 semaines, « PT2H » = 2 heures) avec add()/sub().
$now = new DateTime(); // current time
$now->setTimezone(new DateTimeZone('Asia/Shanghai'));
// From string (many formats supported)
$date = new DateTime('2024-06-15 14:30:00');
$date = new DateTime('first day of next month');
$date = new DateTime('next Friday');
// From format (precise parsing)
$date = DateTime::createFromFormat('d/m/Y', '15/06/2024');
// Modify dates with relative expressions
$date->modify('+1 week');
$date->modify('-2 days');
$date->modify('last day of this month');
$date->modify('+1 year 3 months');
// Immutable version (recommended — doesn't modify original)
$dt = new DateTimeImmutable('2024-06-15');
$next = $dt->modify('+1 day'); // $dt unchanged, $next is new object
echo $dt->format('Y-m-d'); // 2024-06-15 (unchanged!)
echo $next->format('Y-m-d'); // 2024-06-16Formatage et fuseaux horaires
format() utilise des lettres de motif pour personnaliser la sortie — Y (année 4 chiffres), m (mois 2 chiffres), d (jour 2 chiffres), H (24 heures), i (minutes), s (secondes). Pour ISO 8601 (utilisé dans les APIs), utilisez « Y-m-d\TH:i:sP » ou le raccourci « c ». Conversion de fuseau horaire : créez avec le fuseau source, puis setTimezone pour convertir. Stockez toujours les dates en UTC en base de données et convertissez vers le fuseau de l'utilisateur uniquement pour l'affichage. La base de données de fuseaux horaires de PHP est complète (inclut les règles DST). Utilisez DateTimeZone::listIdentifiers() pour obtenir toutes les zones supportées. Le caractère d'échappement « T » dans format() sort un « T » littéral (pour ISO 8601).
// Format codes (most common):
// Y=2024 y=24 m=06 n=6 d=15 j=15
// H=14 (24h) h=02 (12h) i=30 s=00 A=PM a=pm
// D=Mon l=Monday M=Jun F=June N=1 (Mon=1..Sun=7)
$date = new DateTime('2024-06-15 14:30:00');
echo $date->format('Y-m-d H:i:s'); // 2024-06-15 14:30:00
echo $date->format('l, F j, Y'); // Saturday, June 15, 2024
echo $date->format('Y-m-d\TH:i:sP'); // 2024-06-15T14:30:00+08:00 (ISO 8601)
// Timezone conversion
$utc = new DateTime('2024-06-15 14:00:00', new DateTimeZone('UTC'));
$utc->setTimezone(new DateTimeZone('Asia/Shanghai'));
echo $utc->format('H:i'); // 22:00 (UTC+8)
// List supported timezones
$tzs = DateTimeZone::listIdentifiers();
// ['UTC', 'America/New_York', 'Asia/Shanghai', ...]
// Get timezone offset
$tz = new DateTimeZone('America/Los_Angeles');
$offset = $tz->getOffset(new DateTime()); // seconds from UTCIntervalles de date et différences
DateInterval représente une durée en utilisant le format de durée ISO 8601 (P1Y2M3DT4H5M6S). add() et sub() appliquent les intervalles aux dates. diff() retourne un DateInterval représentant la différence entre deux dates — la propriété « days » donne le total des jours, tandis que « y », « m », « d » donnent les décompositions par composant. La propriété « invert » indique la direction (1 si la deuxième date est antérieure). Attention à l'arithmétique de mois : ajouter « P1M » au 31 jan. donne le 2 mars (fév. a 28-29 jours), pas le 31 fév. Pour les calculs de jours ouvrés, itérez et sautez les week-ends/jours fériés manuellement ou utilisez une bibliothèque comme nesbot/carbon.
// DateInterval: represents a duration
$interval = new DateInterval('P1Y2M3DT4H5M6S');
// P = period, 1Y = 1 year, 2M = 2 months, 3D = 3 days
// T = time separator, 4H = 4 hours, 5M = 5 minutes, 6S = 6 seconds
$date = new DateTime('2024-06-15');
$date->add(new DateInterval('P1M')); // +1 month → 2024-07-15
$date->sub(new DateInterval('P10D')); // -10 days → 2024-07-05
// Difference between two dates
$d1 = new DateTime('2024-01-01');
$d2 = new DateTime('2024-12-31');
$diff = $d1->diff($d2);
echo $diff->days; // 365 (total days)
echo $diff->m; // 0 (months component)
echo $diff->y; // 0 (years component)
echo $diff->format('%a days'); // 365 days
echo $diff->format('%y years, %m months, %d days'); // 0 years, 11 months, 30 days
// Check if inverted (d2 < d1)
echo $diff->invert; // 0 if d2 >= d1, 1 if d2 < d1DatePeriod (itération de plages de dates)
DatePeriod itère sur une plage de dates à un intervalle spécifié — parfait pour générer des calendriers, rapports, ou événements récurrents. Le constructeur prend (start, interval, end) ou (start, interval, recurrences). La date de fin est exclusive. Cas d'usage courants : générer tous les jours d'un mois pour une vue calendrier, lister les périodes de paie, ou créer des plannings d'événements récurrents. Utilisez iterator_to_array() pour matérialiser la période en tableau. Pour les règles de récurrence complexes (par ex., « chaque 2e mardi »), envisagez une bibliothèque dédiée comme rrule (règles de récurrence RFC 5545).
// DatePeriod iterates over a range of dates
$start = new DateTime('2024-06-01');
$end = new DateTime('2024-06-10');
$interval = new DateInterval('P1D'); // 1 day
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $day) {
echo $day->format('Y-m-d (D)') . "\n";
}
// 2024-06-01 (Sat)
// 2024-06-02 (Sun)
// ... through 2024-06-09 (end is exclusive)
// Generate next 12 months
$start = new DateTime('first day of this month');
$interval = new DateInterval('P1M');
$period = new DatePeriod($start, $interval, 12); // 12 recurrences
foreach ($period as $month) {
echo $month->format('F Y') . "\n";
}
// Every Monday for a year
$start = new DateTime('next Monday');
$end = new DateTime('+1 year');
$period = new DatePeriod($start, new DateInterval('P1W'), $end);
$mondays = iterator_to_array($period);Bibliothèque Carbon (DateTime amélioré)
Carbon étend DateTime avec une API fluide et expressive — c'est le standard de facto dans l'écosystème PHP (utilisé par Laravel). diffForHumans() produit « il y a 5 jours », « dans 3 heures » — parfait pour les timestamps d'UI. L'API fluide chaîne les méthodes (addYear()->subMonth()->endOfMonth()). Les méthodes de comparaison (isWeekend, isPast, isToday) simplifient les vérifications courantes. La localisation supporte 50+ langues pour une sortie lisible par l'humain. Carbon 3 (2024+) est immutable par défaut. Installez via Composer : composer require nesbot/carbon. Si vous utilisez Laravel, Carbon est déjà inclus.
// Carbon: the most popular DateTime library (composer require nesbot/carbon)
use Carbon\Carbon;
$now = Carbon::now('Asia/Shanghai');
$tomorrow = Carbon::tomorrow();
$lastWeek = Carbon::now()->subWeek();
// Human-readable differences
echo Carbon::now()->diffForHumans(Carbon::now()->subDays(5));
// "5 days ago"
echo Carbon::now()->addHours(3)->diffForHumans();
// "3 hours from now"
// Fluent API
$date = Carbon::create(2024, 6, 15, 14, 30, 0)
->addYear()
->subMonth()
->endOfMonth()
->setTimezone('UTC');
// Comparison methods
if ($date->isWeekend()) { echo "Weekend!"; }
if ($date->isPast()) { echo "Past"; }
if ($date->isFuture()) { echo "Future"; }
if ($date->isToday()) { echo "Today"; }
if ($date->isLeapYear()) { echo "Leap year"; }
// Localization
Carbon::setLocale('zh');
echo Carbon::now()->subDay()->diffForHumans(); // "1天前"Espaces de noms et autoloading
Bases des espaces de noms
Les espaces de noms organisent le code en packages hiérarchiques, empêchant les collisions de noms de classes entre bibliothèques. La déclaration namespace doit être la première instruction (après declare()). L'instruction « use » importe les classes d'autres espaces de noms — placez les use en haut du fichier. L'aliasing (as) résout les conflits quand deux classes ont le même nom. Le backslash de tête (\DateTime) fait référence à l'espace de noms global. Les espaces de noms PHP utilisent les backslashes (\) comme séparateurs, mappant à la structure de répertoires dans l'autoloading PSR-4. Les use groupés (use App\Models\{User, Post}) réduisent le boilerplate.
<?php
// Namespaces prevent name collisions (like packages in Java)
namespace App\Services;
class UserService {
public function find($id) { /* ... */ }
}
// Using namespaced classes
use App\Services\UserService;
use App\Models\User;
$service = new UserService();
$user = new User();
// Aliasing (resolve conflicts or shorten names)
use App\Services\UserService as USvc;
use App\Models\User as UserModel;
// Global namespace (backslash prefix)
$now = new \DateTime(); // \ means root/global namespace
$array = new \ArrayObject();
// Multiple use statements grouped
use App\Models\{User, Post, Comment};
use App\Services\{UserService, PostService};Standard d'autoloading PSR-4
PSR-4 est la spécification d'autoloading standard — il mappe les espaces de noms aux chemins de répertoires, donc vous n'avez jamais besoin d'instructions require/include manuelles. La règle : App\Services\UserService mappe vers src/Services/UserService.php (App\ → src/). Configurez le mappage dans la section autoload de composer.json. Après avoir ajouté de nouvelles classes, exécutez « composer dump-autoload » pour régénérer la class map. Le fichier vendor/autoload.php (généré par Composer) gère le chargement — incluez-le une fois dans votre point d'entrée (index.php). PSR-4 exige que les noms de classes correspondent aux noms de fichiers (UserService → UserService.php).
// PSR-4: namespace structure maps to file paths
// App\Services\UserService → src/Services/UserService.php
// composer.json PSR-4 configuration:
// {
// "autoload": {
// "psr-4": {
// "App\\": "src/"
// }
// }
// }
// File: src/Services/UserService.php
namespace App\Services;
class UserService {
public function getUser($id) {
return "User $id";
}
}
// File: src/Models/User.php
namespace App\Models;
class User {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
// After adding classes, regenerate autoloader:
// $ composer dump-autoload
// The autoloader is included once in your entry point:
require __DIR__ . '/vendor/autoload.php';
$service = new App\Services\UserService();
$user = new App\Models\User('Alice');Autoloading sans Composer (spl_autoload)
spl_autoload_register enregistre une fonction appelée quand une classe n'est pas encore chargée — elle reçoit le nom de classe pleinement qualifié et devrait require le fichier correspondant. Vous pouvez enregistrer plusieurs autoloaders (ils sont appelés dans l'ordre). C'est ce que Composer utilise en interne. Pour la production, utilisez toujours l'autoloader PSR-4 de Composer — il est optimisé, gère les cas limites, et génère des class maps pour des lookups plus rapides. Utilisez spl_autoload_register directement uniquement pour les tout petits projets ou quand Composer n'est pas disponible. Le paramètre « true » dans class_exists() déclenche l'autoloading si la classe n'est pas chargée.
<?php
// Simple autoloader for small projects (no Composer needed)
spl_autoload_register(function ($className) {
// Convert namespace separators to directory separators
$file = __DIR__ . '/src/' . str_replace('\\', '/', $className) . '.php';
// App\Services\UserService → src/App/Services/UserService.php
// Adjust prefix mapping as needed:
$file = str_replace('App/', '', $file); // remove 'App' prefix
if (file_exists($file)) {
require $file;
}
});
// Now classes are loaded automatically on first use
$service = new App\Services\UserService();
// Multiple autoloaders (called in order until one loads the class)
spl_autoload_register(function ($class) {
$path = __DIR__ . '/lib/' . $class . '.php';
if (file_exists($path)) require $path;
});
// Check if a class is autoloadable
if (class_exists('App\Helper', true)) { // true = attempt autoload
$helper = new App\Helper();
}Constantes et fonctions d'espace de noms
Les espaces de noms peuvent contenir des constantes et fonctions, pas seulement des classes. Importez-les avec « use const » et « use function » (PHP 5.6+). C'est utile pour les constantes de configuration et les fonctions utilitaires. Les appels de fonction/constante non qualifiés ont un comportement de repli : PHP cherche d'abord dans l'espace de noms courant, puis repli vers l'espace de noms global. C'est pourquoi vous pouvez appeler strlen() sans backslash — mais pour la performance et la clarté, préfixez les fonctions globales avec \ dans le code namespacé. Les imports groupés (use App\Config\{const DB_HOST, function connect}) réduisent la verbosité.
<?php
namespace App\Config;
// Constants in namespaces
const DB_HOST = 'localhost';
const DB_PORT = 3306;
// Functions in namespaces
function connect() {
return 'Connected to ' . DB_HOST;
}
// Using namespaced constants and functions
use const App\Config\DB_HOST;
use function App\Config\connect;
echo DB_HOST; // localhost
echo connect(); // Connected to localhost
// Or with fully-qualified names:
echo \App\Config\DB_HOST;
echo \App\Config\connect();
// Namespace-level use for multiple imports
use App\Config\{const DB_HOST, const DB_PORT, function connect};
// Fallback: unqualified function calls fall back to global
// if not found in current namespace
namespace App;
$len = strlen('hello'); // calls global strlen() (fallback)Classes anonymes et autoloading
Les classes anonymes (PHP 7+) vous permettent de créer des objets simples uniques sans définir de classe nommée — utiles pour les interfaces, objets mock, et callbacks. Elles peuvent implémenter des interfaces, étendre des classes, avoir des constructeurs, et utiliser des traits. La classe est générée à l'exécution avec un nom auto-généré (class@anonymous). Les classes anonymes sont chargées immédiatement (pas d'autoloading nécessaire). Utilisez-les pour : les motifs de stratégie simples, les doubles de test/mocks, les écouteurs d'événements, et les DTOs. Pour les classes réutilisables, définissez toujours des classes nommées avec un autoloading PSR-4 approprié. Les classes anonymes sont particulièrement pratiques dans les tests pour créer des stubs légers.
<?php
namespace App\Factory;
// Anonymous class (PHP 7+): create a one-off class inline
interface Logger {
public function log(string $msg): void;
}
class App {
private $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
}
$app = new App();
$app->setLogger(new class implements Logger {
public function log(string $msg): void {
echo "[LOG] $msg\n";
}
});
// Anonymous class with constructor
$comparator = new class($ascending = true) {
private $asc;
public function __construct(bool $asc) { $this->asc = $asc; }
public function compare($a, $b): int {
return $this->asc ? $a <=> $b : $b <=> $a;
}
};
// Get the auto-generated class name
echo get_class($comparator); // class@anonymous...POO approfondie (Traits, Interfaces, Abstract)
Classes et méthodes abstraites
Les classes abstraites fournissent une base avec implémentation partagée que les sous-classes étendent. Elles ne peuvent pas être instanciées directement. Les méthodes abstraites définissent un contrat (signature uniquement) que les sous-classes concrètes doivent implémenter — c'est le « motif template method ». Contrairement aux interfaces, les classes abstraites peuvent avoir des propriétés, constructeurs, et méthodes concrètes. Utilisez les classes abstraites quand les sous-classes partagent une implémentation significative (la relation « is-a »). Utilisez les interfaces quand vous avez juste besoin d'un contrat que n'importe quelle classe peut implémenter (la relation « can-do »). Une classe peut étendre une seule classe abstraite mais implémenter plusieurs interfaces.
<?php
// Abstract class: can't be instantiated, may have abstract methods
abstract class Animal {
protected $name;
public function __construct(string $name) {
$this->name = $name;
}
// Abstract method: must be implemented by subclasses
abstract public function makeSound(): string;
// Concrete method: shared implementation
public function describe(): string {
return $this->name . " says " . $this->makeSound();
}
}
class Dog extends Animal {
public function makeSound(): string {
return "Woof!";
}
}
class Cat extends Animal {
public function makeSound(): string {
return "Meow!";
}
}
$dog = new Dog("Rex");
echo $dog->describe(); // Rex says Woof!
// new Animal("test"); // Error: cannot instantiate abstract classInterfaces et implémentation multiple
Les interfaces définissent un contrat — signatures de méthodes sans implémentation. Une classe peut implémenter plusieurs interfaces (contrairement à l'héritage simple pour les classes). Les interfaces permettent le polymorphisme : n'importe quelle classe implémentant Comparable peut être triée, quel que soit son type concret. Utilisez les interfaces pour définir des capacités (Comparable, Serializable, Iterable) qui traversent les hiérarchies de classes. Le type hinting avec interfaces (function sort(Comparable $a)) est plus flexible qu'avec des classes concrètes. L'héritage d'interface (interface A extends B, C) combine les contrats. Le PHP moderne supporte aussi les constantes d'interface et les méthodes statiques dans les interfaces.
<?php
// Interface: pure contract (no implementation)
interface Comparable {
public function compareTo($other): int;
}
interface Serializable {
public function serialize(): string;
public function unserialize(string $data): void;
}
// A class can implement MULTIPLE interfaces
class Product implements Comparable, Serializable {
private $price;
public function __construct(float $price) {
$this->price = $price;
}
public function compareTo($other): int {
return $this->price <=> $other->price; // spaceship operator
}
public function serialize(): string {
return serialize($this->price);
}
public function unserialize(string $data): void {
$this->price = unserialize($data);
}
}
// Type hinting with interfaces
function sortItems(array $items): array {
usort($items, fn($a, $b) => $a->compareTo($b));
return $items;
}
// Interface inheritance
interface Repository extends Comparable, Serializable {
public function find(int $id): ?object;
}Traits (réutilisation de code sans héritage)
Les traits fournissent la réutilisation horizontale de code — des méthodes qui peuvent être « collées » dans n'importe quelle classe sans héritage. Cela résout le problème du diamant (PHP a l'héritage simple). Usages courants de traits : logging, motif singleton, soft deletes, timestamps. Une classe peut utiliser plusieurs traits. Quand les traits ont des méthodes conflictuelles, utilisez « insteadof » pour en choisir une et « as » pour aliaser l'autre. Les traits peuvent avoir des méthodes abstraites (forçant la classe utilisatrice à les implémenter) et des méthodes/propriétés statiques. Attention à ne pas abuser des traits — ils peuvent rendre le code plus difficile à tracer. Préférez la composition (injection de dépendances) aux traits pour le comportement complexe.
<?php
// Trait: reusable method groups (PHP's answer to multiple inheritance)
trait Logger {
protected function log(string $msg, string $level = 'INFO'): void {
echo "[$level] " . date('Y-m-d H:i:s') . " $msg\n";
// In real code: write to file/database
}
}
trait Singleton {
private static $instance;
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
class UserService {
use Logger, Singleton;
public function findUser($id) {
$this->log("Finding user $id");
return "User $id";
}
}
$svc = UserService::getInstance();
$svc->findUser(42); // [INFO] 2024-06-15 14:30:00 Finding user 42
// Conflict resolution when traits have same method
trait A { public function hello() { return 'A'; } }
trait B { public function hello() { return 'B'; } }
class C {
use A, B {
B::hello insteadof A; // use B's hello
A::hello as helloFromA; // alias A's as helloFromA
}
}Liaison statique tardive (static:: vs self::)
La Liaison Statique Tardive (LSB) est la différence entre self:: (compilation, fait toujours référence à la classe définissante) et static:: (exécution, fait référence à la classe appelante). Cela compte dans l'héritage : si Base a une méthode utilisant self::$table, elle voit toujours $table de Base même quand appelée sur Child. Utiliser static::$table la fait voir $table de Child. LSB est essentielle pour les motifs factory (new static() crée des instances de la classe appelante), ActiveRecord (chaque modèle a sa propre table), et le motif singleton. Le type de retour « static » (PHP 8+) déclare que la méthode retourne une instance de la classe appelante.
<?php
class Base {
protected static $table = 'base';
public static function getTable(): string {
// self:: refers to the class where the method is DEFINED
return self::$table; // always 'base'
}
public static function getTableStatic(): string {
// static:: refers to the class that was CALLED (runtime)
return static::$table; // late static binding
}
public static function create(): static {
// 'static' return type + new static() = factory pattern
return new static();
}
}
class Child extends Base {
protected static $table = 'child';
}
echo Child::getTable(); // 'base' (self:: = Base)
echo Child::getTableStatic(); // 'child' (static:: = Child)
echo get_class(Child::create()); // 'Child' (new static = Child)
// self:: is resolved at compile time (the defining class)
// static:: is resolved at runtime (the calling class)
// This is "Late Static Binding" — essential for factory patternsMéthodes magiques
Les méthodes magiques sont des méthodes spéciales qui interceptent les opérations sur les objets. __get/__set créent des propriétés dynamiques (utiles pour les data transfer objects, ORMs). __toString permet echo $object. __invoke rend un objet appelable comme une fonction. __isset/__unset supportent isset()/unset() sur les propriétés dynamiques. __debugInfo personnalise la sortie var_dump. Autres méthodes magiques : __construct, __destruct, __clone (pour le clonage profond), __call/__callStatic (pour les méthodes non définies, permet les APIs fluides et mixins), __serialize/__unserialize (remplace __sleep/__wakeup en PHP 7.4+). Utilisez les méthodes magiques avec parcimonie — elles ajoutent du comportement « magique » qui peut être difficile à déboguer. Documentez-les clairement.
<?php
class MagicBox {
private $data = [];
// Called when accessing undefined properties
public function __get($name) {
return $this->data[$name] ?? null;
}
// Called when setting undefined properties
public function __set($name, $value) {
$this->data[$name] = $value;
}
// Called when isset() or empty() on undefined property
public function __isset($name): bool {
return isset($this->data[$name]);
}
// Called when unset() on undefined property
public function __unset($name): void {
unset($this->data[$name]);
}
// Called when object is used as string
public function __toString(): string {
return json_encode($this->data);
}
// Called when object is called as function
public function __invoke($arg) {
return "Called with $arg";
}
// Called for var_dump/debugging
public function __debugInfo(): array {
return ['keys' => array_keys($this->data)];
}
}
$box = new MagicBox();
$box->name = "Alice"; // __set
echo $box->name; // __get → Alice
echo $box; // __toString → {"name":"Alice"}
echo $box("test"); // __invoke → Called with testGestion de paquets Composer
Bases de composer.json
composer.json est le manifeste pour les projets PHP. require liste les dépendances de production avec contraintes de version (^ autorise les mises à jour mineures, ~ autorise les patchs). autoload définit le mappage espace de noms-vers-répertoire PSR-4. require-dev contient les dépendances de développement uniquement. Exécutez composer install pour configurer le projet.
{
"name": "myorg/myapp",
"type": "project",
"require": {
"php": ">=8.1",
"monolog/monolog": "^3.0",
"symfony/console": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": { "MyApp\\": "src/" }
}
}Installer et mettre à jour
composer install lit composer.lock pour les versions exactes (builds reproductibles). composer require ajoute un paquet et résout les dépendances. composer update récupère les versions plus récentes dans les contraintes. Utilisez --no-dev pour la production. --optimize-autoloader convertit PSR-4 en classmap pour un autoloading plus rapide en production.
# Install all dependencies from composer.lock
composer install
# Add a package (modifies composer.json)
composer require monolog/monolog
composer require --dev phpunit/phpunit
# Update all packages to latest allowed versions
composer update
# Update a single package
composer update monolog/monolog
# Production install (no dev, optimized autoloader)
composer install --no-dev --optimize-autoloader
# Show installed packages
composer showContraintes de version
Le caret (^) est la contrainte la plus courante : autorise les changements qui ne modifient pas le chiffre non-zéro le plus à gauche. Le tilde (~) verrouille au niveau du patch. Pour les versions 0.x, ^0.3 autorise 0.3.x mais pas 0.4. Utilisez toujours des contraintes pour obtenir les correctifs de sécurité tout en évitant les changements cassants. Épinglez les versions exactes dans composer.lock pour la reproductibilité.
"require": {
// Caret: >=1.2.0, <2.0.0 (allows minor+patch)
"vendor/pkg": "^1.2",
// Tilde: >=1.2.0, <1.3.0 (patch only)
"vendor/pkg2": "~1.2",
// Exact version
"vendor/pkg3": "1.2.3",
// Range
"vendor/pkg4": ">=1.0 <2.0",
// Wildcard
"vendor/pkg5": "1.2.*",
// Stability flags
"vendor/pkg6": "dev-main",
"vendor/pkg7": "2.0@beta"
}Autoloading PSR-4
L'autoloading PSR-4 mappe les préfixes d'espace de noms aux répertoires : MyApp\Services\UserService résout vers src/Services/User.php. Exécutez composer dump-autoload après avoir ajouté de nouvelles classes. Pour la production, utilisez --optimize pour générer une classmap (une recherche dans un tableau au lieu de vérifications du système de fichiers). L'autoloading classmap scanne les répertoires et est le plus rapide pour les bases de code fixes.
// PSR-4: "MyApp\\": "src/" means
// MyApp\User -> src/User.php
// MyApp\Services\Auth -> src/Services/Auth.php
namespace MyApp\Services;
class UserService
{
public function find(int $id): ?User
{
// ...
}
}
// After running composer dump-autoload:
// require 'vendor/autoload.php';
// $service = new \MyApp\Services\UserService();
// Classmap (faster for production)
// "autoload": { "classmap": ["src/", "lib/"] }Scripts et hooks
Les scripts Composer définissent des commandes spécifiques au projet. Exécutez avec composer <name>. Les événements intégrés (post-install-cmd, post-update-cmd, pre-autoload-dump) se déclenchent automatiquement. Les scripts peuvent référencer d'autres scripts avec @name. Utilisez les scripts pour standardiser les workflows de développement à travers les membres de l'équipe.
{
"scripts": {
"test": "phpunit",
"lint": "phpcs --standard=PSR12 src/",
"fix": "phpcbf --standard=PSR12 src/",
"post-install-cmd": [
"MyApp\\Setup::postInstall",
"php artisan migrate"
],
"post-update-cmd": "@post-install-cmd"
}
}
// Run: composer test, composer lint, etc.cURL avancé
Multi-requêtes (parallèle)
curl_multi_exec exécute plusieurs requêtes en parallèle, réduisant considérablement le temps total pour les appels API par lots. curl_multi_select bloque jusqu'à ce qu'il y ait de l'activité, évitant l'attente active. Fermez toujours les handles et le handle multi pour libérer les ressources. C'est la fondation du scraping HTTP haute performance et de l'agrégation d'API.
<?php
$urls = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments',
];
$multi = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
$handles[$i] = curl_init($url);
curl_setopt($handles[$i], CURLOPT_RETURNTRANSFER, true);
curl_setopt($handles[$i], CURLOPT_TIMEOUT, 10);
curl_multi_add_handle($multi, $handles[$i]);
}
do {
$status = curl_multi_exec($multi, $active);
if ($active) curl_multi_select($multi);
} while ($active && $status === CURLM_OK);
foreach ($handles as $i => $ch) {
$responses[$i] = curl_multi_getcontent($ch);
curl_multi_remove_handle($multi, $ch);
}
curl_multi_close($multi);Réponses streaming
CURLOPT_WRITEFUNCTION fournit un callback pour chaque chunk de la réponse, permettant le traitement streaming des gros fichiers sans les charger entièrement en mémoire. Retournez la longueur du chunk pour signaler la consommation. C'est essentiel pour télécharger de gros fichiers, traiter des APIs streaming, ou parser CSV/JSON incrémentalement.
<?php
$ch = curl_init('https://example.com/large-file.csv');
$file = fopen('download.csv', 'w');
curl_setopt($ch, CURLOPT_FILE, $file); // Write to file
// OR use callback for streaming processing:
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) use ($file) {
fwrite($file, $chunk);
// Or parse incrementally:
// $lines = explode("\n", $chunk);
return strlen($chunk); // Must return bytes consumed
});
curl_exec($ch);
fclose($file);
curl_close($ch);Authentification et cookies
Définissez des en-têtes personnalisés avec CURLOPT_HTTPHEADER pour l'authentification (tokens Bearer, clés API). COOKIEJAR/COOKIEFILE persistent les cookies entre les requêtes pour l'auth basée sur session. CURLOPT_USERPWD définit l'HTTP Basic Auth. Pour POST, définissez CURLOPT_POSTFIELDS avec JSON et l'en-tête Content-Type. Définissez toujours Accept pour contrôler le format de réponse.
<?php
$ch = curl_init('https://api.example.com/data');
// Bearer token
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
]);
// Cookie jar (persist across requests)
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt'); // Save
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt'); // Load
// Basic auth
curl_setopt($ch, CURLOPT_USERPWD, 'username:password');
// POST with JSON body
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['key' => 'value']));
$response = curl_exec($ch);Gestion d'erreurs et retries
Vérifiez toujours la valeur de retour de curl_exec (false en cas d'échec) et curl_error pour le message. curl_getinfo fournit le code de statut HTTP, le timing, et les infos de redirection. Implémentez un backoff exponentiel pour les retries afin de gérer les limites de débit et les échecs transitoires. Distinguez les erreurs réseau (erreur curl) des erreurs HTTP (code de statut).
<?php
function fetchWithRetry(string $url, int $max = 3): ?string
{
for ($attempt = 1; $attempt <= $max; $attempt++) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$error = curl_error($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response !== false && $code >= 200 && $code < 300) {
return $response;
}
if ($attempt < $max) {
sleep(pow(2, $attempt)); // Exponential backoff
}
}
throw new RuntimeException("Failed: {$error}");
}Référence des options cURL
CURLOPT_FOLLOWLOCATION suit les redirections HTTP (3xx). Gardez toujours SSL_VERIFYPEER true en production pour empêcher les attaques MITM ; téléchargez cacert.pem depuis curl.haxx.se. CURLOPT_ENCODING active la compression. Utilisez CURLOPT_VERBOSE avec STDERR pour déboguer les problèmes de connexion. Définissez des timeouts raisonnables pour éviter les blocages.
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return not echo
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
// SSL (keep VERIFYPEER true in production!)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
// Performance
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
// Debug
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, fopen('curl.log', 'w'));Traitement d'image (GD)
Créer et charger des images
imagecreatetruecolor crée une image true color (millions de couleurs). imagecolorallocate enregistre une couleur et retourne un identifiant. imagecreatefromjpeg/png/webp charge des fichiers existants. Vérifiez toujours la valeur de retour (false en cas d'échec). Utilisez imagesx/imagesy pour obtenir les dimensions. Libérez la mémoire avec imagedestroy quand terminé.
<?php
// Create a blank image
$img = imagecreatetruecolor(400, 300);
// Allocate colors
$white = imagecolorallocate($img, 255, 255, 255);
$red = imagecolorallocate($img, 255, 0, 0);
// Fill background
imagefill($img, 0, 0, $white);
// Load existing images
$photo = imagecreatefromjpeg('photo.jpg');
$png = imagecreatefrompng('logo.png');
$webp = imagecreatefromwebp('image.webp');
// Get dimensions
$width = imagesx($photo);
$height = imagesy($photo);Dessiner des formes et du texte
GD fournit des primitives de dessin : rectangles, ellipses, lignes, polygones, et arcs. Les variantes remplies (imagefilled*) dessinent des formes pleines. imagettftext rend les polices TrueType avec contrôle d'angle et de taille. Envoyez toujours un en-tête Content-Type avant de sortir des données d'image. Appelez imagedestroy pour libérer la mémoire.
<?php
$img = imagecreatetruecolor(400, 300);
$white = imagecolorallocate($img, 255, 255, 255);
$red = imagecolorallocate($img, 255, 0, 0);
$blue = imagecolorallocate($img, 0, 0, 255);
imagefill($img, 0, 0, $white);
// Shapes
imagerectangle($img, 50, 50, 150, 120, $red);
imagefilledrectangle($img, 200, 50, 300, 120, $blue);
imageellipse($img, 100, 200, 80, 80, $red);
imageline($img, 0, 0, 400, 300, $red);
// Text with TrueType font
imagettftext($img, 20, 0, 50, 270, $red, 'arial.ttf', 'Hello GD!');
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);Redimensionner et rogner
imagecopyresampled produit des résultats de meilleure qualité qu'imagecopyresized (utilise l'interpolation). Maintenez le ratio d'aspect en calculant les dimensions depuis l'original. Pour les miniatures, rognage centré vers un carré pour une mise en page cohérente. Détruisez toujours les images source après copie pour empêcher les fuites de mémoire dans le traitement par lots.
<?php
function resizeImage($src, $maxW, $maxH) {
list($w, $h) = getimagesize($src);
$ratio = min($maxW / $w, $maxH / $h);
$newW = (int)($w * $ratio);
$newH = (int)($h * $ratio);
$srcImg = imagecreatefromjpeg($src);
$dstImg = imagecreatetruecolor($newW, $newH);
// High-quality resampling
imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0,
$newW, $newH, $w, $h);
imagedestroy($srcImg);
return $dstImg;
}
// Center crop to square
function cropSquare($src, $size) {
list($w, $h) = getimagesize($src);
$min = min($w, $h);
$x = (int)(($w - $min) / 2);
$y = (int)(($h - $min) / 2);
$dst = imagecreatetruecolor($size, $size);
$img = imagecreatefromjpeg($src);
imagecopyresampled($dst, $img, 0, 0, $x, $y, $size, $size, $min, $min);
return $dst;
}Filtres et effets
imagefilter applique des effets intégrés : niveaux de gris, luminosité (plage -255 à 255), contraste (négatif augmente), flou, détection de contours, négatif, et colorisation (RGB + alpha). Pixelate crée un effet mosaïque. Ils sont rapides mais basiques ; pour les effets avancés, utilisez ImageMagick (extension Imagick) qui supporte les matrices de convolution et les filtres personnalisés.
<?php
$img = imagecreatefromjpeg('photo.jpg');
// Built-in filters
imagefilter($img, IMG_FILTER_GRAYSCALE); // B&W
imagefilter($img, IMG_FILTER_BRIGHTNESS, 30); // Brighten
imagefilter($img, IMG_FILTER_CONTRAST, -20); // More contrast
imagefilter($img, IMG_FILTER_GAUSSIAN_BLUR); // Blur
imagefilter($img, IMG_FILTER_EDGEDETECT); // Edge detection
imagefilter($img, IMG_FILTER_NEGATE); // Invert colors
// Colorize (tint)
imagefilter($img, IMG_FILTER_COLORIZE, 0, 0, 100, 0); // Blue tint
// Pixelate
imagefilter($img, IMG_FILTER_PIXELATE, 10, true);
imagepng($img, 'filtered.png');
imagedestroy($img);Filigranes et compositing
imagecopymerge superpose une image sur une autre avec une opacité réglable (0-100). Les filigranes PNG avec canaux alpha se fondent naturellement. Pour les filigranes texte, utilisez imagecolorallocatealpha pour du texte semi-transparent. La qualité imagejpeg va de 0 (pire) à 100 (meilleur) ; 75-90 est un bon équilibre pour le web. Détruisez toujours les deux images.
<?php
$photo = imagecreatefromjpeg('photo.jpg');
$watermark = imagecreatefrompng('logo.png');
$pw = imagesx($photo); $ph = imagesy($photo);
$ww = imagesx($watermark); $wh = imagesy($watermark);
// Position: bottom-right with 20px padding
$destX = $pw - $ww - 20;
$destY = $ph - $wh - 20;
// Merge with 50% opacity
imagecopymerge($photo, $watermark, $destX, $destY, 0, 0, $ww, $wh, 50);
// Text watermark
$color = imagecolorallocatealpha($photo, 255, 255, 255, 60);
imagettftext($photo, 30, 0, 20, $ph - 20, $color, 'arial.ttf', '© 2025');
imagejpeg($photo, 'watermarked.jpg', 90); // 90% quality
imagedestroy($photo);
imagedestroy($watermark);API REST approfondie
Routage et gestion de requête
Les APIs REST mappent les méthodes HTTP aux opérations CRUD : GET (lire), POST (créer), PUT/PATCH (mettre à jour), DELETE (supprimer). Parsez le chemin URL pour l'identification des ressources. Lisez le corps de requête depuis php://input pour POST/PUT. Retournez toujours les codes de statut HTTP appropriés (200, 201, 400, 404, 500) et les réponses JSON avec l'en-tête Content-Type.
<?php
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($path, '/'));
switch (true) {
case $method === 'GET' && $segments[0] === 'users':
if (isset($segments[1])) getUser((int)$segments[1]);
else listUsers();
break;
case $method === 'POST' && $segments[0] === 'users':
$data = json_decode(file_get_contents('php://input'), true);
createUser($data);
break;
case $method === 'PUT' && $segments[0] === 'users' && isset($segments[1]):
$data = json_decode(file_get_contents('php://input'), true);
updateUser((int)$segments[1], $data);
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Not Found']);
}Réponse et codes de statut
Définissez toujours Content-Type: application/json pour les réponses API. Utilisez les bons codes de statut : 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 422 (Unprocessable Entity), 429 (Too Many Requests), 500 (Server Error). Incluez les détails d'erreur pour le débogage mais n'exposez jamais les stack traces en production.
<?php
function jsonResponse($data, int $status = 200): void
{
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
exit;
}
// Success responses
jsonResponse(['data' => $users], 200); // OK
jsonResponse(['data' => $user], 201); // Created
// Error responses
jsonResponse(['error' => 'Validation failed'], 400);
jsonResponse(['error' => 'Unauthorized'], 401);
jsonResponse(['error' => 'Forbidden'], 403);
jsonResponse(['error' => 'Not Found'], 404);
jsonResponse(['error' => 'Server error'], 500);Pagination et filtrage
Implémentez la pagination avec LIMIT/OFFSET et retournez les métadonnées (total, page courante, total des pages). Validez et assainissez les colonnes de tri contre une liste blanche pour empêcher l'injection SQL. Plafonnez per_page pour empêcher les requêtes excessives. Utilisez LIKE pour la recherche avec wildcards. Retournez les métadonnées de pagination dans un objet meta séparé des données.
<?php
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = min(100, max(1, (int)($_GET['per_page'] ?? 20)));
$offset = ($page - 1) * $perPage;
// Whitelist sort columns (prevent SQL injection)
$sort = in_array($_GET['sort'] ?? '', ['name', 'email'])
? $_GET['sort'] : 'id';
$order = strtolower($_GET['order'] ?? 'asc') === 'desc' ? 'DESC' : 'ASC';
$sql = "SELECT * FROM users ORDER BY $sort $order LIMIT ? OFFSET ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$perPage, $offset]);
$users = $stmt->fetchAll();
// Total count for pagination metadata
$total = (int)$pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
jsonResponse([
'data' => $users,
'meta' => [
'page' => $page,
'per_page' => $perPage,
'total' => $total,
'total_pages' => ceil($total / $perPage),
],
]);Limitation de débit
La limitation de débit empêche l'abus d'API. Utilisez des algorithmes à fenêtre fixe (simple) ou fenêtre glissante (plus précis). Stockez les compteurs dans Redis pour les systèmes distribués. Retournez les en-têtes X-RateLimit (Limit, Remaining, Reset) pour que les clients puissent s'autoréguler. HTTP 429 avec Retry-After indique aux clients quand réessayer. Pour la production, utilisez Redis ou un limiteur de débit dédié.
<?php
function checkRateLimit(int $userId, int $max = 100, int $window = 3600): bool
{
$file = sys_get_temp_dir() . "/rate_{$userId}_" . floor(time() / $window);
$count = file_exists($file) ? (int)file_get_contents($file) : 0;
if ($count >= $max) {
$reset = (floor(time() / $window) + 1) * $window;
header("X-RateLimit-Limit: $max");
header("X-RateLimit-Remaining: 0");
header("Retry-After: " . ($reset - time()));
http_response_code(429);
echo json_encode(['error' => 'Rate limit exceeded']);
return false;
}
file_put_contents($file, $count + 1);
header("X-RateLimit-Remaining: " . ($max - $count - 1));
return true;
}
if (!checkRateLimit($userId)) exit;Versionnage d'API
Stratégies de versionnage d'API : le préfixe d'URL (/v1/) est le plus explicite et cache-friendly ; l'en-tête Accept est RESTful mais plus difficile à tester. Documentez les APIs avec des annotations OpenAPI (Swagger). Générez des docs interactives avec des outils comme swagger-php. Versionnez dès le départ ; les changements cassants nécessitent une nouvelle version. Dépréciez les anciennes versions avec l'en-tête Sunset.
<?php
// Versioning via URL prefix: /v1/users, /v2/users
$version = $segments[0] ?? 'v1';
// Versioning via Accept header
// Accept: application/vnd.myapp.v2+json
preg_match('/vnd\.myapp\.(v\d+)\+json/',
$_SERVER['HTTP_ACCEPT'] ?? '', $m);
$version = $m[1] ?? 'v1';
// OpenAPI/Swagger documentation
/**
* @OA\Get(path="/api/users",
* @OA\Response(response=200, description="List users")
* )
*/Sécurité approfondie (XSS/CSRF)
Prévention XSS
XSS (Cross-Site Scripting) injecte des scripts malveillants dans les pages web. Prévenez-le en encodant la sortie selon le contexte : htmlspecialchars pour HTML, json_encode pour JavaScript, urlencode pour URLs. ENT_QUOTES échappe les simples et doubles quotes. Content-Security-Policy (CSP) ajoute une défense en profondeur en restreignant les sources de scripts. Ne faites jamais confiance à l'entrée utilisateur.
<?php
// Output encoding (prevents XSS)
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// JavaScript string (use json_encode)
echo '<script>var name = ' . json_encode($name) . ';</script>';
// URL parameter
echo 'redirect=' . urlencode($url);
// Content-Security-Policy header
header("Content-Security-Policy: default-src 'self'; script-src 'self'");
// Disable inline scripts
header("X-XSS-Protection: 1; mode=block");Protection CSRF
CSRF (Cross-Site Request Forgery) trompe les utilisateurs pour qu'ils soumettent des actions non désirées. Prévenez-le avec des tokens anti-CSRF : générez un token aléatoire par session, incluez-le dans les formulaires comme champ caché, et vérifiez sur POST/PUT/DELETE. Utilisez hash_equals pour la comparaison temporellement sûre. Pour AJAX, envoyez le token dans un en-tête personnalisé. Les cookies SameSite=Strict fournissent une protection supplémentaire.
<?php
// Generate CSRF token
function csrfToken(): string {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// Verify CSRF token (timing-safe)
function verifyCsrf(string $token): bool {
return !empty($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
// In the form
echo '<input type="hidden" name="csrf_token" value="' . csrfToken() . '">';
// On POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verifyCsrf($_POST['csrf_token'] ?? '')) {
http_response_code(403);
die('CSRF token validation failed');
}
}Prévention de l'injection SQL
L'injection SQL permet aux attaquants d'exécuter du SQL arbitraire. Utilisez toujours des requêtes préparées avec des requêtes paramétrées : la base de données sépare la logique SQL des données, rendant l'injection impossible. Ne concaténez jamais l'entrée utilisateur dans des chaînes SQL. Pour les requêtes dynamiques (clauses IN, ORDER BY), construisez la structure SQL avec des placeholders et passez les valeurs comme paramètres.
<?php
// BAD: string concatenation (SQL injection vulnerable)
$sql = "SELECT * FROM users WHERE name = '" . $_GET['name'] . "'";
// Attack: ?name=' OR '1'='1
// GOOD: prepared statements
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = ? AND status = ?');
$stmt->execute([$_GET['name'], 'active']);
$users = $stmt->fetchAll();
// Named parameters
$stmt = $pdo->prepare(
'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([':name' => $name, ':email' => $email]);
// Dynamic IN clause (still safe)
$ids = [1, 2, 3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);Hachage de mots de passe
Ne stockez jamais de mots de passe en texte clair. password_hash utilise bcrypt (ou Argon2 si disponible) avec génération automatique de sel. Le hash inclut l'algorithme, le coût, et le sel, donc password_verify peut vérifier contre n'importe quel format. Utilisez password_needs_rehash pour mettre à niveau les hashes quand vous augmentez le facteur de coût ou changez d'algorithme. Argon2 est recommandé pour les nouvelles applications.
<?php
// Hash a password (uses bcrypt by default, auto-salts)
$hash = password_hash('myPassword123', PASSWORD_DEFAULT);
// $2y$10$... (bcrypt with cost 10)
// Verify a password
if (password_verify($inputPassword, $storedHash)) {
// Check if rehash is needed (algorithm upgrade)
if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {
$newHash = password_hash($inputPassword, PASSWORD_DEFAULT);
// Update stored hash in database
}
// Log user in
}
// Argon2 (PHP 7.2+, requires libsodium)
$hash = password_hash('password', PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64 MB
'time_cost' => 4, // iterations
'threads' => 2,
]);Validation d'entrée
Validez l'entrée côté serveur toujours (la validation côté client est pour l'UX uniquement). Utilisez filter_input avec FILTER_VALIDATE_* pour la vérification de type et FILTER_SANITIZE_* pour le nettoyage. Pour les règles personnalisées, utilisez regex ou des bibliothèques de validation dédiées (Respect/Validation, Symfony Validator). Utilisez une approche liste blanche : acceptez uniquement les champs connus, rejetez tout le reste.
<?php
// Validate email
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
$errors[] = 'Invalid email';
}
// Validate integer with range
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 150]
]);
// Validate URL
$url = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);
// Custom validation with regex
function validateUsername(string $u): ?string {
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $u)) {
return 'Username must be 3-20 alphanumeric chars';
}
return null;
}
// Whitelist approach for arrays
$allowed = ['name', 'email', 'age'];
$input = array_intersect_key($_POST, array_flip($allowed));Espaces de noms et autoloading approfondis
Déclaration d'espace de noms
Les espaces de noms empêchent les collisions de noms de classes et organisent le code hiérarchiquement. La déclaration namespace doit être la première instruction. use importe les classes, fonctions, et constantes. Les alias (as) résolvent les conflits. Le namespacing PHP utilise les backslashes. Le standard PSR-4 mappe les séparateurs d'espace de noms aux séparateurs de répertoires : MyApp\Services\UserService -> src/Services/UserService.php.
<?php
// File: src/Services/UserService.php
namespace MyApp\Services;
use MyApp\Models\User;
use MyApp\Repositories\UserRepository;
use MyApp\Exceptions\NotFoundException;
class UserService
{
public function __construct(
private UserRepository $repo
) {}
public function find(int $id): User
{
$user = $this->repo->findById($id);
if (!$user) {
throw new NotFoundException("User {$id} not found");
}
return $user;
}
}Autoloading PSR-4
PSR-4 est la spécification d'autoloading standard : un préfixe d'espace de noms mappe vers un répertoire de base, et chaque séparateur d'espace de noms devient un séparateur de répertoire. Composer génère l'autoloader qui résout les noms de classes vers les chemins de fichiers automatiquement. Exécutez composer dump-autoload après avoir ajouté de nouvelles classes. L'autoloader ne charge les classes que lorsqu'elles sont référencées pour la première fois (lazy loading).
// composer.json
{
"autoload": {
"psr-4": {
"MyApp\\": "src/",
"Tests\\": "tests/"
}
}
}
// After composer install:
require __DIR__ . '/vendor/autoload.php';
// Now all classes auto-load:
use MyApp\Services\UserService;
use MyApp\Controllers\UserController;
// PSR-4 rules:
// MyApp\User -> src/User.php
// MyApp\Services\Auth -> src/Services/Auth.php
// MyApp\Models\User\Profile -> src/Models/User/Profile.phpAutoloader personnalisé
spl_autoload_register ajoute une fonction à la pile d'autoloader. Quand une classe est référencée mais non chargée, PHP appelle chaque autoloader enregistré dans l'ordre jusqu'à ce qu'un charge la classe. Plusieurs autoloaders peuvent coexister (par ex., un pour PSR-4, un pour les classes legacy). Vérifiez toujours si le fichier existe avant de require pour éviter les erreurs. Composer utilise ce mécanisme en interne.
<?php
spl_autoload_register(function (string $class): void {
$prefix = 'MyApp\\';
$baseDir = __DIR__ . '/src/';
// Check if class uses our namespace prefix
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return; // Not our class
}
// Get relative class name
$relativeClass = substr($class, $len);
// Replace namespace separators with directory separators
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
if (file_exists($file)) {
require $file;
}
});
// Multiple autoloaders can be registered (stack-based)
spl_autoload_register(function ($class) {
$path = __DIR__ . '/legacy/' . $class . '.php';
if (file_exists($path)) require $path;
});Autoloading Classmap et Files
L'autoloading classmap scanne les répertoires au moment du dump-autoload et construit un tableau mappant les noms de classes aux chemins de fichiers. C'est plus rapide que PSR-4 (une recherche dans un tableau vs vérifications du système de fichiers) et est recommandé pour la production. files autoload des fichiers spécifiques à chaque requête, utile pour les fonctions utilitaires et les constantes qui ne peuvent pas être autoloadées comme classes. Utilisez --optimize pour la classmap de production.
{
"autoload": {
"psr-4": { "MyApp\\": "src/" },
"classmap": ["src/Legacy/", "lib/OldClasses.php"],
"files": ["src/helpers.php", "src/constants.php"]
}
}
// classmap: scans directories and builds class-to-file map
// Faster than PSR-4 for production (no filesystem checks)
// files: always-loaded files (for functions and constants)
// src/helpers.php:
<?php
function dd($var) { var_dump($var); die; }
function env(string $key, $default = null) { /* ... */ }Résolution d'espace de noms
Dans le code namespacé, les noms de classes non qualifiés se résolvent via les imports d'abord, puis l'espace de noms courant. Les classes intégrées (DateTime, PDO, Exception) vivent dans l'espace de noms global ; référencez-les avec un backslash de tête ou importez-les. Les fonctions et constantes replient vers l'espace de noms global si non trouvées localement. Utilisez le FQCN (backslash de tête) pour les références absolues.
<?php
namespace MyApp\Services;
use MyApp\Models\User;
class UserService
{
public function create(): User
{
// User resolves to MyApp\Models\User (imported)
return new User();
}
public function find(): \MyApp\Models\User
{
// Fully Qualified Class Name (leading backslash)
return new \MyApp\Models\User();
}
public function date(): \DateTime
{
// Global classes need backslash or import
return new \DateTime();
}
}Générateurs et yield
Générateur de base
Les générateurs produisent des valeurs paresseusement avec yield, une à la fois, sans construire toute la collection en mémoire. C'est économe en mémoire pour les séquences grandes ou infinies. La fonction retourne un objet Generator qui implémente Iterator. Chaque yield met en pause l'exécution, reprend à l'itération suivante. Utilisez les générateurs pour lire de gros fichiers, les curseurs de base de données, et les séquences calculées.
<?php
function rangeGen(int $start, int $end, int $step = 1): Generator
{
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
// Iterate without loading all values into memory
foreach (rangeGen(1, 5) as $value) {
echo $value . ' '; // 1 2 3 4 5
}
// Memory efficient for large sequences
foreach (rangeGen(1, 1000000) as $value) {
if ($value % 100000 === 0) echo "Reached {$value}\n";
}Yield de paires clé-valeur
Les générateurs peuvent yield des paires clé-valeur en utilisant la syntaxe yield key => value, comme les tableaux associatifs. Cela préserve les clés à travers les transformations. Pour filtrer les valeurs, ne les yield simplement pas. Le Generator maintient sa position dans l'itération, donc vous pouvez construire un traitement en pipeline où chaque générateur transforme ou filtre le flux.
<?php
function mapGen(array $data): Generator
{
foreach ($data as $key => $value) {
yield $key => strtoupper($value);
}
}
foreach (mapGen(['a' => 'hello', 'b' => 'world']) as $key => $value) {
echo "{$key} => {$value}\n";
}
// a => HELLO
// b => WORLD
// Filter by not yielding unwanted values
function filterGen(array $data): Generator
{
foreach ($data as $value) {
if ($value > 0) yield $value;
}
}Envoyer des valeurs aux générateurs
La méthode send() passe une valeur dans le générateur, qui devient le résultat de l'expression yield. Cela permet la communication bidirectionnelle, utile pour les coroutines et machines à états. current() démarre le générateur. getReturn() récupère la valeur de retour après que le générateur termine. Le bloc finally s'exécute quand le générateur est détruit, permettant le nettoyage des ressources.
<?php
function accumulator(): Generator
{
$total = 0;
while (true) {
$value = yield $total;
if ($value === null) break;
$total += $value;
}
return $total;
}
$gen = accumulator();
$gen->current(); // Start: 0
$gen->send(10); // Returns 10
$gen->send(20); // Returns 30
$gen->send(5); // Returns 35
$gen->send(null); // Triggers return
echo $gen->getReturn(); // 35Yield from (délégation)
yield from délègue à un autre générateur, tableau, ou Traversable, aplatissant ses valeurs dans le générateur externe. La valeur de retour du générateur interne est disponible pour le générateur externe. Cela permet la composition : construisez des pipelines complexes à partir de générateurs simples. yield from est aussi plus efficace que d'itérer et re-yielder manuellement.
<?php
function innerGen(): Generator
{
yield 1;
yield 2;
return 'inner done';
}
function outerGen(): Generator
{
yield 0;
$result = yield from innerGen();
echo "Inner returned: {$result}\n";
yield 3;
}
foreach (outerGen() as $value) {
echo $value . ' '; // 0 1 2 3
}
// Inner returned: inner done
// Yield from arrays
function mixedGen(): Generator {
yield from [10, 20, 30];
yield from new ArrayObject([40, 50]);
}Cas d'usage pratiques
Les générateurs excellent à traiter de grands flux de données infinis : lire des fichiers ligne par ligne, itération de curseur de base de données, fetch d'API paginée, et séquences mathématiques. Le motif take() limite un générateur infini. Les générateurs composent bien : pipez les données à travers plusieurs générateurs pour filtrer, mapper, et réduire. La mémoire reste constante quelle que soit la taille des données.
<?php
// 1. Read large files line by line (memory efficient)
function readLines(string $file): Generator {
$handle = fopen($file, 'r');
while (!feof($handle)) {
$line = fgets($handle);
if ($line !== false) yield rtrim($line);
}
fclose($handle);
}
foreach (readLines('large.log') as $line) {
if (str_contains($line, 'ERROR')) {
echo "{$line}\n";
}
}
// 2. Infinite sequence with take()
function naturals(): Generator {
$n = 1;
while (true) yield $n++;
}
function take(Generator $gen, int $n): Generator {
for ($i = 0; $i < $n; $i++) {
yield $gen->current();
$gen->next();
}
}
foreach (take(naturals(), 5) as $num) {
echo $num . ' '; // 1 2 3 4 5
}Sécurité
Prévention de l'injection SQL
L'injection SQL survient quand l'entrée utilisateur est concaténée dans SQL. Utilisez toujours des requêtes préparées avec des requêtes paramétrées. PDO et MySQLi les supportent tous les deux. Ne faites jamais confiance à l'entrée utilisateur. Validez et assainissez toutes les données externes.
// BAD: vulnerable
$sql = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "'";
// GOOD: prepared statements
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->execute([':name' => $_POST['name']]);
$users = $stmt->fetchAll();Prévention XSS
XSS (Cross-Site Scripting) injecte des scripts malveillants. htmlspecialchars convertit les caractères spéciaux en entités HTML. ENT_QUOTES échappe les simples et doubles quotes. Échappez toujours à la sortie des données utilisateur vers HTML. Utilisez les en-têtes Content-Security-Policy pour la défense en profondeur.
// BAD: output without escaping
echo $_GET['name'];
// GOOD: escape output
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
// For HTML attributes
echo 'value="' . htmlspecialchars($value, ENT_QUOTES) . '"';Hachage de mots de passe
password_hash utilise bcrypt (ou argon2) avec génération automatique de sel. N'utilisez jamais md5 ou sha1 pour les mots de passe. password_verify vérifie un mot de passe contre un hash. password_needs_rehash permet de mettre à niveau les algorithmes de hash. Le sel est intégré dans la chaîne de hash.
// Hash a password
$hash = password_hash('mypassword', PASSWORD_DEFAULT);
// Verify
if (password_verify('mypassword', $hash)) {
echo 'Valid password';
}
// Check if needs rehash
if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
$newHash = password_hash('mypassword', PASSWORD_DEFAULT);
}Protection CSRF
CSRF (Cross-Site Request Forgery) trompe les utilisateurs pour des actions non d ésirées. Générez un token aléatoire par session. Incluez-le dans les formulaires comme champ caché. Vérifiez sur POST en utilisant hash_equals (comparaison temporellement sûre). Les cookies SameSite fournissent une protection supplémentaire.
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
// In form
echo '<input type="hidden" name="token" value="' . $_SESSION['token'] . '">';
// Verify
if (!hash_equals($_SESSION['token'], $_POST['token'] ?? '')) {
die('CSRF token mismatch');
}Sécurité de session
cookie_httponly empêche l'accès JavaScript. cookie_secure garantit HTTPS uniquement. samesite=Strict empêche CSRF. use_strict_mode rejette les IDs de session non initialisés. session_regenerate_id empêche la fixation de session. Régénérez toujours après les changements de privilège.
// Secure session settings
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // HTTPS only
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', 1);
session_start();
// Regenerate ID after login
session_regenerate_id(true);Snippets PHP associés
Copy-paste ready code for common tasks.
Tableaux et fonctions de tableau en PHP
Créer des tableaux indexés, associatifs et multidimensionnels avec map et filter.
Fonctions de chaîne en PHP
Manipuler des chaînes avec substr, replace, explode et sprintf en PHP.
Lire et écrire des fichiers en PHP
Lire, écrire, ajouter et itérer des fichiers avec les fonctions de système de fichiers PHP.
Requêtes de base de données PDO en PHP
Se connecter et exécuter des requêtes préparées en toute sécurité avec PDO en PHP.
Sessions et cookies en PHP
Stocker des données utilisateur entre les requêtes avec sessions et cookies en PHP.
Classes et héritage en PHP
Définir des classes avec constructeurs, visibilité et héritage en PHP.
Was this helpful?