MFormations
Modern PHP Engineering

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

TypeSignification
voidLa fonction termine sans retourner de valeur
neverLa 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

  1. Créez une fonction qui valide et nettoie un email avec filter_var
  2. Écrivez un script qui lit un fichier CSV et retourne un tableau associatif
  3. Implémentez un système de flash messages en session
  4. Utilisez le spread operator pour fusionner deux tableaux
  5. 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