Lire du code Laravel
Séance 5 · ~1 période
Vous avez écrit des classes, des traits et des enums dans un donjon. Voici six extraits d'un projet Laravel 12. Aucune notion nouvelle : ce sont les mots des chapitres 1 à 8, dans du code que vous n'avez pas écrit.
À la fin de ce chapitre, vous serez capables de :
- nommer le concept POO derrière chaque ligne d'un fichier Laravel ;
- repérer l'héritage, les traits, les enums, les interfaces et les relations dans un fichier inconnu ;
- distinguer ce que Laravel fabrique pour vous de ce que vous écrivez ;
- retrouver le chapitre du cours qui explique une ligne que vous ne comprenez pas.
Les quatre premiers extraits sont des fichiers écrits dans un projet. Les deux derniers viennent du code du framework lui-même. Laravel n'écrit pas declare(strict_types=1) en tête de ses fichiers : c'est notre convention à nous, pas la sienne.
1. Un modèle Eloquent
Eloquent est la partie de Laravel qui relie une classe PHP à une table de la base de données. Voici un modèle et l'enum qui l'accompagne.
<?php
namespace App\Enums;
enum PostStatus: string
{
case Draft = 'draft';
case Published = 'published';
}
<?php
namespace App\Models;
use App\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'body', 'status'];
protected function casts(): array
{
return [
'status' => PostStatus::class,
];
}
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
| La ligne | Le mot du cours | Chapitre |
|---|---|---|
namespace App\Models; puis use ...\Model; |
Namespace et import. Le chemin du fichier suit le namespace (PSR-4) | 2 |
enum PostStatus: string |
Un enum dont chaque cas porte une valeur, ici une chaîne | 6 |
class Post extends Model |
extends : héritage. Post reçoit les méthodes de Model sans en écrire une seule |
5 |
use HasFactory; |
use à l'intérieur d'une classe : un trait. Ses méthodes sont ajoutées à Post |
6 |
protected $fillable |
protected : lisible dans la classe et dans ses classes filles, pas depuis l'extérieur |
1, 5 |
protected function casts(): array |
Redéfinition. Model a déjà une méthode casts() ; Post donne la sienne |
5 |
'status' => PostStatus::class |
::class donne le nom complet d'une classe, sous forme de chaîne |
2, 6 |
public function comments(): HasMany |
Type de retour, et une association vers Comment |
3, 4 |
return $this->hasMany(...) |
$this désigne cet article-ci. hasMany() vient de Model |
1, 5 |
Le même modèle en diagramme de classes :
@startuml
class Model
class Post {
+comments(): HasMany
}
class Comment
Model <|-- Post
Model <|-- Comment
Post --> "0..*" Comment : comments
@enduml
$fillable est une liste blanche. Post::create() n'écrit que les colonnes qui y figurent, pour qu'un formulaire trafiqué ne puisse pas modifier n'importe quelle colonne.
Note
protected $fillable n'a pas de type, contrairement à toutes les propriétés écrites depuis le chapitre 1. Cette propriété vient d'une version de Laravel antérieure aux propriétés typées de PHP 7.4. Lui ajouter un type aujourd'hui casserait des milliers d'applications.
À l'usage :
$post = Post::create(['title' => 'Bonjour', 'body' => '…', 'status' => 'draft']);
if ($post->status === PostStatus::Draft) {
foreach ($post->comments as $comment) {
echo $comment->body;
}
}
$post->status ne contient pas la chaîne 'draft' mais l'objet PostStatus::Draft. C'est le travail de casts(). Le même mécanisme, sans Laravel :
<?php
declare(strict_types=1);
enum PostStatus: string
{
case Draft = 'draft';
case Published = 'published';
}
$status = PostStatus::from('draft');
echo $status === PostStatus::Draft ? 'oui' : 'non', "\n";
echo $status->value, "\n";
oui
draft
Attention
Post::create() s'écrit avec ::, mais aucune méthode create() n'est déclarée dans Model. Eloquent redirige l'appel vers son constructeur de requêtes. Retenez la règle d'écriture, pas le mécanisme : :: s'adresse à la classe, -> à un objet.
Une chose à retenir : un modèle Eloquent est une classe fille de Model qui utilise un trait.
2. Un contrôleur
Un contrôleur reçoit une requête HTTP et renvoie une réponse.
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
]);
$post = Post::create($validated);
return redirect()->route('posts.show', $post);
}
}
La classe mère est dans votre projet, et elle est abstraite :
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
| La ligne | Le mot du cours | Chapitre |
|---|---|---|
class PostController extends Controller |
extends encore. La classe mère est abstraite : on ne fait jamais new Controller |
5 |
store(Request $request) |
Paramètre typé par une classe. Vous n'écrivez jamais new Request : Laravel fabrique l'objet et vous le donne |
4 |
: RedirectResponse |
Type de retour : la méthode promet un objet de ce type | 1 |
$request->validate([...]) |
Appel de méthode sur un objet reçu. Si la validation échoue, la méthode lève une exception et Laravel l'attrape | 5 |
$validated |
Un tableau, pas un objet. Tout n'est pas un objet, même en POO | 1 |
redirect()->route(...) |
Une fonction qui renvoie un objet, sur lequel on appelle aussitôt une méthode | 1 |
Une chose à retenir : un paramètre typé par une classe, c'est Laravel qui fabrique l'objet et vous le donne.
3. Une migration
Une migration décrit une table de la base de données en PHP.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->string('status')->default('draft');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
| La ligne | Le mot du cours | Chapitre |
|---|---|---|
return new class extends Migration |
Une classe anonyme : elle hérite de Migration et n'a pas de nom, parce qu'on ne l'appellera nulle part ailleurs |
5 |
Schema::create(...) |
Appel avec :: : on s'adresse à la classe Schema |
1 |
function (Blueprint $table) |
Une fonction anonyme donnée en argument. Laravel l'appelle en lui passant un Blueprint |
8 |
$table->string('status')->default('draft') |
Chaînage : la première méthode renvoie un objet, la seconde s'appelle dessus | 8 |
up(): void et down(): void |
Deux méthodes publiques : appliquer la migration, l'annuler | 1 |
Une chose à retenir : une migration est une classe fille de Migration, et la base de données s'y décrit avec des objets.
4. Une route
Une route relie une adresse à une méthode d'un contrôleur.
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
| La ligne | Le mot du cours | Chapitre |
|---|---|---|
use App\Http\Controllers\PostController; |
Import, en dehors de toute classe. C'est le use du chapitre 2, pas celui d'un trait |
2 |
Route::get(...) |
Appel avec :: : on s'adresse à la classe Route |
1 |
PostController::class |
Le nom complet de la classe, sous forme de chaîne. Aucun objet n'est créé ici | 2, 6 |
[PostController::class, 'show'] |
Un tableau de deux chaînes : la classe et la méthode. Laravel fabriquera l'objet à l'arrivée de la requête | 4 |
->name('posts.show') |
Chaînage : get() renvoie un objet, on lui donne son nom |
8 |
Une chose à retenir : un fichier de routes ne crée aucun objet, il dit quelle méthode de quelle classe répondra à quelle adresse.
5. Une Collection
Post::all() ne renvoie pas un tableau mais un objet Illuminate\Support\Collection. Voici sa déclaration et celle de l'interface principale qu'elle réalise, recopiées du framework.
class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable
interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
| La ligne | Le mot du cours | Chapitre |
|---|---|---|
implements |
Réalisation d'une interface : la classe s'engage à fournir les méthodes du contrat | 6 |
Countable |
C'est pour cela que count($posts) fonctionne |
6, 8 |
IteratorAggregate |
C'est pour cela que foreach ($posts as $post) fonctionne |
6, 8 |
JsonSerializable |
C'est pour cela que json_encode($posts) renvoie ce qu'on attend |
6 |
interface Enumerable extends ... |
Une interface peut en étendre d'autres. Une seule ligne en apporte cinq | 6 |
C'est votre collection du chapitre 8, en plus grand :
$titles = Post::all()
->filter(fn (Post $post) => $post->status === PostStatus::Published)
->map(fn (Post $post) => $post->title);
Une chose à retenir : trois interfaces natives du chapitre 6 expliquent à elles seules pourquoi une Collection se compte, se parcourt et se convertit en JSON.
6. Cinq lignes du framework
Le début de Illuminate\Auth\SessionGuard.
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
use GuardHelpers, Macroable;
public readonly string $name;
protected $lastAttempted;
| La ligne | Le mot du cours | Chapitre |
|---|---|---|
implements StatefulGuard, SupportsBasicAuth |
Deux interfaces réalisées par une même classe | 6 |
use GuardHelpers, Macroable; |
Deux traits utilisés dans une même classe | 6 |
public readonly string $name; |
readonly : la valeur est écrite une fois, dans le constructeur, et plus jamais ensuite |
1 |
protected $lastAttempted; |
Encore une propriété sans type, pour la même raison qu'au premier extrait | 1, 5 |
Une chose à retenir : cinq lignes du framework contiennent quatre mots du cours et aucun mot nouveau.
7. La carte du bloc
| Le mot du cours | Chapitre | Où vous le voyez dans Laravel |
|---|---|---|
Classe, objet, $this |
1 | Chaque Post, Request, Collection manipulé |
protected |
1, 5 | $fillable, $hidden, casts() |
:: sur une classe |
1 | Post::all(), Route::get(), Schema::create() |
readonly |
1 | SessionGuard::$name |
Namespace, use, PSR-4 |
2 | Les premières lignes de tout fichier |
| Association et multiplicités | 3 | hasMany(), belongsTo(), belongsToMany() |
| Objet reçu en paramètre | 4 | store(Request $request), function (Blueprint $table) |
extends, redéfinition |
5 | extends Model, extends Controller, extends Migration |
| Classe abstraite | 5 | App\Http\Controllers\Controller |
| Exceptions | 5 | validate() qui échoue, et la page d'erreur qui suit |
implements, interface |
6 | Collection, SessionGuard |
| Trait | 6 | HasFactory, Notifiable, Macroable |
| Enum | 6 | casts() avec PostStatus::class |
| Fonction anonyme, chaînage | 8 | function (Blueprint $table), ->filter()->map() |
À retenir
::s'adresse à la classe,->à un objet.- Un fichier de Laravel commence presque toujours par des
use, puis unextends. - Un paramètre typé par une classe : Laravel fabrique l'objet et vous le donne.
- Une relation Eloquent est une association UML, avec ses multiplicités.
- Une ligne que vous ne savez pas nommer a un chapitre : la carte ci-dessus dit lequel.
Exercices
- Ouvrez
vendor/laravel/framework/src/Illuminate/Http/Request.php. Notez leextends, chaque trait et chaque interface, avec le chapitre correspondant. Comptez 15 minutes. - En une phrase et avec le vocabulaire du chapitre 6 : pourquoi
foreach ($post->comments as $comment)fonctionne-t-il ? - Reprenez le diagramme de la section 1 et ajoutez-y
PostController, avec une dépendance versPost. N'ajoutez aucune relation qui n'apparaît pas dans les extraits de ce chapitre.