Laravel und PHP Interviewfragen: Die Top 25 in 2026

Die 25 haeufigsten Laravel- und PHP-Interviewfragen. Eloquent ORM, Middleware, Artisan, Queues, Tests und Architektur mit ausfuehrlichen Antworten und Codebeispielen.

Laravel und PHP Interviewfragen - Vollstaendiger Leitfaden

Laravel-Vorstellungsgespraeche pruefen den sicheren Umgang mit dem beliebtesten PHP-Framework, das Verstaendnis des Eloquent ORM, der MVC-Architektur und die Faehigkeit, robuste und wartbare Anwendungen zu entwickeln. Dieser Leitfaden behandelt die 25 am haeufigsten gestellten Fragen, von Laravel-Grundlagen bis hin zu fortgeschrittenen Deployment-Patterns in der Produktion.

Interview-Tipp

Interviewer schaetzen Kandidaten, die Laravels architektonische Entscheidungen erlaeutern koennen. Das Verstaendnis, warum das Framework bestimmte Konventionen verfolgt (Convention over Configuration, Service Container), macht in Vorstellungsgespraechen einen echten Unterschied.

Laravel-Grundlagen

Frage 1: Der Request Lifecycle in Laravel

Der Laravel Request Lifecycle durchlaeuft mehrere Schichten, bevor er den Controller erreicht. Das Verstaendnis dieses Zyklus ist fuer Debugging und Performance-Optimierung unverzichtbar.

public/index.phpphp
// 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);

Der vollstaendige Zyklus: index.php → Autoload → Bootstrap → Service Providers → Middlewares → Routing → Controller → Response → Terminate. Jeder Schritt kann abgefangen und angepasst werden.

Frage 2: Was ist der Service Container und wie funktioniert Dependency Injection?

Der Service Container bildet den Kern von Laravel. Er verwaltet die Instanziierung von Klassen und loest Abhaengigkeiten automatisch ueber Constructor Injection auf.

app/Services/PaymentService.phpphp
// 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;
        }
    }
}
app/Providers/AppServiceProvider.phpphp
// 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 entkoppelt Klassen und erleichtert Unit-Tests durch Mocking.

Frage 3: Was ist der Unterschied zwischen Facades und Dependency Injection?

Facades bieten eine statische Syntax fuer den Zugriff auf Container-Services, waehrend Dependency Injection Abhaengigkeiten explizit macht.

php
// 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 ist Dependency Injection wegen der besseren Testbarkeit vorzuziehen. Facades eignen sich fuer Hilfsfunktionen und Ad-hoc-Code.

Frage 4: Wie funktionieren Service Providers in Laravel?

Service Providers sind der zentrale Konfigurationspunkt der Anwendung. Jeder Provider registriert Services, konfiguriert Bindings und initialisiert Komponenten.

app/Providers/PaymentServiceProvider.phpphp
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');
    }
}

Ausfuehrungsreihenfolge: Zuerst alle register()-Methoden, dann alle boot()-Methoden. Diese Reihenfolge stellt sicher, dass Abhaengigkeiten waehrend des Boot-Vorgangs verfuegbar sind.

Eloquent ORM

Frage 5: Eloquent-Beziehungen und ihre Unterschiede

Eloquent bietet verschiedene Beziehungstypen zur Modellierung von Tabellenverknuepfungen. Jeder Typ hat spezifische Anwendungsfaelle.

app/Models/User.phpphp
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');
    }
}

Polymorphe Beziehungen (morphOne, morphMany, morphToMany) ermoeglichen es einem Model, ueber eine einzige Beziehung mit mehreren anderen Model-Typen verknuepft zu werden.

Frage 6: Das N+1-Problem und Eager Loading als Loesung

Das N+1-Problem entsteht, wenn eine Hauptabfrage N zusaetzliche Abfragen zum Laden von Beziehungen ausloest. Es ist die haeufigste Ursache fuer Performanceprobleme in Laravel-Anwendungen.

php
// ❌ 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'];
}

Mit php artisan telescope:prune und Laravel Telescope lassen sich N+1-Probleme waehrend der Entwicklung erkennen.

Frage 7: Query Scopes erstellen und einsetzen

Query Scopes kapseln wiederverwendbare Abfragebedingungen auf Model-Ebene und machen den Code lesbarer und DRY.

app/Models/Article.phpphp
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 verbessern die Lesbarkeit und zentralisieren die Abfragelogik. Globale Scopes eignen sich fuer Multi-Tenancy oder Soft Delete.

Bereit für deine Laravel-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Middleware und Routing

Frage 8: Wie funktionieren Middlewares in Laravel?

Middlewares filtern eingehende HTTP-Requests und koennen ausgehende Responses modifizieren. Jeder Request durchlaeuft einen Middleware-Stack.

app/Http/Middleware/CheckSubscription.phpphp
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,
        ]);
    });
routes/web.phpphp
// Applying middlewares to routes
Route::middleware(['auth', 'subscription:premium'])->group(function () {
    Route::get('/dashboard', DashboardController::class);
    Route::resource('projects', ProjectController::class);
});

Die Reihenfolge der Middlewares ist entscheidend: Sie werden beim Eingang von oben nach unten und beim Ausgang von unten nach oben ausgefuehrt.

Frage 9: Route Model Binding und seine Varianten

Route Model Binding injiziert automatisch Eloquent-Models in Controller, basierend auf URL-Parametern.

routes/web.phpphp
// 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();
app/Models/Article.phpphp
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();
    }
}
app/Providers/RouteServiceProvider.phpphp
// 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 reduziert Boilerplate-Code und zentralisiert die Aufloeselogik.

Queues und Jobs

Frage 10: Jobs und Queues in Laravel implementieren

Queues ermoeglichen es, ressourcenintensive Aufgaben in den Hintergrund zu verlagern und so die Reaktionsfaehigkeit der Anwendung zu verbessern.

app/Jobs/ProcessPodcast.phpphp
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);
    }
}
php
// 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();

Der Worker wird mit php artisan queue:work --queue=high,default gestartet, um mehrere Queues nach Prioritaet abzuarbeiten.

Frage 11: Unterschied zwischen Jobs, Events und Listeners

Alle drei Konzepte entkoppeln Code, verfolgen aber unterschiedliche Absichten.

php
// 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,
        ]);
    }
}
app/Providers/EventServiceProvider.phpphp
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 foerdern eine entkoppelte Architektur: Der Code, der das Event ausloest, kennt dessen Auswirkungen nicht.

Sicherheit und Authentifizierung

Frage 12: Wie schuetzt Laravel vor CSRF-Angriffen?

Laravel generiert automatisch ein eindeutiges CSRF-Token pro Session und ueberprueft dieses Token bei jedem POST-, PUT-, PATCH- und DELETE-Request.

php
// 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;
bootstrap/app.phpphp
// 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
        ]);
    });

Der CSRF-Schutz sollte niemals global deaktiviert werden. Ausnahmen sind nur fuer Endpunkte sinnvoll, die anderweitig authentifiziert werden.

Frage 13: Authentifizierung mit Laravel Sanctum implementieren

Laravel Sanctum bietet leichtgewichtige Authentifizierung fuer SPAs, mobile Apps und Token-basierte APIs.

php
// 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;
}
app/Http/Controllers/AuthController.phpphp
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 unterstuetzt auch Cookie-Authentifizierung fuer Same-Domain-SPAs und bietet dabei automatischen CSRF-Schutz.

Frage 14: API-Absicherung mit Policies und Gates

Policies und Gates zentralisieren die Autorisierungslogik und trennen Geschaeftsregeln von Controllern.

app/Policies/ArticlePolicy.phpphp
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;
    }
}
app/Http/Controllers/ArticleController.phpphp
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 sind an ein Model gebunden, Gates dienen allgemeinen Autorisierungen.

Validierung und Formulare

Frage 15: Custom Validation Rules erstellen

Laravel bietet verschiedene Moeglichkeiten zur Erstellung benutzerdefinierter Validierungen, je nach Komplexitaet und Wiederverwendbarkeit.

app/Rules/StrongPassword.phpphp
// 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) . '.');
        }
    }
}
app/Http/Requests/RegisterRequest.phpphp
// 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 zentralisieren Validierung, Autorisierung und Fehlermeldungen und entlasten damit die Controller.

Bereit für deine Laravel-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Testing

Frage 16: Teststruktur in Laravel

Laravel bietet PHPUnit mit dedizierten Hilfsfunktionen zum Testen verschiedener Anwendungsschichten.

tests/Feature/ArticleControllerTest.phpphp
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);
    }
}
tests/Unit/Services/PaymentServiceTest.phpphp
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, Integration) und Unit-Tests (isolierte Klassen mit Mocks) sollten klar getrennt werden.

Frage 17: Factories und Seeders effektiv einsetzen

Factories generieren realistische Testdaten, Seeders befuellen die Datenbank.

database/factories/ArticleFactory.phpphp
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 ermoeglichen verschiedene Varianten, ohne die Factory-Logik zu duplizieren.

Architektur und Patterns

Frage 18: Das Repository Pattern in Laravel implementieren

Das Repository Pattern abstrahiert den Datenzugriff und erleichtert das Testen durch Mocking von Abfragen.

app/Contracts/ArticleRepositoryInterface.phpphp
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;
}
app/Repositories/EloquentArticleRepository.phpphp
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
);

Das Repository Pattern ist bei komplexen Anwendungen nuetzlich, kann aber bei einfachen Projekten ueberdimensioniert sein. Das Kosten-Nutzen-Verhaeltnis sollte abgewogen werden.

Frage 19: Transaktionen und Concurrency in Laravel

Transaktionen gewaehrleisten die Datenintegritaet bei mehreren Operationen. Laravel vereinfacht deren Verwaltung.

app/Services/OrderService.phpphp
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() verhindert parallele Lesezugriffe waehrend der Transaktion. Es sollte sparsam eingesetzt werden, um Deadlocks zu vermeiden.

Frage 20: Effektives Caching in Laravel

Caching verbessert die Performance drastisch, indem repetitive Abfragen vermieden werden.

php
// 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));
        });
    }
}
config/cache.php - Recommended Redis configuration for productionphp
'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
        'lock_connection' => 'default',
    ],
],

Cache-Tags ermoeglichen eine effiziente gruppierte Invalidierung. Bei gleichzeitigem Ablauf vieler Cache-Eintraege besteht die Gefahr eines Cache Stampede.

Performance und Optimierung

Frage 21: Performance einer Laravel-Anwendung optimieren

Optimierung umfasst mehrere Ebenen: Abfragen, Cache, Konfiguration und Infrastruktur.

1. Eloquent query optimizationphp
$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
bash
# 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-dev

Diese Optimierungen koennen die Boot-Zeit in der Produktion um 50 % oder mehr reduzieren.

Frage 22: Debugging und Profiling einer Laravel-Anwendung

Laravel bietet verschiedene Werkzeuge zur Identifizierung von Performance-Problemen und Bugs.

php
// 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]);
config/logging.php - Channel configurationphp
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'slack'],
        'ignore_exceptions' => false,
    ],
    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'debug',
        'days' => 14,
    ],
],

Telescope eignet sich fuer die Entwicklung, ein APM-Tool (New Relic, Datadog) fuer das kontinuierliche Monitoring in der Produktion.

Deployment und Produktion

Frage 23: Migrationen in der Produktion ohne Downtime

Produktions-Migrationen erfordern besondere Sorgfalt, um Serviceunterbrechungen zu vermeiden.

database/migrations/2026_01_30_add_role_to_users_table.phpphp
// 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();
    });
}
php
// 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
    });
}

Die Expand-Contract-Strategie ermoeglicht das Hinzufuegen von Spalten ohne Downtime: nullable hinzufuegen → Daten migrieren → non-nullable setzen.

Frage 24: Laravel fuer Hochverfuegbarkeit konfigurieren

Eine Hochverfuegbarkeitsarchitektur erfordert die Trennung zustandsloser Komponenten und die Verwaltung geteilter Zustaende.

config/session.php - Sessions shared between instancesphp
'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'),
    ],
],
php
// 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);
    }
});

Jede Instanz muss zustandslos sein. Sessions, Cache und Queues sollten Redis oder einen gemeinsamen Speicher nutzen.

Frage 25: Best Practices fuer Laravel-Deployments

Ein robustes Deployment kombiniert Automatisierung, Pruefungen und einfaches Rollback.

bash
# 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!"
.env.production - Critical variablesphp
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-Checkliste:

  • php artisan config:cache - Konfiguration cachen
  • php artisan route:cache - Routen cachen
  • php artisan view:cache - Views kompilieren
  • composer install --no-dev - Produktionsabhaengigkeiten
  • Automatisierte Tests vor dem Deployment
  • Health Checks konfiguriert
  • Monitoring und Alerting eingerichtet

Fazit

Diese 25 Fragen decken die wesentlichen Aspekte von Laravel- und PHP-Interviews ab, vom Service Container bis hin zu Deployment-Patterns in der Produktion.

Vorbereitungs-Checkliste:

  • Service Container und Dependency Injection
  • Eloquent ORM: Beziehungen, Scopes, Eager Loading
  • Middleware, Routing und Sicherheit
  • Queues, Jobs und asynchrone Events
  • Testing: Feature-Tests, Unit-Tests, Factories
  • Erweiterte Patterns: Repository, Transaktionen, Caching
  • Deployment: Migrationen, Optimierung, Hochverfuegbarkeit
Weitergehende Informationen

Jede Frage verdient eine tiefere Auseinandersetzung mit der offiziellen Laravel-Dokumentation. Interviewer schaetzen Kandidaten, die die Feinheiten des Frameworks kennen und ihre technischen Entscheidungen begruenden koennen.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tags

#laravel
#php
#interview
#eloquent
#technical interview

Teilen

Verwandte Artikel