PHP natif (8.4)
🎯 Objectif : maîtriser PHP 8.4 sans framework. Tu comprendras alors ce que Laravel/Symfony font à ta place — et tu sauras assembler un microservice quand un framework complet serait sur-dimensionné.
À l'issue de cet axe, tu sauras :
- Écrire du PHP 8.4 idiomatique avec types stricts
- Manier classes, traits, interfaces, enums, attributs
- Accéder à une base avec PDO en évitant les injections
- Configurer Composer + autoload PSR-4
- Écrire un mini-routeur HTTP qui ressemble à Express/Hono
Setup minimal d’un projet
Section intitulée « Setup minimal d’un projet »mkdir taskly-php && cd taskly-phpcomposer init --name="me/taskly-php" --type=project --require="php:^8.3" -ncomposer.json :
{ "name": "me/taskly-php", "type": "project", "require": { "php": "^8.3" }, "autoload": { "psr-4": { "App\\": "src/" } }}composer dump-autoloadTu peux maintenant créer src/Anything.php namespace App\Anything, et require __DIR__ . '/vendor/autoload.php' te donne accès à toutes les classes automatiquement.
Types stricts — toujours
Section intitulée « Types stricts — toujours »<?phpdeclare(strict_types=1);
namespace App\Domain;
function add(int $a, int $b): int{ return $a + $b;}
add(1, 2); // 3add('1', 2); // ❌ TypeErrordeclare(strict_types=1) doit être en première ligne de chaque fichier. Sans ça, PHP fait du type juggling laxiste ('1' est converti en 1). Avec, tu obtiens un comportement Java-like.
Le système de types PHP 8.4
Section intitulée « Le système de types PHP 8.4 »function process( int $count, string $name, bool $active = true, ?Task $task = null, // nullable array $items = [], int|string $id, // union type Foo&Bar $combined, // intersection string|null $maybeStr, // équivalent ?string): void { // ...}Property hooks (PHP 8.4)
Section intitulée « Property hooks (PHP 8.4) »class Person{ public string $fullName { get => trim("{$this->firstName} {$this->lastName}"); }
public function __construct( public string $firstName, public string $lastName, ) {}}Ressemble aux getters TypeScript. Plus besoin de méthodes getFullName().
Classes et objets
Section intitulée « Classes et objets »final class Task{ public function __construct( public readonly int $id, public string $title, public bool $done = false, ) {}
public function complete(): self { return new self($this->id, $this->title, true); }}
$t = new Task(1, 'Apprendre PHP');$t = $t->complete();echo $t->done ? '✓' : '✗'; // ✓readonly: la propriété ne peut être assignée qu’une fois (dans le constructeur).- Constructor property promotion :
public int $iddirectement dans la signature crée la propriété. final: empêche l’héritage.
Enums (PHP 8.1+)
Section intitulée « Enums (PHP 8.1+) »enum Priority: string{ case Low = 'low'; case Medium = 'medium'; case High = 'high';
public function label(): string { return match($this) { Priority::Low => 'Faible', Priority::Medium => 'Moyenne', Priority::High => 'Élevée', }; }}
$p = Priority::High;echo $p->value; // 'high'echo $p->label(); // 'Élevée'Idem TypeScript ou Rust. Termine les const STATUS_PENDING = 'pending' à la pelle.
Match — l’expression
Section intitulée « Match — l’expression »$grade = match(true) { $score >= 90 => 'A', $score >= 80 => 'B', $score >= 70 => 'C', default => 'F',};Plus sûr que switch (comparaison stricte ===, pas de fall-through implicite).
Attributs (PHP 8.0+)
Section intitulée « Attributs (PHP 8.0+) »Équivalents des decorators TypeScript ou des annotations Java/Python.
#[Attribute]class Route{ public function __construct(public string $path, public string $method = 'GET') {}}
class TaskController{ #[Route('/tasks', 'GET')] public function list(): array { /* ... */ }
#[Route('/tasks/{id}', 'GET')] public function show(int $id): Task { /* ... */ }}Symfony et Laravel les utilisent massivement (validation, routing, sécurité…).
PDO — la DB sans ORM
Section intitulée « PDO — la DB sans ORM »PDO (PHP Data Objects) est l’API standard pour parler à n’importe quelle base.
$dsn = 'sqlite:./data.db';$pdo = new PDO($dsn);$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
// Création$pdo->exec("CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0)");
// SELECT avec paramètres (sécurisé contre SQL injection)$stmt = $pdo->prepare('SELECT * FROM tasks WHERE id = :id');$stmt->execute(['id' => 42]);$task = $stmt->fetch();
// INSERT$stmt = $pdo->prepare('INSERT INTO tasks (title) VALUES (:title)');$stmt->execute(['title' => 'Apprendre PHP']);$id = (int) $pdo->lastInsertId();Mini-routeur fait main
Section intitulée « Mini-routeur fait main »Pour comprendre ce qu’un framework apporte, voici un routeur HTTP minimal en ~50 lignes :
<?phpdeclare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
class Router{ /** @var array<int, array{method: string, pattern: string, handler: callable, params: string[]}> */ private array $routes = [];
public function add(string $method, string $path, callable $handler): void { $params = []; $regex = preg_replace_callback( '/\{([^}]+)\}/', function (array $m) use (&$params): string { $params[] = $m[1]; return '([^/]+)'; }, $path, ); $this->routes[] = [ 'method' => $method, 'pattern' => '#^' . $regex . '$#', 'handler' => $handler, 'params' => $params, ]; }
public function dispatch(string $method, string $uri): void { $path = parse_url($uri, PHP_URL_PATH); foreach ($this->routes as $route) { if ($route['method'] !== $method) continue; if (preg_match($route['pattern'], $path, $matches)) { array_shift($matches); $params = array_combine($route['params'], $matches); $result = ($route['handler'])(...$params); header('Content-Type: application/json'); echo json_encode($result); return; } } http_response_code(404); echo json_encode(['error' => 'Not Found']); }}
$router = new Router();
$router->add('GET', '/health', fn() => ['status' => 'ok']);$router->add('GET', '/tasks/{id}', fn(string $id) => ['id' => (int)$id, 'title' => 'Demo']);
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);php -S localhost:3000 -t public# curl http://localhost:3000/tasks/42Tu obtiens un mini-Hono PHP en 50 lignes. Quand tu vois Laravel ou Symfony, tu sauras que c’est ce genre de logique étendue à 1000× plus de fonctionnalités (middlewares, DI, validation, etc.).
Tests avec PHPUnit
Section intitulée « Tests avec PHPUnit »composer require --dev phpunit/phpunitvendor/bin/phpunit --inituse PHPUnit\Framework\TestCase;use App\Domain\Task;
class TaskTest extends TestCase{ public function test_complete_marks_task_done(): void { $task = new Task(1, 'Test', false); $completed = $task->complete(); $this->assertTrue($completed->done); $this->assertFalse($task->done); // immutable }}vendor/bin/phpunitStatic analysis avec PHPStan
Section intitulée « Static analysis avec PHPStan »composer require --dev phpstan/phpstanvendor/bin/phpstan analyse src --level=8PHPStan scanne ton code statiquement et détecte des bugs : types incohérents, propriétés manquantes, méthodes inexistantes. Niveau 1 (laxiste) à 9 (strict).
# phpstan.neonparameters: level: 8 paths: - srcC’est l’équivalent du tsc --noEmit côté TypeScript. Indispensable sur les vrais projets.
Auto-évaluation
Section intitulée « Auto-évaluation »Pour aller plus loin
Section intitulée « Pour aller plus loin »- PHP: The Right Way — phptherightway.com
- PHP Documentation — php.net/docs.php
- PHPStan — phpstan.org
- Composer Docs — getcomposer.org/doc
Suite : Laravel 12 ou Symfony 7.4 LTS.