Modern PHP EngineeringLe type
Le type
Chapitre 1
01 — Fondamentaux PHP
01 — Fondamentaux PHP
Cours 01 — Fondamentaux PHP
01.1 Les types en PHP 8
Système de types
PHP 8 propose un système de types riche avec des déclarations strictes et des inférences.
<?php
declare(strict_types=1);
// Types scalaires
$int: int = 42;
$float: float = 3.14;
$string: string = 'Hello';
$bool: bool = true;
// Types composés
$array: array = [1, 2, 3];
$callable: callable = fn() => true;
$iterable: iterable = [1, 2, 3];
// Types spéciaux
$null: null = null;
$void: void; // utilisable uniquement comme type de retour
$never: never; // fonction qui ne termine jamais (boucle infinie, exit)
// Union types (PHP 8.0+)
function foo(int|string $value): int|float { ... }
// Mixed
function bar(mixed $value): mixed { ... }
// Intersection types (PHP 8.1+)
function baz(Countable&ArrayAccess $collection): void { ... }
// DNF types (PHP 8.2+)
function qux((Countable&ArrayAccess)|null $collection): void { ... }
Le type never
<?php
// Une fonction qui ne retourne jamais (elle termine le script)
function redirect(string $url): never {
header("Location: $url");
exit;
}
// Ou une boucle infinie
function loop(): never {
while (true) {
// traitement continu
}
}
Le type void vs never
| Type | Signification |
|---|---|
void | La fonction termine sans retourner de valeur |
never | La fonction ne termine jamais (exit, boucle infinie) |
01.2 Déclarations de type strict
declare(strict_types=1)
Par défaut, PHP tente de convertir automatiquement les types (coercion). Avec strict_types=1, les types doivent correspondre exactement.
<?php
// Sans strict : conversion automatique
function add(int $a, int $b): int {
return $a + $b;
}
echo add('5', '10'); // 15 — PHP convertit automatiquement
// Avec strict
declare(strict_types=1);
function addStrict(int $a, int $b): int {
return $a + $b;
}
echo addStrict('5', '10'); // TypeError: Argument must be int
Type coercion et conversion
<?php
// Conversions implicites (sans strict_types)
$sum = '10' + 5; // 15 (string converti en int)
// Conversions explicites (cast)
$int = (int) '42'; // 42
$float = (float) '3.14'; // 3.14
$string = (string) 42; // '42'
$bool = (bool) 1; // true
$array = (array) $object; // Tableau des propriétés publiques
// Nouvelles fonctions PHP 8+
$int = intval('42');
$float = floatval('3.14');
$str = strval(42);
01.3 Strings
Syntaxes
<?php
// Simple quote — pas d'interpolation
$name = 'World';
echo 'Hello $name'; // Hello $name
// Double quote — interpolation
echo "Hello $name"; // Hello World
echo "Hello {$name}"; // Hello World
// Heredoc (PHP 5.3+)
$html = <<<HTML
<div class="container">
<h1>Hello $name</h1>
<p>Bienvenue sur mon site</p>
</div>
HTML;
// Nowdoc (PHP 5.3+) — pas d'interpolation
$sql = <<<'SQL'
SELECT * FROM users
WHERE name = '$name' -- $name n'est PAS interprété
SQL;
// Heredoc avec indentation (PHP 7.3+)
$json = <<<JSON
{
"name": "$name",
"version": 1.0
}
JSON;
Formatage avec sprintf
<?php
$format = 'Bonjour %s, vous êtes le %dème visiteur.';
echo sprintf($format, 'Alice', 42);
// Bonjour Alice, vous êtes le 42ème visiteur.
// sprintf avec arguments nommés (PHP 8.0+)
echo sprintf(
'Bonjour %1$s, votre commande %2$s est prête. Merci %1$s !',
'Alice',
'#1234'
);
Manipulation de strings
<?php
declare(strict_types=1);
$text = ' Hello Modern PHP World! ';
// PHP 8.0+
str_contains($text, 'PHP'); // true
str_starts_with($text, 'Hello'); // false (espaces)
str_ends_with($text, 'World!'); // false (espaces)
// Nettoyage
trim($text); // 'Hello Modern PHP World!'
ltrim($text); // 'Hello Modern PHP World! '
rtrim($text); // ' Hello Modern PHP World!'
// PHP 8.3+
mb_str_pad('PHP', 10, '-', STR_PAD_BOTH); // '----PHP----'
mb_trim($text); // 'Hello Modern PHP World!'
// Longueur
strlen('café'); // 5 (octets)
mb_strlen('café'); // 4 (caractères)
// Découpage
explode(' ', 'a b c'); // ['a', 'b', 'c']
implode(', ', ['a', 'b']); // 'a, b'
// Regex
preg_match('/^H.*d$/', $text); // 1
preg_replace('/\s+/', '-', trim($text)); // 'Hello-Modern-PHP-World!'
01.4 Tableaux
Création et manipulation
<?php
declare(strict_types=1);
// Tableau indexé
$fruits = ['pomme', 'banane', 'cerise'];
// Tableau associatif
$user = [
'name' => 'Alice',
'age' => 30,
'roles' => ['admin', 'editor'],
];
// Spread operator (PHP 7.4+)
$more = [...$fruits, 'datte', 'figue'];
// ['pomme', 'banane', 'cerise', 'datte', 'figue']
// Array unpacking with string keys (PHP 8.1+)
$user2 = [...$user, 'email' => 'alice@example.com'];
// Destructuring
[$first, $second, $third] = $fruits;
// $first = 'pomme', $second = 'banane', $third = 'cerise'
['name' => $name, 'age' => $age] = $user;
// array_is_list (PHP 8.1+)
array_is_list([0, 1, 2]); // true
array_is_list(['a' => 1]); // false
Fonctions array_*
<?php
$numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5];
array_map(fn(int $n): int => $n * 2, $numbers);
// [6, 2, 8, 2, 10, 18, 4, 12, 10]
array_filter($numbers, fn(int $n): bool => $n > 3);
// [4, 5, 9, 6, 5]
array_reduce($numbers, fn(int $carry, int $n): int => $carry + $n, 0);
// 36
array_unique($numbers);
// [3, 1, 4, 5, 9, 2, 6]
sort($numbers);
// [1, 1, 2, 3, 4, 5, 5, 6, 9]
usort($numbers, fn(int $a, int $b): int => $b <=> $a);
// Tri décroissant
array_chunk($numbers, 3);
// [[1, 1, 2], [3, 4, 5], [5, 6, 9]]
array_column($users, 'name');
// Extraire une colonne d'un tableau multidimensionnel
array_key_first($numbers); // PHP 7.3+ — première clé
array_key_last($numbers); // PHP 7.3+ — dernière clé
01.5 Superglobales
<?php
// $_GET — Paramètres d'URL
// /page.php?name=Alice&page=2
$name = $_GET['name'] ?? 'invité';
$page = (int) ($_GET['page'] ?? 1);
// $_POST — Corps de requête POST
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
// $_SERVER — Informations serveur
$_SERVER['REQUEST_METHOD']; // 'GET', 'POST', etc.
$_SERVER['HTTP_HOST']; // 'example.com'
$_SERVER['REQUEST_URI']; // '/page.php?name=Alice'
$_SERVER['REMOTE_ADDR']; // IP du client
$_SERVER['HTTP_USER_AGENT']; // User-agent
$_SERVER['HTTP_REFERER']; // Page précédente
// $_SESSION — Données de session (voir 01.6)
// $_COOKIE — Cookies HTTP
$theme = $_COOKIE['theme'] ?? 'light';
// $_FILES — Fichiers uploadés
// $_ENV — Variables d'environnement
// $_REQUEST — Fusion de $_GET, $_POST, $_COOKIE
// $_FILES — Fichiers téléchargés
// Validation des entrées
function sanitizeInput(array $data, array $fields): array {
$sanitized = [];
foreach ($fields as $field => $rules) {
$value = $data[$field] ?? null;
if ($value === null && ($rules['required'] ?? false)) {
throw new InvalidArgumentException("$field is required");
}
$sanitized[$field] = match ($rules['type'] ?? 'string') {
'int' => filter_var($value, FILTER_VALIDATE_INT),
'email' => filter_var($value, FILTER_VALIDATE_EMAIL),
'url' => filter_var($value, FILTER_VALIDATE_URL),
default => htmlspecialchars(strip_tags($value), ENT_QUOTES, 'UTF-8'),
};
}
return $sanitized;
}
01.6 Sessions et cookies
Sessions
<?php
// Démarrage de session
session_start([
'cookie_lifetime' => 86400 * 7, // 7 jours
'cookie_secure' => true, // HTTPS uniquement
'cookie_httponly' => true, // Pas accessible en JS
'cookie_samesite' => 'Lax', // Protection CSRF
'gc_maxlifetime' => 86400 * 7, // Nettoyage après 7 jours
]);
// Stockage
$_SESSION['user_id'] = 42;
$_SESSION['roles'] = ['admin', 'editor'];
$_SESSION['last_activity'] = time();
// Vérification
if (($_SESSION['last_activity'] ?? 0) < time() - 3600) {
session_regenerate_id(true); // Rotation session
$_SESSION['last_activity'] = time();
}
// Destruction
session_destroy();
setcookie(session_name(), '', time() - 3600, '/');
// Flash messages
$_SESSION['flash'] = [
'type' => 'success',
'message' => 'Utilisateur créé avec succès',
];
// Session handler personnalisé (Redis, DB, etc.)
// Implémenter SessionHandlerInterface
Cookies
<?php
// Création
setcookie(
'theme',
'dark',
[
'expires' => time() + 86400 * 30,
'path' => '/',
'domain' => 'example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]
);
// Lecture
$theme = $_COOKIE['theme'] ?? 'light';
// Suppression
setcookie('theme', '', ['expires' => time() - 3600]);
// Cookie sécurisé (crypté)
function setSecureCookie(string $name, mixed $value, string $key): void {
$payload = json_encode($value);
$iv = random_bytes(openssl_cipher_iv_length('aes-256-gcm'));
$encrypted = openssl_encrypt($payload, 'aes-256-gcm', $key, 0, $iv, $tag);
$data = base64_encode($iv . $tag . $encrypted);
setcookie($name, $data, ['expires' => time() + 3600, 'httponly' => true, 'secure' => true]);
}
01.7 Gestion des fichiers
<?php
declare(strict_types=1);
// Lecture
$content = file_get_contents('/path/to/file.txt');
$lines = file('/path/to/file.txt', FILE_IGNORE_NEW_LINES);
// Écriture
file_put_contents('/path/to/file.txt', 'Hello World', FILE_APPEND);
// Ouverture avec resource
$handle = fopen('/path/to/file.txt', 'r');
while (($line = fgets($handle)) !== false) {
echo $line;
}
fclose($handle);
// CSV
$data = array_map('str_getcsv', file('data.csv'));
$handle = fopen('output.csv', 'w');
foreach ($data as $row) {
fputcsv($handle, $row);
}
fclose($handle);
// Upload
$uploaded = $_FILES['document'];
$allowed = ['pdf', 'doc', 'docx'];
$ext = strtolower(pathinfo($uploaded['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
throw new InvalidArgumentException('Extension non autorisée');
}
$dest = '/uploads/' . bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($uploaded['tmp_name'], $dest);
// DirectoryIterator
$dir = new DirectoryIterator('/path');
foreach ($dir as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
echo $file->getPathname() . "\n";
}
}
// SplFileObject (POO)
$file = new SplFileObject('/path/to/file.txt');
$file->setFlags(SplFileObject::DROP_NEW_LINE);
foreach ($file as $lineNum => $line) {
echo "Ligne $lineNum: $line\n";
}
01.8 Gestion des erreurs
Try/Catch moderne
<?php
declare(strict_types=1);
try {
$result = riskyOperation();
} catch (InvalidArgumentException $e) {
// Erreur métier
log_error($e->getMessage());
throw $e; // Re-throw si non géré ici
} catch (RuntimeException | LogicException $e) {
// Multi-catch
notifyAdmin($e);
} catch (Throwable $e) {
// Attrape TOUT (Error + Exception)
http_response_code(500);
echo json_encode(['error' => 'Internal Server Error']);
} finally {
// Toujours exécuté (même avec return/throw)
cleanup();
}
Classes d'erreur PHP 8
Throwable
├── Error
│ ├── TypeError (PHP 7+)
│ ├── ValueError (PHP 8.0+)
│ ├── ArithmeticError
│ ├── DivisionByZeroError
│ ├── ParseError
│ └── AssertionError
└── Exception
├── LogicException
│ ├── InvalidArgumentException
│ ├── LengthException
│ └── OutOfRangeException
└── RuntimeException
├── OutOfBoundsException
├── OverflowException
├── UnderflowException
├── UnexpectedValueException
└── DomainException
Erreurs PHP 8.0+
<?php
// ValueError (PHP 8.0+)
try {
$pos = strpos('hello', ''); // ValueError: Empty needle
} catch (ValueError $e) {
echo "Erreur de valeur : {$e->getMessage()}";
}
// TypeError
try {
function add(int $a): int { return $a; }
add('not a number');
} catch (TypeError $e) {
echo "Erreur de type : {$e->getMessage()}";
}
// Backtrace moderne
try {
fail();
} catch (Throwable $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
echo $e->getFile() . ':' . $e->getLine() . "\n";
echo $e->getTraceAsString() . "\n";
}
Error handler personnalisé
<?php
set_error_handler(function (
int $severity,
string $message,
string $file,
int $line
): bool {
if (!(error_reporting() & $severity)) {
return false;
}
throw new ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function (Throwable $e): void {
http_response_code(500);
echo json_encode([
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTrace(),
]);
});
Exercices
- Créez une fonction qui valide et nettoie un email avec
filter_var - Écrivez un script qui lit un fichier CSV et retourne un tableau associatif
- Implémentez un système de flash messages en session
- Utilisez le spread operator pour fusionner deux tableaux
- Créez une hiérarchie d'exceptions personnalisées
Références
- php.net/manual/fr/language.types.php
- php.net/manual/fr/language.types.type-system.php
- php.net/manual/fr/language.errors.php
- php.net/manual/fr/wrappers.php