Questions d'entretien Laravel 2026 : Eloquent, Queues et Patterns d'architecture

Guide complet des questions d'entretien technique Laravel en 2026. Maîtrisez Eloquent ORM, les queues, les patterns d'architecture et les bonnes pratiques pour réussir votre prochain entretien.

Questions d'entretien Laravel 2026

En 2026, Laravel reste le framework PHP le plus populaire pour le développement d'applications web. Avec la sortie de Laravel 12 et l'évolution constante de l'écosystème, les recruteurs attendent des candidats une maîtrise approfondie d'Eloquent ORM, des systèmes de queues et des patterns d'architecture modernes. Cet article présente les questions les plus fréquemment posées lors des entretiens techniques Laravel, accompagnées d'exemples de code concrets et d'explications détaillées.

Ce que recherchent les recruteurs

Les entreprises recherchent des développeurs Laravel capables de concevoir des applications scalables et maintenables. Au-delà de la syntaxe, il est essentiel de démontrer une compréhension des concepts sous-jacents : le cycle de vie des requêtes, l'injection de dépendances, le Service Container et les patterns de conception. Les candidats qui réussissent sont ceux qui peuvent expliquer le "pourquoi" derrière chaque choix technique.

Eloquent ORM : Questions avancées sur les relations

Eloquent est au cœur de Laravel et constitue souvent le premier sujet abordé lors des entretiens. Les recruteurs testent la capacité à optimiser les requêtes et à éviter les problèmes de performance courants.

Comment résoudre le problème N+1 queries ?

Le problème N+1 survient lorsque le code exécute une requête supplémentaire pour chaque élément d'une collection. Cette situation peut rapidement dégrader les performances de l'application.

php
// Problème N+1 : Une requête pour les posts + N requêtes pour les auteurs
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // Requête supplémentaire à chaque itération
}

// Solution : Eager Loading avec with()
$posts = Post::with('author')->get();
foreach ($posts as $post) {
    echo $post->author->name; // Aucune requête supplémentaire
}

// Eager Loading conditionnel pour des besoins spécifiques
$posts = Post::with(['author' => function ($query) {
    $query->select('id', 'name', 'email');
}])->get();

// Eager Loading imbriqué pour les relations multiples
$posts = Post::with(['author.profile', 'comments.user'])->get();

L'utilisation de with() permet de charger les relations en deux requêtes seulement, quelle que soit la taille de la collection. Pour les applications critiques, l'activation de Model::preventLazyLoading() en développement permet de détecter automatiquement ces problèmes.

Expliquez la différence entre hasOne, hasMany, belongsTo et belongsToMany

La compréhension des relations Eloquent est fondamentale pour modéliser correctement une base de données.

php
class User extends Model
{
    // Un utilisateur possède un profil (1:1)
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }

    // Un utilisateur peut avoir plusieurs articles (1:N)
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    // Un utilisateur peut avoir plusieurs rôles (N:N)
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class)
            ->withPivot('assigned_at')
            ->withTimestamps();
    }
}

class Post extends Model
{
    // Un article appartient à un utilisateur
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    // Relation polymorphe : un article peut avoir des commentaires
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Comment créer des Query Scopes réutilisables ?

Les scopes permettent d'encapsuler la logique de requête pour la réutiliser à travers l'application.

php
class Post extends Model
{
    // Scope local : filtrer les articles publiés
    public function scopePublished(Builder $query): Builder
    {
        return $query->whereNotNull('published_at')
            ->where('published_at', '<=', now());
    }

    // Scope avec paramètre
    public function scopeByAuthor(Builder $query, int $authorId): Builder
    {
        return $query->where('author_id', $authorId);
    }

    // Scope dynamique basé sur une condition
    public function scopePopular(Builder $query, int $minViews = 1000): Builder
    {
        return $query->where('views_count', '>=', $minViews)
            ->orderByDesc('views_count');
    }
}

// Utilisation chainée des scopes
$trendingPosts = Post::published()
    ->popular(5000)
    ->with('author')
    ->limit(10)
    ->get();

Système de Queues : Traitement asynchrone et Jobs

Les queues sont essentielles pour maintenir des temps de réponse rapides en déléguant les tâches lourdes à des workers en arrière-plan.

Comment créer et dispatcher un Job ?

app/Jobs/ProcessPodcastUpload.phpphp
class ProcessPodcastUpload implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        public readonly Podcast $podcast,
        public readonly string $filePath
    ) {}

    public function handle(AudioProcessor $processor): void
    {
        // Traitement du fichier audio
        $processor->normalize($this->filePath);
        $processor->generateWaveform($this->filePath);
        
        // Mise à jour du statut
        $this->podcast->update(['status' => 'processed']);
        
        // Notification de l'utilisateur
        $this->podcast->author->notify(new PodcastProcessed($this->podcast));
    }

    // Gestion des échecs
    public function failed(Throwable $exception): void
    {
        $this->podcast->update(['status' => 'failed']);
        Log::error('Podcast processing failed', [
            'podcast_id' => $this->podcast->id,
            'error' => $exception->getMessage()
        ]);
    }

    // Configuration des tentatives
    public int $tries = 3;
    public int $backoff = 60; // Secondes entre les tentatives
}

// Dispatch du job
ProcessPodcastUpload::dispatch($podcast, $filePath)
    ->onQueue('audio-processing')
    ->delay(now()->addMinutes(5));

Comment gérer les Job Batches et les chaînes de jobs ?

Laravel permet de regrouper plusieurs jobs et de suivre leur progression.

php
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

// Création d'un batch de jobs
$batch = Bus::batch([
    new ProcessImage($image1),
    new ProcessImage($image2),
    new ProcessImage($image3),
])->then(function (Batch $batch) {
    // Tous les jobs ont réussi
    Log::info('Batch completed successfully');
})->catch(function (Batch $batch, Throwable $e) {
    // Premier échec détecté
    Log::error('Batch failed', ['error' => $e->getMessage()]);
})->finally(function (Batch $batch) {
    // Exécuté dans tous les cas
    Cache::forget('image-processing-status');
})->onQueue('images')
  ->dispatch();

// Chaîne de jobs séquentiels
Bus::chain([
    new ExtractVideoFrames($video),
    new GenerateThumbnails($video),
    new NotifyUser($video->user),
])->onQueue('video-processing')
  ->dispatch();

Patterns d'architecture : Service Layer et Repository Pattern

Les patterns d'architecture permettent de structurer le code de manière maintenable et testable.

Comment implémenter le Repository Pattern ?

app/Repositories/Contracts/UserRepositoryInterface.phpphp
interface UserRepositoryInterface
{
    public function find(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function create(array $data): User;
    public function update(int $id, array $data): bool;
    public function delete(int $id): bool;
    public function paginate(int $perPage = 15): LengthAwarePaginator;
}

// app/Repositories/EloquentUserRepository.php
class EloquentUserRepository implements UserRepositoryInterface
{
    public function __construct(
        private readonly User $model
    ) {}

    public function find(int $id): ?User
    {
        return $this->model->find($id);
    }

    public function findByEmail(string $email): ?User
    {
        return $this->model->where('email', $email)->first();
    }

    public function create(array $data): User
    {
        return $this->model->create($data);
    }

    public function update(int $id, array $data): bool
    {
        return $this->model->where('id', $id)->update($data) > 0;
    }

    public function delete(int $id): bool
    {
        return $this->model->destroy($id) > 0;
    }

    public function paginate(int $perPage = 15): LengthAwarePaginator
    {
        return $this->model->latest()->paginate($perPage);
    }
}

// Binding dans AppServiceProvider
public function register(): void
{
    $this->app->bind(
        UserRepositoryInterface::class,
        EloquentUserRepository::class
    );
}

Comment structurer un Service Layer efficace ?

app/Services/OrderService.phpphp
class OrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly InventoryService $inventoryService,
        private readonly PaymentGateway $paymentGateway
    ) {}

    public function createOrder(User $user, array $items): Order
    {
        return DB::transaction(function () use ($user, $items) {
            // Vérification du stock
            foreach ($items as $item) {
                if (!$this->inventoryService->checkAvailability($item['product_id'], $item['quantity'])) {
                    throw new InsufficientStockException($item['product_id']);
                }
            }

            // Création de la commande
            $order = $this->orderRepository->create([
                'user_id' => $user->id,
                'status' => OrderStatus::PENDING,
                'total' => $this->calculateTotal($items),
            ]);

            // Ajout des lignes de commande
            foreach ($items as $item) {
                $order->items()->create($item);
                $this->inventoryService->reserve($item['product_id'], $item['quantity']);
            }

            // Événement de création
            event(new OrderCreated($order));

            return $order;
        });
    }

    private function calculateTotal(array $items): float
    {
        return collect($items)->sum(fn ($item) => $item['price'] * $item['quantity']);
    }
}

Middleware et Request Lifecycle

Comprendre le cycle de vie d'une requête est essentiel pour débugger et optimiser les applications Laravel.

Comment créer un Middleware personnalisé ?

app/Http/Middleware/EnsureApiVersion.phpphp
class EnsureApiVersion
{
    public function handle(Request $request, Closure $next, string $version = 'v1'): Response
    {
        $requestedVersion = $request->header('Accept-Version', $version);
        
        if (!in_array($requestedVersion, ['v1', 'v2'])) {
            return response()->json([
                'error' => 'Unsupported API version',
                'supported' => ['v1', 'v2']
            ], 400);
        }

        // Ajout de la version au contexte de la requête
        $request->attributes->set('api_version', $requestedVersion);

        $response = $next($request);

        // Ajout des headers de réponse
        return $response->withHeaders([
            'X-API-Version' => $requestedVersion,
            'X-RateLimit-Remaining' => RateLimiter::remaining($request->ip(), 60)
        ]);
    }
}

// Enregistrement dans bootstrap/app.php (Laravel 11+)
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            'api.version' => EnsureApiVersion::class,
        ]);
    });

Tests : Bonnes pratiques pour les applications Laravel

Les tests sont un sujet fréquent en entretien, car ils démontrent une approche professionnelle du développement.

Comment tester un Controller avec des dépendances ?

php
class OrderControllerTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_create_order(): void
    {
        // Arrange
        $user = User::factory()->create();
        $product = Product::factory()->create(['stock' => 10, 'price' => 99.99]);
        
        // Mock du service de paiement
        $this->mock(PaymentGateway::class, function ($mock) {
            $mock->shouldReceive('charge')
                ->once()
                ->andReturn(new PaymentResult(success: true, transactionId: 'txn_123'));
        });

        // Act
        $response = $this->actingAs($user)
            ->postJson('/api/orders', [
                'items' => [
                    ['product_id' => $product->id, 'quantity' => 2]
                ]
            ]);

        // Assert
        $response->assertStatus(201)
            ->assertJsonStructure([
                'data' => ['id', 'status', 'total', 'items']
            ]);

        $this->assertDatabaseHas('orders', [
            'user_id' => $user->id,
            'status' => 'pending'
        ]);
    }

    public function test_order_fails_with_insufficient_stock(): void
    {
        $user = User::factory()->create();
        $product = Product::factory()->create(['stock' => 1]);

        $response = $this->actingAs($user)
            ->postJson('/api/orders', [
                'items' => [
                    ['product_id' => $product->id, 'quantity' => 5]
                ]
            ]);

        $response->assertStatus(422)
            ->assertJsonValidationErrors(['items.0.quantity']);
    }
}

Prêt à réussir tes entretiens Laravel ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Questions conceptuelles courantes

Au-delà du code, les recruteurs évaluent la compréhension des concepts fondamentaux.

Expliquez le Service Container et l'injection de dépendances

Le Service Container de Laravel est un conteneur d'inversion de contrôle (IoC) qui gère les dépendances des classes. Lorsqu'une classe a besoin d'une dépendance, le container la résout automatiquement.

php
// Résolution automatique par type-hinting
class ReportController extends Controller
{
    public function __construct(
        private readonly ReportGenerator $generator, // Résolu automatiquement
        private readonly CacheManager $cache
    ) {}
}

// Binding personnalisé pour les interfaces
$this->app->bind(ReportGeneratorInterface::class, function ($app) {
    return new PdfReportGenerator(
        $app->make(PdfRenderer::class),
        config('reports.default_format')
    );
});

// Singleton pour les instances partagées
$this->app->singleton(MetricsCollector::class, function ($app) {
    return new MetricsCollector(
        $app->make(RedisConnection::class)
    );
});

Comment optimiser les performances d'une application Laravel ?

L'optimisation des performances couvre plusieurs aspects :

1. Cache des configurations et routesphp
// php artisan config:cache
// php artisan route:cache
// php artisan view:cache

// 2. Utilisation du cache pour les données fréquemment accédées
$users = Cache::remember('active-users', 3600, function () {
    return User::where('active', true)
        ->with('profile')
        ->get();
});

// 3. Pagination pour les grandes collections
$posts = Post::with('author')
    ->published()
    ->cursorPaginate(20); // Plus efficace que paginate() pour les grands datasets

// 4. Index de base de données dans les migrations
Schema::table('posts', function (Blueprint $table) {
    $table->index(['published_at', 'author_id']);
    $table->index('status');
});

// 5. Chunking pour le traitement de masse
User::where('newsletter', true)
    ->chunkById(1000, function ($users) {
        foreach ($users as $user) {
            SendNewsletter::dispatch($user);
        }
    });

Conclusion

La préparation aux entretiens Laravel en 2026 nécessite une maîtrise approfondie d'Eloquent ORM, des systèmes de queues et des patterns d'architecture. Les recruteurs recherchent des développeurs capables d'écrire du code performant, maintenable et testable. La pratique régulière avec des projets réels reste le meilleur moyen de consolider ces connaissances. Il est recommandé de contribuer à des projets open source et d'explorer les nouvelles fonctionnalités de Laravel 12 pour rester à jour avec l'évolution du framework.

Défi du jour

Tu saurais repérer le bug en Laravel ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 16 septembre 2026

Tags

#laravel
#php
#eloquent
#queues
#architecture
#entretien

Partager

Articles similaires