Laravel en PHP sollicitatievragen: de Top 25 in 2026
De 25 meest gestelde Laravel- en PHP-sollicitatievragen. Eloquent ORM, middleware, Artisan, queues, tests en architectuur met uitgebreide antwoorden en codevoorbeelden.

Technische gesprekken over Laravel toetsen de beheersing van het populairste PHP-framework, het begrip van het Eloquent ORM, de MVC-architectuur en het vermogen om robuuste en onderhoudbare applicaties te bouwen. Deze gids behandelt de 25 vaakst gestelde vragen, van Laravel-basisprincipes tot geavanceerde deployment-patronen in productie.
Interviewers waarderen kandidaten die de architecturale keuzes van Laravel kunnen toelichten. Begrijpen waarom het framework bepaalde conventies hanteert (Convention over Configuration, Service Container) maakt een wezenlijk verschil tijdens sollicitatiegesprekken.
Laravel-basisprincipes
Vraag 1: De Request Lifecycle in Laravel
De Laravel request lifecycle doorloopt meerdere lagen voordat de controller wordt bereikt. Inzicht in deze cyclus is onmisbaar voor debugging en performance-optimalisatie.
// Entry point for all HTTP requests
require __DIR__.'/../vendor/autoload.php';
// Load the Laravel application
$app = require_once __DIR__.'/../bootstrap/app.php';
// The HTTP kernel handles the request
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);De volledige cyclus: index.php → autoload → bootstrap → service providers → middlewares → routing → controller → response → terminate. Elke stap kan worden onderschept en aangepast.
Vraag 2: Wat is de Service Container en hoe werkt Dependency Injection?
De Service Container vormt de kern van Laravel. Deze beheert de instantiatie van klassen en lost afhankelijkheden automatisch op via constructor injection.
// Service with automatically injected dependencies
namespace App\Services;
use App\Contracts\PaymentGatewayInterface;
use App\Repositories\OrderRepository;
use Illuminate\Support\Facades\Log;
class PaymentService
{
public function __construct(
private PaymentGatewayInterface $gateway, // Interface resolved by container
private OrderRepository $orders // Concrete class auto-resolved
) {}
public function processPayment(int $orderId, float $amount): bool
{
$order = $this->orders->find($orderId);
try {
$result = $this->gateway->charge($amount, $order->customer);
$order->markAsPaid($result->transactionId);
return true;
} catch (PaymentException $e) {
Log::error('Payment failed', ['order' => $orderId, 'error' => $e->getMessage()]);
return false;
}
}
}// Binding an interface to a concrete implementation
public function register(): void
{
// Simple binding: new instance on each injection
$this->app->bind(
PaymentGatewayInterface::class,
StripeGateway::class
);
// Singleton: same instance shared everywhere
$this->app->singleton(
CacheService::class,
fn($app) => new CacheService($app['config']['cache.driver'])
);
}Dependency injection ontkoppelt klassen en vergemakkelijkt unit testing door middel van mocking.
Vraag 3: Wat is het verschil tussen Facades en Dependency Injection?
Facades bieden een statische syntax om toegang te krijgen tot container-services, terwijl dependency injection afhankelijkheden expliciet maakt.
// Using Facades: concise syntax but implicit dependencies
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class ReportController extends Controller
{
public function generate()
{
// Facades: static access to services
$data = Cache::remember('report_data', 3600, fn() => $this->fetchData());
Log::info('Report generated');
return view('report', compact('data'));
}
}
// Dependency Injection: explicit and testable dependencies
use Illuminate\Contracts\Cache\Repository as CacheContract;
use Psr\Log\LoggerInterface;
class ReportController extends Controller
{
public function __construct(
private CacheContract $cache,
private LoggerInterface $logger
) {}
public function generate()
{
// Same functionality, explicit dependencies
$data = $this->cache->remember('report_data', 3600, fn() => $this->fetchData());
$this->logger->info('Report generated');
return view('report', compact('data'));
}
}In business-klassen verdient dependency injection de voorkeur vanwege de betere testbaarheid. Facades zijn geschikt voor helpers en ad-hoccode.
Vraag 4: Hoe werken Service Providers in Laravel?
Service Providers zijn het centrale configuratiepunt van de applicatie. Elke provider registreert services, configureert bindings en initialiseert componenten.
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\PaymentService;
use App\Contracts\PaymentGatewayInterface;
use App\Gateways\StripeGateway;
class PaymentServiceProvider extends ServiceProvider
{
// Register method: bindings and registrations
// Don't access other services here (not yet loaded)
public function register(): void
{
$this->app->singleton(PaymentGatewayInterface::class, function ($app) {
return new StripeGateway(
config('services.stripe.key'),
config('services.stripe.secret')
);
});
}
// Boot method: initialization after all providers
// Full access to all application services
public function boot(): void
{
// Register macros, event listeners, routes, etc.
$this->loadRoutesFrom(__DIR__.'/../routes/payment.php');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'payment');
// Publish files for packages
$this->publishes([
__DIR__.'/../config/payment.php' => config_path('payment.php'),
], 'payment-config');
}
}Uitvoeringsvolgorde: eerst alle register()-methodes, daarna alle boot()-methodes. Deze volgorde garandeert dat afhankelijkheden beschikbaar zijn tijdens de boot-fase.
Eloquent ORM
Vraag 5: Eloquent-relaties en hun verschillen
Eloquent biedt verschillende relatietypen om tabelkoppelingen te modelleren. Elk type heeft specifieke toepassingen.
class User extends Model
{
// A user has one profile (1:1)
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}
// A user has many articles (1:N)
public function articles(): HasMany
{
return $this->hasMany(Article::class);
}
// A user has many roles via pivot table (N:N)
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class)
->withPivot('assigned_at') // Additional pivot columns
->withTimestamps(); // created_at/updated_at on pivot
}
// A user has many comments through articles (HasManyThrough)
public function comments(): HasManyThrough
{
return $this->hasManyThrough(
Comment::class, // Final model
Article::class // Intermediate model
);
}
}
// app/Models/Article.php
class Article extends Model
{
// An article belongs to a user (inverse of hasMany)
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
// Polymorphic relationship: an article can have tags, like other models
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}Polymorfe relaties (morphOne, morphMany, morphToMany) stellen een model in staat om via een enkele relatie gekoppeld te worden aan meerdere modeltypen.
Vraag 6: Het N+1-probleem en Eager Loading als oplossing
Het N+1-probleem ontstaat wanneer een hoofdquery N aanvullende queries genereert om relaties te laden. Dit is de meest voorkomende oorzaak van traagheid in Laravel-applicaties.
// ❌ PROBLEM: N+1 queries
// 1 query for articles + 1 query PER article for the author
$articles = Article::all();
foreach ($articles as $article) {
echo $article->author->name; // SQL query on each iteration!
}
// ✅ SOLUTION 1: with() - Eager Loading
// Only 2 queries (articles + users with IN clause)
$articles = Article::with('author')->get();
foreach ($articles as $article) {
echo $article->author->name; // Already loaded, no query
}
// ✅ SOLUTION 2: Nested Eager Loading
// Loads articles, their authors, and author roles
$articles = Article::with(['author.roles', 'comments.user'])->get();
// ✅ SOLUTION 3: Eager Loading with constraints
$articles = Article::with([
'comments' => function ($query) {
$query->where('approved', true)
->orderBy('created_at', 'desc')
->limit(5);
}
])->get();
// ✅ SOLUTION 4: Default Eager Loading in the model
class Article extends Model
{
// These relationships are always loaded automatically
protected $with = ['author', 'category'];
}Met php artisan telescope:prune en Laravel Telescope kunnen N+1-problemen tijdens de ontwikkeling worden opgespoord.
Vraag 7: Query Scopes aanmaken en toepassen
Query Scopes kapselen herbruikbare querycondities in op modelniveau, waardoor de code leesbaarder en DRY wordt.
class Article extends Model
{
// Global Scope: automatically applied to ALL queries
protected static function booted(): void
{
// Excludes soft-deleted articles by default
static::addGlobalScope('published', function (Builder $builder) {
$builder->where('status', 'published');
});
}
// Local Scope: called explicitly via scopeScopeName
public function scopePopular(Builder $query, int $minViews = 1000): Builder
{
return $query->where('view_count', '>=', $minViews);
}
public function scopeByAuthor(Builder $query, User $author): Builder
{
return $query->where('user_id', $author->id);
}
public function scopeRecent(Builder $query, int $days = 7): Builder
{
return $query->where('created_at', '>=', now()->subDays($days));
}
public function scopeWithStats(Builder $query): Builder
{
return $query->withCount('comments')
->withSum('reactions', 'score');
}
}
// Usage: fluent scope chaining
$articles = Article::popular(500)
->recent(30)
->byAuthor($user)
->withStats()
->orderByDesc('comment_count')
->paginate(20);
// Ignore a Global Scope
$allArticles = Article::withoutGlobalScope('published')->get();Lokale scopes verbeteren de leesbaarheid en centraliseren de querylogica. Globale scopes zijn geschikt voor multi-tenancy of soft delete.
Klaar om je Laravel gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Middleware en Routing
Vraag 8: Hoe werken Middlewares in Laravel?
Middlewares filteren inkomende HTTP-requests en kunnen uitgaande responses aanpassen. Elke request doorloopt een middleware-stack.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckSubscription
{
public function handle(Request $request, Closure $next, string $plan = 'basic'): Response
{
$user = $request->user();
// Check before the controller
if (!$user || !$user->hasActiveSubscription($plan)) {
if ($request->expectsJson()) {
return response()->json(['error' => 'Subscription required'], 403);
}
return redirect()->route('subscription.plans');
}
// Pass to next middleware or controller
$response = $next($request);
// Modify response after the controller
$response->headers->set('X-Subscription-Plan', $user->subscription->plan);
return $response;
}
}
// bootstrap/app.php (Laravel 11+)
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
// Global middleware (all requests)
$middleware->append(LogRequestMiddleware::class);
// Aliases for use in routes
$middleware->alias([
'subscription' => CheckSubscription::class,
'role' => EnsureUserHasRole::class,
]);
// Middleware groups
$middleware->group('api', [
ThrottleRequests::class.':api',
SubstituteBindings::class,
]);
});// Applying middlewares to routes
Route::middleware(['auth', 'subscription:premium'])->group(function () {
Route::get('/dashboard', DashboardController::class);
Route::resource('projects', ProjectController::class);
});De volgorde van middlewares is bepalend: bij binnenkomst worden ze van boven naar beneden uitgevoerd en bij het verlaten van beneden naar boven.
Vraag 9: Route Model Binding en de varianten
Route Model Binding injecteert automatisch Eloquent-modellen in controllers op basis van URL-parameters.
// Implicit binding: Laravel automatically resolves by ID
Route::get('/articles/{article}', [ArticleController::class, 'show']);
// Binding by slug instead of ID
Route::get('/articles/{article:slug}', [ArticleController::class, 'show']);
// Binding with relationship (automatic scope)
Route::get('/users/{user}/articles/{article}', function (User $user, Article $article) {
// Laravel automatically verifies that the article belongs to the user
return $article;
})->scopeBindings();class Article extends Model
{
// Customize the default resolution key
public function getRouteKeyName(): string
{
return 'slug'; // Resolves by slug instead of id
}
// Customize the resolution query
public function resolveRouteBinding($value, $field = null): ?Model
{
return $this->where($field ?? 'slug', $value)
->where('status', 'published')
->firstOrFail();
}
}// Explicit custom binding
public function boot(): void
{
Route::bind('article', function (string $value) {
return Article::where('slug', $value)
->published()
->with('author')
->firstOrFail();
});
}Route Model Binding vermindert boilerplate-code en centraliseert de resolutielogica.
Queues en Jobs
Vraag 10: Jobs en Queues implementeren in Laravel
Queues maken het mogelijk om zware taken naar de achtergrond te verplaatsen, wat de responsiviteit van de applicatie verbetert.
namespace App\Jobs;
use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Number of attempts before final failure
public int $tries = 3;
// Timeout in seconds
public int $timeout = 600;
// Delay between attempts (exponential backoff)
public array $backoff = [30, 60, 120];
public function __construct(
public Podcast $podcast
) {}
public function handle(AudioProcessor $processor): void
{
// The job executes in the background
$processor->transcode($this->podcast->audio_path);
$processor->generateWaveform($this->podcast);
$this->podcast->update(['status' => 'processed']);
}
// Failure handling
public function failed(\Throwable $exception): void
{
$this->podcast->update(['status' => 'failed']);
// Admin notification, logging, etc.
}
// Conditions for retrying the job
public function retryUntil(): \DateTime
{
return now()->addHours(24);
}
}// Dispatching the job
ProcessPodcast::dispatch($podcast); // Default queue
ProcessPodcast::dispatch($podcast)->onQueue('audio'); // Specific queue
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10)); // Delay
// Job chaining (sequential execution)
Bus::chain([
new ProcessPodcast($podcast),
new GenerateThumbnail($podcast),
new NotifySubscribers($podcast),
])->dispatch();
// Job batch (parallel execution with tracking)
Bus::batch([
new ProcessPodcast($podcast1),
new ProcessPodcast($podcast2),
new ProcessPodcast($podcast3),
])->then(function (Batch $batch) {
// All jobs succeeded
})->catch(function (Batch $batch, \Throwable $e) {
// First failure
})->finally(function (Batch $batch) {
// All jobs finished (success or failure)
})->dispatch();De worker wordt gestart met php artisan queue:work --queue=high,default om meerdere queues op prioriteit te verwerken.
Vraag 11: Het verschil tussen Jobs, Events en Listeners
Alle drie de concepten ontkoppelen code, maar met verschillende intenties.
// Jobs: single task to execute
// Used for heavy or deferred operations
class SendWelcomeEmail implements ShouldQueue
{
public function handle(Mailer $mailer): void
{
$mailer->send(new WelcomeEmail($this->user));
}
}
// Events: notification that something happened
// The event contains only data, not logic
class UserRegistered
{
public function __construct(
public User $user,
public string $source
) {}
}
// Listeners: react to events
// An event can have multiple listeners
class SendWelcomeNotification implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
$event->user->notify(new WelcomeNotification());
}
}
class TrackRegistration
{
public function handle(UserRegistered $event): void
{
Analytics::track('user_registered', [
'user_id' => $event->user->id,
'source' => $event->source,
]);
}
}protected $listen = [
UserRegistered::class => [
SendWelcomeNotification::class, // Queued
TrackRegistration::class, // Sync
CreateDefaultSettings::class, // Sync
],
];
// Triggering the event
event(new UserRegistered($user, 'web'));
// Or
UserRegistered::dispatch($user, 'web');Events bevorderen een ontkoppelde architectuur: de code die het event afvuurt, kent de gevolgen niet.
Beveiliging en Authenticatie
Vraag 12: Hoe beschermt Laravel tegen CSRF-aanvallen?
Laravel genereert automatisch een uniek CSRF-token per sessie en verifieert dit token bij elke POST-, PUT-, PATCH- en DELETE-request.
// In Blade forms
<form method="POST" action="/profile">
@csrf {{-- Generates a hidden field with the token --}}
@method('PUT') {{-- HTTP method spoofing --}}
<input type="text" name="name" value="{{ $user->name }}">
<button type="submit">Update</button>
</form>
// For AJAX requests, the token is in the meta tag
<meta name="csrf-token" content="{{ csrf_token() }}">
// Axios configuration to automatically send the token
axios.defaults.headers.common['X-CSRF-TOKEN'] =
document.querySelector('meta[name="csrf-token"]').content;// Exclude routes from CSRF verification (external webhooks)
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'stripe/webhook', // Stripe webhook authenticated by signature
'api/*', // API authenticated by token
]);
});CSRF-bescherming mag nooit globaal worden uitgeschakeld. Uitzonderingen zijn alleen zinvol voor endpoints die op een andere manier worden geauthenticeerd.
Vraag 13: Authenticatie implementeren met Laravel Sanctum
Laravel Sanctum biedt lichtgewicht authenticatie voor SPA's, mobiele apps en token-gebaseerde API's.
// Installation and configuration
// php artisan install:api (Laravel 11+)
// app/Models/User.php
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}class AuthController extends Controller
{
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!Auth::attempt($credentials)) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
$user = Auth::user();
// Create a token with abilities (permissions)
$token = $user->createToken('api-token', [
'articles:read',
'articles:write',
'profile:update',
]);
return response()->json([
'user' => $user,
'token' => $token->plainTextToken,
]);
}
public function logout(Request $request): JsonResponse
{
// Revoke the current token
$request->user()->currentAccessToken()->delete();
// Or revoke all tokens
// $request->user()->tokens()->delete();
return response()->json(['message' => 'Logged out']);
}
}
// routes/api.php
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn(Request $r) => $r->user());
Route::post('/logout', [AuthController::class, 'logout']);
// Ability verification
Route::middleware('ability:articles:write')->group(function () {
Route::post('/articles', [ArticleController::class, 'store']);
});
});Sanctum ondersteunt ook cookie-authenticatie voor SPA's op hetzelfde domein, met automatische CSRF-bescherming.
Vraag 14: Een API beveiligen met Policies en Gates
Policies en Gates centraliseren de autorisatielogica en scheiden bedrijfsregels van controllers.
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
class ArticlePolicy
{
// Pre-check: admins have all rights
public function before(User $user, string $ability): ?bool
{
if ($user->isAdmin()) {
return true; // Allow everything
}
return null; // Continue to specific method
}
public function view(?User $user, Article $article): bool
{
// Published articles visible to all
if ($article->status === 'published') {
return true;
}
// Drafts visible only to author
return $user?->id === $article->user_id;
}
public function update(User $user, Article $article): bool
{
return $user->id === $article->user_id;
}
public function delete(User $user, Article $article): bool
{
return $user->id === $article->user_id
&& $article->comments()->count() === 0;
}
}class ArticleController extends Controller
{
public function update(Request $request, Article $article)
{
// Check policy, throws 403 if unauthorized
$this->authorize('update', $article);
$article->update($request->validated());
return redirect()->route('articles.show', $article);
}
}
// In Blade
@can('update', $article)
<a href="{{ route('articles.edit', $article) }}">Edit</a>
@endcan
// Gates for non-model authorizations
Gate::define('access-admin', function (User $user) {
return $user->role === 'admin';
});
// Usage
if (Gate::allows('access-admin')) {
// ...
}Policies zijn gekoppeld aan een model, Gates dienen voor algemene autorisaties.
Validatie en Formulieren
Vraag 15: Custom validatieregels aanmaken
Laravel biedt verschillende manieren om aangepaste validaties te creeren, afhankelijk van complexiteit en herbruikbaarheid.
// Custom rule as a class (reusable)
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class StrongPassword implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$errors = [];
if (strlen($value) < 12) {
$errors[] = 'at least 12 characters';
}
if (!preg_match('/[A-Z]/', $value)) {
$errors[] = 'at least one uppercase letter';
}
if (!preg_match('/[a-z]/', $value)) {
$errors[] = 'at least one lowercase letter';
}
if (!preg_match('/[0-9]/', $value)) {
$errors[] = 'at least one digit';
}
if (!preg_match('/[@$!%*?&#]/', $value)) {
$errors[] = 'at least one special character';
}
if (!empty($errors)) {
$fail("The password must contain: " . implode(', ', $errors) . '.');
}
}
}// Form Request with complex validation
namespace App\Http\Requests;
use App\Rules\StrongPassword;
use Illuminate\Foundation\Http\FormRequest;
class RegisterRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Or authorization logic
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'confirmed', new StrongPassword()],
'company' => ['required_if:account_type,business', 'string'],
'vat_number' => [
'nullable',
'string',
// Closure for inline validation
function ($attribute, $value, $fail) {
if ($value && !$this->isValidVatNumber($value)) {
$fail('The VAT number is invalid.');
}
},
],
];
}
public function messages(): array
{
return [
'email.unique' => 'This email address is already in use.',
'password.confirmed' => 'The passwords do not match.',
];
}
protected function prepareForValidation(): void
{
// Normalization before validation
$this->merge([
'email' => strtolower(trim($this->email)),
]);
}
}Form Requests centraliseren validatie, autorisatie en foutmeldingen, waardoor controllers lichter worden.
Klaar om je Laravel gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Testing
Vraag 16: Teststructuur in Laravel
Laravel biedt PHPUnit met toegewijde helpers om de verschillende lagen van de applicatie te testen.
namespace Tests\Feature;
use App\Models\Article;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ArticleControllerTest extends TestCase
{
use RefreshDatabase; // Resets DB between each test
public function test_guest_can_view_published_articles(): void
{
$article = Article::factory()->published()->create();
$response = $this->get('/articles/' . $article->slug);
$response->assertStatus(200);
$response->assertSee($article->title);
}
public function test_authenticated_user_can_create_article(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/articles', [
'title' => 'My new article',
'content' => 'Test content',
]);
$response->assertRedirect();
$this->assertDatabaseHas('articles', [
'title' => 'My new article',
'user_id' => $user->id,
]);
}
public function test_user_cannot_update_others_article(): void
{
$owner = User::factory()->create();
$other = User::factory()->create();
$article = Article::factory()->for($owner)->create();
$response = $this->actingAs($other)
->put('/articles/' . $article->id, [
'title' => 'Modified title',
]);
$response->assertStatus(403);
}
}namespace Tests\Unit\Services;
use App\Services\PaymentService;
use App\Contracts\PaymentGatewayInterface;
use App\Repositories\OrderRepository;
use Mockery;
use Tests\TestCase;
class PaymentServiceTest extends TestCase
{
public function test_process_payment_charges_correct_amount(): void
{
// Mock dependencies
$gateway = Mockery::mock(PaymentGatewayInterface::class);
$gateway->shouldReceive('charge')
->once()
->with(99.99, Mockery::any())
->andReturn((object) ['transactionId' => 'tx_123']);
$orders = Mockery::mock(OrderRepository::class);
$orders->shouldReceive('find')
->with(1)
->andReturn($this->createOrder());
$service = new PaymentService($gateway, $orders);
$result = $service->processPayment(1, 99.99);
$this->assertTrue($result);
}
}Feature-tests (HTTP, integratie) en unit-tests (geisoleerde klassen met mocks) dienen duidelijk gescheiden te worden.
Vraag 17: Factories en Seeders effectief inzetten
Factories genereren realistische testgegevens, Seeders vullen de database.
namespace Database\Factories;
use App\Models\Article;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class ArticleFactory extends Factory
{
protected $model = Article::class;
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->sentence(6),
'slug' => fake()->unique()->slug(),
'content' => fake()->paragraphs(5, true),
'status' => 'draft',
'view_count' => fake()->numberBetween(0, 10000),
'created_at' => fake()->dateTimeBetween('-1 year'),
];
}
// States for different configurations
public function published(): static
{
return $this->state(fn(array $attr) => [
'status' => 'published',
'published_at' => fake()->dateTimeBetween('-6 months'),
]);
}
public function draft(): static
{
return $this->state(['status' => 'draft', 'published_at' => null]);
}
public function popular(): static
{
return $this->state(['view_count' => fake()->numberBetween(10000, 100000)]);
}
// Relationship configuration
public function configure(): static
{
return $this->afterCreating(function (Article $article) {
// Create tags after article creation
$article->tags()->attach(
\App\Models\Tag::factory()->count(3)->create()
);
});
}
}
// Usage in tests
$article = Article::factory()->published()->create();
$articles = Article::factory()->count(10)->for($user)->create();
$articleWithComments = Article::factory()
->has(Comment::factory()->count(5))
->create();States maken varianten mogelijk zonder de factory-logica te dupliceren.
Architectuur en Patronen
Vraag 18: Het Repository Pattern implementeren in Laravel
Het Repository Pattern abstraheert de datatoegang en vergemakkelijkt het testen door het mogelijk te maken queries te mocken.
namespace App\Contracts;
use App\Models\Article;
use Illuminate\Pagination\LengthAwarePaginator;
interface ArticleRepositoryInterface
{
public function find(int $id): ?Article;
public function findBySlug(string $slug): ?Article;
public function getPublished(int $perPage = 20): LengthAwarePaginator;
public function getByAuthor(int $userId, int $perPage = 20): LengthAwarePaginator;
public function create(array $data): Article;
public function update(Article $article, array $data): Article;
public function delete(Article $article): bool;
}namespace App\Repositories;
use App\Contracts\ArticleRepositoryInterface;
use App\Models\Article;
use Illuminate\Pagination\LengthAwarePaginator;
class EloquentArticleRepository implements ArticleRepositoryInterface
{
public function __construct(
private Article $model
) {}
public function find(int $id): ?Article
{
return $this->model->with('author')->find($id);
}
public function findBySlug(string $slug): ?Article
{
return $this->model
->where('slug', $slug)
->with(['author', 'tags'])
->firstOrFail();
}
public function getPublished(int $perPage = 20): LengthAwarePaginator
{
return $this->model
->published()
->with('author')
->orderByDesc('published_at')
->paginate($perPage);
}
public function create(array $data): Article
{
return $this->model->create($data);
}
public function update(Article $article, array $data): Article
{
$article->update($data);
return $article->fresh();
}
public function delete(Article $article): bool
{
return $article->delete();
}
}
// Binding in the ServiceProvider
$this->app->bind(
ArticleRepositoryInterface::class,
EloquentArticleRepository::class
);Het Repository Pattern is nuttig voor complexe applicaties, maar kan overdreven zijn voor eenvoudige projecten. De kosten-batenverhouding dient zorgvuldig te worden afgewogen.
Vraag 19: Transacties en concurrency in Laravel
Transacties waarborgen de data-integriteit bij meerdere operaties. Laravel vereenvoudigt het beheer ervan.
use Illuminate\Support\Facades\DB;
class OrderService
{
public function processOrder(Cart $cart, User $user): Order
{
// Transaction with closure: automatic rollback on exception
return DB::transaction(function () use ($cart, $user) {
// Create the order
$order = Order::create([
'user_id' => $user->id,
'total' => $cart->total(),
'status' => 'pending',
]);
// Create order items
foreach ($cart->items as $item) {
$order->items()->create([
'product_id' => $item->product_id,
'quantity' => $item->quantity,
'price' => $item->product->price,
]);
// Decrement stock with pessimistic locking
$product = Product::lockForUpdate()->find($item->product_id);
if ($product->stock < $item->quantity) {
throw new InsufficientStockException($product);
}
$product->decrement('stock', $item->quantity);
}
// Clear the cart
$cart->clear();
return $order;
}, attempts: 3); // 3 attempts in case of deadlock
}
public function updateOrderStatus(Order $order, string $status): void
{
// Optimistic locking with version/timestamp
$updated = DB::table('orders')
->where('id', $order->id)
->where('updated_at', $order->updated_at) // Version check
->update([
'status' => $status,
'updated_at' => now(),
]);
if ($updated === 0) {
throw new ConcurrencyException('The order was modified in the meantime');
}
}
}lockForUpdate() voorkomt gelijktijdige leesbewerkingen tijdens de transactie. Spaarzaam gebruik is aan te raden om deadlocks te voorkomen.
Vraag 20: Effectieve caching in Laravel
Caching verbetert de prestaties drastisch door repetitieve queries te vermijden.
// Caching strategies
use Illuminate\Support\Facades\Cache;
class ArticleService
{
public function getPopularArticles(): Collection
{
// Cache-Aside: check cache, otherwise load and store
return Cache::remember('articles:popular', 3600, function () {
return Article::published()
->popular()
->with('author')
->limit(10)
->get();
});
}
public function getArticle(string $slug): Article
{
// Cache by dynamic key
return Cache::remember("article:{$slug}", 1800, function () use ($slug) {
return Article::where('slug', $slug)
->with(['author', 'comments.user'])
->firstOrFail();
});
}
public function updateArticle(Article $article, array $data): Article
{
$article->update($data);
// Cache invalidation after modification
Cache::forget("article:{$article->slug}");
Cache::forget('articles:popular');
// Tags for grouped invalidation (Redis only)
Cache::tags(['articles', "user:{$article->user_id}"])->flush();
return $article;
}
public function getArticleWithLock(int $id): Article
{
// Atomic lock to avoid cache stampede
return Cache::lock("article-lock:{$id}", 10)->block(5, function () use ($id) {
return Cache::remember("article:{$id}", 3600, fn() => Article::findOrFail($id));
});
}
}'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
],Cache-tags maken efficiente gegroepeerde invalidatie mogelijk. Bij gelijktijdig verlopen van veel cache-items dreigt een cache stampede.
Prestaties en Optimalisatie
Vraag 21: Prestaties van een Laravel-applicatie optimaliseren
Optimalisatie beslaat meerdere niveaus: queries, cache, configuratie en infrastructuur.
$articles = Article::query()
->select(['id', 'title', 'slug', 'published_at', 'user_id']) // Specific columns
->with(['author:id,name,avatar']) // Selective eager loading
->withCount('comments') // Count in one query
->published()
->latest('published_at')
->cursorPaginate(20); // Cursor pagination (more performant)
// 2. Chunking for mass operations
Article::query()
->where('status', 'published')
->chunkById(1000, function ($articles) {
foreach ($articles as $article) {
// Processing in batches of 1000
ProcessArticle::dispatch($article);
}
});
// 3. Mass update without models
Article::where('published_at', '<', now()->subYear())
->update(['status' => 'archived']); // Single SQL query# Optimization commands for production
php artisan config:cache # Cache configuration
php artisan route:cache # Cache routes
php artisan view:cache # Compile Blade views
php artisan event:cache # Cache event mappings
php artisan optimize # Run all optimizations
# Optimized autoloader
composer install --optimize-autoloader --no-devDeze optimalisaties kunnen de opstarttijd in productie met 50% of meer verminderen.
Vraag 22: Debugging en profiling van een Laravel-applicatie
Laravel biedt diverse tools om prestatieproblemen en bugs te identificeren.
// Laravel Telescope for development debugging
// Captures requests, jobs, exceptions, etc.
// Query debugging
DB::enableQueryLog();
$articles = Article::with('author')->get();
$queries = DB::getQueryLog();
dump($queries); // Shows all SQL queries
// Debug bar integrated with Blade
@dump($variable) // Display and continue
@dd($variable) // Dump and die
// Structured logging
use Illuminate\Support\Facades\Log;
Log::channel('slack')->critical('Payment failed', [
'user_id' => $user->id,
'amount' => $amount,
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
// Context logging
Log::withContext(['request_id' => request()->id()]);
Log::info('Processing order', ['order_id' => $order->id]);'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
],Telescope is geschikt voor ontwikkeling, een APM-tool (New Relic, Datadog) voor continue monitoring in productie.
Deployment en Productie
Vraag 23: Migraties in productie zonder downtime
Productiemigraties vereisen bijzondere zorgvuldigheid om serviceonderbrekingen te voorkomen.
// Safe migration: add a nullable column first
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
// Step 1: Add nullable column
$table->string('role')->nullable()->after('email');
});
}
// Step 2: Data migration (separate job)
// php artisan tinker
// User::whereNull('role')->update(['role' => 'user']);
// Step 3: Second migration for constraint
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('role')->nullable(false)->default('user')->change();
});
}// For column deletions (3 deployments)
// Deployment 1: Stop using the column in code
// Deployment 2: Delete the column
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('deprecated_field');
});
// Migration with timeout for large tables
public function up(): void
{
DB::statement('SET lock_timeout TO \'5s\'');
Schema::table('large_table', function (Blueprint $table) {
$table->index('status'); // Concurrent index if PostgreSQL
});
}De expand-contract-strategie maakt het mogelijk kolommen toe te voegen zonder downtime: nullable toevoegen → data migreren → non-nullable instellen.
Vraag 24: Laravel configureren voor hoge beschikbaarheid
Een architectuur voor hoge beschikbaarheid vereist de scheiding van stateless componenten en het beheer van gedeelde state.
'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => 'session',
// config/cache.php - Shared cache
'default' => env('CACHE_DRIVER', 'redis'),
// config/queue.php - Redis queues for distribution
'default' => env('QUEUE_CONNECTION', 'redis'),
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'default',
'retry_after' => 90,
'block_for' => null,
],
],
// config/filesystems.php - S3 storage for files
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
],
],// Health check endpoint for load balancer
Route::get('/health', function () {
try {
DB::connection()->getPdo();
Cache::store('redis')->ping();
return response()->json(['status' => 'healthy']);
} catch (\Exception $e) {
return response()->json(['status' => 'unhealthy'], 503);
}
});Elke instantie moet stateless zijn. Sessies, cache en queues dienen Redis of een gedeelde store te gebruiken.
Vraag 25: Best practices voor Laravel-deployments
Een robuuste deployment combineert automatisering, controles en eenvoudige rollback.
# deploy.sh - Typical deployment script
#!/bin/bash
set -e
echo "Pulling latest code..."
git pull origin main
echo "Installing dependencies..."
composer install --no-dev --optimize-autoloader
echo "Running migrations..."
php artisan migrate --force
echo "Caching configuration..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
echo "Restarting queue workers..."
php artisan queue:restart
echo "Clearing old cache..."
php artisan cache:clear
echo "Deployment complete!"APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:... # Generated with php artisan key:generate
LOG_CHANNEL=stack
LOG_LEVEL=warning
# Never expose credentials in plain text
# Use secret managers (Vault, AWS Secrets Manager)Deployment-checklist:
php artisan config:cache- Configuratie cachenphp artisan route:cache- Routes cachenphp artisan view:cache- Views compilerencomposer install --no-dev- Productieafhankelijkheden- Geautomatiseerde tests voor deployment
- Health checks geconfigureerd
- Monitoring en alerting ingesteld
Conclusie
Deze 25 vragen bestrijken de essentiele aspecten van Laravel- en PHP-sollicitatiegesprekken, van de Service Container tot deployment-patronen in productie.
Voorbereidingschecklist:
- Service Container en dependency injection
- Eloquent ORM: relaties, scopes, eager loading
- Middleware, routing en beveiliging
- Queues, jobs en asynchrone events
- Testing: feature-tests, unit-tests, factories
- Geavanceerde patronen: Repository, Transacties, Caching
- Deployment: migraties, optimalisatie, hoge beschikbaarheid
Elke vraag verdient verdere verdieping met de officiele Laravel-documentatie. Interviewers waarderen kandidaten die de nuances van het framework kennen en hun technische keuzes kunnen onderbouwen.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Laravel 11: Een complete applicatie bouwen vanaf nul
Uitgebreide handleiding voor Laravel 11: installatie, Eloquent-modellen, authenticatie met Breeze, controllers, routes, autorisatie met policies, REST API met Sanctum, testen met Pest en productie-deployment.

Django en Python sollicitatievragen: De Top 25 in 2026
De 25 meest voorkomende Django- en Python-sollicitatievragen. ORM, views, middleware, DRF, signals en optimalisatie met gedetailleerde antwoorden en codevoorbeelden.

Rust Sollicitatievragen: Complete Gids 2026
De 25 meest gestelde Rust-sollicitatievragen. Ownership, borrowing, lifetimes, traits, async en concurrency met uitgebreide antwoorden en codevoorbeelden.