Laravel Lösungen: Fortgeschrittene Patterns, Debugging und Interview-Fragen 2026

Fortgeschrittene Laravel-Lösungen mit Service-Klassen, Repository-Pattern, Telescope-Debugging und Interview-Fragen für erfahrene PHP-Entwickler.

Laravel fortgeschrittenes Debugging und architektonische Patterns mit PHP 8.4

Laravel-Lösungen für Produktionsanwendungen erfordern mehr als grundlegende CRUD-Operationen. Der Unterschied zwischen Junior- und Senior-Laravel-Entwicklern zeigt sich in der Codestrukturierung, dem Debugging von Problemen und den Antworten auf architektonische Fragen in Vorstellungsgesprächen.

Worauf Interviewer achten

Senior-Laravel-Positionen erwarten, dass Kandidaten den Service Container erklären, Debugging-Workflows mit Telescope oder Debugbar demonstrieren und begründen können, wann Patterns wie Repository vs. direkte Eloquent-Queries angewendet werden sollten.

Service-Klassen: Geschäftslogik aus Controllern extrahieren

Controller in Laravel behandeln HTTP-Aspekte: Anfragen empfangen, Eingaben validieren und Antworten zurückgeben. Geschäftslogik gehört in dedizierte Service-Klassen, was den Code testbar und wiederverwendbar über Controller, Commands und Jobs hinweg macht.

app/Services/OrderService.phpphp
// Service class encapsulating order business logic

namespace App\Services;

use App\Models\Order;
use App\Models\User;
use App\Exceptions\InsufficientStockException;
use App\Jobs\SendOrderConfirmationEmail;
use Illuminate\Support\Facades\DB;

class OrderService
{
    public function __construct(
        private InventoryService $inventory,
        private PaymentService $payment
    ) {}

    /**
     * Create an order with full validation and side effects
     *
     * @throws InsufficientStockException
     */
    public function createOrder(User $user, array $items, string $paymentMethod): Order
    {
        // Validate stock availability before starting transaction
        foreach ($items as $item) {
            if (!$this->inventory->hasStock($item['product_id'], $item['quantity'])) {
                throw new InsufficientStockException($item['product_id']);
            }
        }

        return DB::transaction(function () use ($user, $items, $paymentMethod) {
            // Create order record
            $order = $user->orders()->create([
                'status' => 'pending',
                'total' => $this->calculateTotal($items),
            ]);

            // Attach order items
            foreach ($items as $item) {
                $order->items()->create($item);
                $this->inventory->decrementStock($item['product_id'], $item['quantity']);
            }

            // Process payment
            $this->payment->charge($user, $order->total, $paymentMethod);
            $order->update(['status' => 'paid']);

            // Queue confirmation email
            SendOrderConfirmationEmail::dispatch($order);

            return $order;
        });
    }

    private function calculateTotal(array $items): int
    {
        return collect($items)->sum(fn($item) => $item['price'] * $item['quantity']);
    }
}
app/Http/Controllers/OrderController.phpphp
// Controller delegating to service class

namespace App\Http\Controllers;

use App\Http\Requests\CreateOrderRequest;
use App\Services\OrderService;
use Illuminate\Http\JsonResponse;

class OrderController extends Controller
{
    public function __construct(
        private OrderService $orderService
    ) {}

    public function store(CreateOrderRequest $request): JsonResponse
    {
        $order = $this->orderService->createOrder(
            $request->user(),
            $request->validated('items'),
            $request->validated('payment_method')
        );

        return response()->json(['order_id' => $order->id], 201);
    }
}

Service-Klassen können überall in Laravel injiziert werden: Controller, Commands, andere Services oder Job-Klassen. Die Service Container Dokumentation behandelt automatische Auflösung und Binding.

Repository Pattern: Wenn direktes Eloquent nicht ausreicht

Das Repository-Pattern fügt eine Abstraktionsschicht zwischen Geschäftslogik und Datenzugriff hinzu. Dieses Pattern erweist sich als wertvoll, wenn Anwendungen Datenquellen wechseln, Query-Ergebnisse transparent cachen oder komplexe Query-Logik isolieren müssen.

Interview-Kontext

Kandidaten werden oft gefragt, ob sie das Repository-Pattern mit Laravel verwenden. Die richtige Antwort hängt vom Kontext ab: Repositories bieten Mehrwert für große Anwendungen mit komplexen Datenanforderungen, erzeugen aber unnötige Abstraktion für einfache CRUD-Anwendungen.

app/Repositories/Contracts/UserRepositoryInterface.phpphp
// Interface defining the contract

namespace App\Repositories\Contracts;

use App\Models\User;
use Illuminate\Support\Collection;

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function getActiveUsers(): Collection;
    public function getUsersWithRecentOrders(int $days = 30): Collection;
    public function create(array $data): User;
    public function update(User $user, array $data): User;
}
app/Repositories/EloquentUserRepository.phpphp
// Eloquent implementation of the repository

namespace App\Repositories;

use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

class EloquentUserRepository implements UserRepositoryInterface
{
    public function findById(int $id): ?User
    {
        return Cache::remember(
            "user.{$id}",
            now()->addMinutes(10),
            fn() => User::find($id)
        );
    }

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

    public function getActiveUsers(): Collection
    {
        return User::where('status', 'active')
            ->orderBy('name')
            ->get();
    }

    public function getUsersWithRecentOrders(int $days = 30): Collection
    {
        return User::whereHas('orders', function ($query) use ($days) {
            $query->where('created_at', '>=', now()->subDays($days));
        })
        ->with(['orders' => fn($q) => $q->latest()->limit(5)])
        ->get();
    }

    public function create(array $data): User
    {
        $user = User::create($data);
        Cache::forget("user.{$user->id}");
        return $user;
    }

    public function update(User $user, array $data): User
    {
        $user->update($data);
        Cache::forget("user.{$user->id}");
        return $user->fresh();
    }
}
app/Providers/RepositoryServiceProvider.phpphp
// Binding interface to implementation

namespace App\Providers;

use App\Repositories\Contracts\UserRepositoryInterface;
use App\Repositories\EloquentUserRepository;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            UserRepositoryInterface::class,
            EloquentUserRepository::class
        );
    }
}

Die Repository-Implementierung behandelt Caching intern und hält Service-Klassen auf Geschäftsregeln fokussiert, anstatt sich um Cache-Invalidierung zu kümmern.

Bereit für deine Laravel-Interviews?

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

Laravel-Anwendungen mit Telescope debuggen

Laravel Telescope bietet einen Debug-Assistenten, der Anfragen, Exceptions, Datenbankabfragen, Jobs und mehr aufzeichnet. Produktions-Debugging erfordert das Verständnis, welche Metriken wichtig sind und wie man Rauschen filtert.

config/telescope.phpphp
// Telescope configuration for different environments

return [
    'enabled' => env('TELESCOPE_ENABLED', true),

    // Only record slow queries in production
    'query' => [
        'slow' => env('TELESCOPE_SLOW_QUERY_THRESHOLD', 100), // milliseconds
    ],

    // Prune old entries to manage database size
    'storage' => [
        'database' => [
            'connection' => env('DB_CONNECTION', 'mysql'),
            'chunk' => 1000,
        ],
    ],
];
app/Providers/TelescopeServiceProvider.phpphp
// Custom filtering for Telescope entries

namespace App\Providers;

use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;

class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
    public function register(): void
    {
        Telescope::night();

        $this->hideSensitiveRequestDetails();

        // Filter out noise in production
        Telescope::filter(function (IncomingEntry $entry) {
            // Always record exceptions and slow queries
            if ($entry->isException()) {
                return true;
            }

            if ($entry->isSlowQuery()) {
                return true;
            }

            // Skip health check endpoints
            if ($entry->isRequest() && str_starts_with($entry->content['uri'] ?? '', '/health')) {
                return false;
            }

            // Skip static asset requests
            if ($entry->isRequest() && preg_match('/\.(css|js|png|jpg|svg)$/', $entry->content['uri'] ?? '')) {
                return false;
            }

            // Record everything in local environment
            if ($this->app->environment('local')) {
                return true;
            }

            // Production: only record failures and slow operations
            return $entry->isFailedRequest()
                || $entry->isFailedJob()
                || $entry->hasMonitoredTag();
        });
    }

    protected function hideSensitiveRequestDetails(): void
    {
        // Hide sensitive data from Telescope UI
        Telescope::hideRequestParameters(['password', 'password_confirmation', 'token']);
        Telescope::hideRequestHeaders(['authorization', 'cookie']);
    }
}

Die Methode isSlowQuery() markiert Datenbankabfragen, die den konfigurierten Schwellenwert überschreiten. Die Analyse langsamer Queries deckt fehlende Indizes und N+1-Probleme auf, die auch Profiling-Tools wie Debugbar erkennen.

Häufige Interview-Fragen und starke Antworten

Technische Interviews für Laravel-Positionen folgen Mustern. Die folgenden Fragen erscheinen häufig, und die Antworten demonstrieren die Tiefe, die Interviewer erwarten.

Was ist der Service Container und warum ist er wichtig?

Der Service Container ist Laravels Dependency Injection Container. Er verwaltet Klassenabhängigkeiten und führt Dependency Injection automatisch durch. Wenn ein Controller-Konstruktor OrderService als Type-Hint hat, löst der Container eine Instanz auf und injiziert sie.

php
// Automatic resolution: container reads type hints and builds dependencies
public function __construct(OrderService $service)
{
    // $service is automatically instantiated and injected
}

// Manual binding for interfaces or complex setup
$this->app->bind(PaymentGateway::class, function ($app) {
    return new StripeGateway(
        config('services.stripe.key'),
        config('services.stripe.secret')
    );
});

// Singleton: same instance throughout the request
$this->app->singleton(MetricsCollector::class, function ($app) {
    return new MetricsCollector(
        $app->make(Cache::class)
    );
});

Der Container ermöglicht lose Kopplung: Klassen hängen von Abstraktionen (Interfaces) ab statt von konkreten Implementierungen, was Testing und das Austauschen von Implementierungen vereinfacht.

Wie behandelt Laravel Datenbank-Transaktionen?

Laravel umschließt Datenbankoperationen mit der Methode DB::transaction() in Transaktionen. Transaktionen gewährleisten Atomarität: Entweder sind alle Operationen erfolgreich, oder alle werden zurückgerollt.

php
// Simple transaction with automatic rollback on exception
DB::transaction(function () {
    $user = User::create(['email' => 'test@example.com']);
    $user->profile()->create(['bio' => 'New user']);
    // If profile creation fails, user creation rolls back
});

// Manual transaction control for complex flows
DB::beginTransaction();

try {
    $order = Order::create($data);
    PaymentGateway::charge($order->total);

    DB::commit();
} catch (PaymentFailedException $e) {
    DB::rollBack();
    throw $e;
}

// Nested transactions use savepoints
DB::transaction(function () {
    User::create(['email' => 'outer@test.com']);

    DB::transaction(function () {
        // This creates a savepoint
        Profile::create(['user_id' => 1]);
    });
    // Inner failure only rolls back to savepoint
});

Interviewer fragen oft nach Deadlocks. Die Antwort: Laravel wiederholt Transaktionen, die aufgrund von Deadlocks fehlschlagen (konfigurierbar über das zweite Argument von DB::transaction()).

Middleware erklären und einen praktischen Anwendungsfall nennen

Middleware filtert HTTP-Anfragen, die in die Anwendung eingehen. Jede Middleware kann Anfragen untersuchen, modifizieren oder ablehnen, bevor sie Controller erreichen.

app/Http/Middleware/EnsureTeamMember.phpphp
// Custom middleware checking team membership

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureTeamMember
{
    public function handle(Request $request, Closure $next, string $role = 'member'): Response
    {
        $team = $request->route('team');

        if (!$request->user()->belongsToTeam($team)) {
            abort(403, 'You are not a member of this team.');
        }

        if ($role === 'admin' && !$request->user()->isTeamAdmin($team)) {
            abort(403, 'Admin access required.');
        }

        return $next($request);
    }
}
routes/web.phpphp
// Applying middleware to routes

Route::middleware(['auth', 'team.member:admin'])->group(function () {
    Route::get('/teams/{team}/settings', [TeamController::class, 'settings']);
    Route::put('/teams/{team}/settings', [TeamController::class, 'updateSettings']);
});

Middleware wird in Reihenfolge ausgeführt. Authentifizierungs-Middleware sollte vor Autorisierungs-Middleware laufen, und Logging-Middleware umschließt typischerweise alles.

Das N+1-Query-Problem lösen

Das N+1-Problem erzeugt eine Query für die initiale Collection plus eine Query pro Element beim Zugriff auf Beziehungen. Eine Liste von 100 Artikeln mit Autoren produziert 101 Queries statt 2.

php
// Problem: 101 queries for 100 articles
$articles = Article::all();
foreach ($articles as $article) {
    echo $article->author->name; // Each access triggers a query
}

// Solution: eager load with 2 queries total
$articles = Article::with('author')->get();
foreach ($articles as $article) {
    echo $article->author->name; // No additional queries
}

// Nested eager loading for complex relationships
$articles = Article::with([
    'author.profile',
    'comments' => fn($query) => $query->latest()->limit(5),
    'comments.user',
    'tags',
])->get();

Laravel kann Lazy Loading verhindern in der Entwicklung, um N+1-Probleme früh zu erkennen:

app/Providers/AppServiceProvider.phpphp
use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::preventLazyLoading(!$this->app->isProduction());
}

Mit dieser Einstellung wirft der Zugriff auf eine nicht geladene Beziehung in der Entwicklung eine Exception und erzwingt explizites Eager Loading.

Bereit für deine Laravel-Interviews?

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

Action-Klassen für Einzelzweck-Operationen

Action-Klassen kapseln einzelne Operationen, die von mehreren Einstiegspunkten aufgerufen werden können. Im Gegensatz zu Service-Klassen, die verwandte Methoden gruppieren, behandeln Actions eine Aufgabe mit klaren Ein- und Ausgaben.

app/Actions/CreateUserAction.phpphp
// Single-purpose action class

namespace App\Actions;

use App\Models\User;
use App\Notifications\WelcomeNotification;
use Illuminate\Support\Facades\Hash;

class CreateUserAction
{
    public function execute(array $data): User
    {
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $user->notify(new WelcomeNotification());

        return $user;
    }
}
php
// Usage in controller
public function store(RegisterRequest $request, CreateUserAction $action)
{
    $user = $action->execute($request->validated());
    return redirect()->route('dashboard');
}

// Usage in command
public function handle(CreateUserAction $action)
{
    $user = $action->execute([
        'name' => $this->argument('name'),
        'email' => $this->argument('email'),
        'password' => $this->argument('password'),
    ]);
    $this->info("Created user: {$user->id}");
}

// Usage in test
public function test_user_creation(): void
{
    $action = new CreateUserAction();
    $user = $action->execute([
        'name' => 'Test User',
        'email' => 'test@example.com',
        'password' => 'password',
    ]);

    $this->assertDatabaseHas('users', ['email' => 'test@example.com']);
}

Action-Klassen funktionieren gut mit Laravels Job-System: Die Action enthält die Logik, und der Job behandelt Queueing und Retry-Verhalten.

Event-getriebene Architektur mit Events und Listeners

Events entkoppeln den Moment, in dem etwas passiert, von den Reaktionen auf dieses Event. Wenn eine Bestellung aufgegeben wird, wird das OrderPlaced-Event ausgelöst. Listeners behandeln E-Mail-Versand, Analytics-Updates und Lagerbenachrichtigungen unabhängig voneinander.

app/Events/OrderPlaced.phpphp
// Event class carrying order data

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderPlaced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order
    ) {}
}
app/Listeners/SendOrderConfirmation.phpphp
// Queued listener for email

namespace App\Listeners;

use App\Events\OrderPlaced;
use App\Notifications\OrderConfirmationNotification;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendOrderConfirmation implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        $event->order->user->notify(
            new OrderConfirmationNotification($event->order)
        );
    }
}
app/Listeners/UpdateInventoryAnalytics.phpphp
// Another listener for the same event

namespace App\Listeners;

use App\Events\OrderPlaced;
use App\Services\AnalyticsService;
use Illuminate\Contracts\Queue\ShouldQueue;

class UpdateInventoryAnalytics implements ShouldQueue
{
    public function __construct(
        private AnalyticsService $analytics
    ) {}

    public function handle(OrderPlaced $event): void
    {
        foreach ($event->order->items as $item) {
            $this->analytics->recordSale(
                $item->product_id,
                $item->quantity,
                $event->order->created_at
            );
        }
    }
}
php
// Dispatching the event
OrderPlaced::dispatch($order);

// Or using the event helper
event(new OrderPlaced($order));

Listeners, die ShouldQueue implementieren, laufen asynchron und verhindern, dass langsame Operationen die HTTP-Antwort blockieren.

Erkenntnisse für Laravel-Entwickler

  • Service-Klassen extrahieren Geschäftslogik aus Controllern, verbessern die Testbarkeit und ermöglichen Wiederverwendung über HTTP-, CLI- und Queue-Kontexte hinweg
  • Das Repository-Pattern bietet Mehrwert, wenn Anwendungen Caching, Datenquellenabstraktion oder komplexe Query-Kapselung benötigen, erzeugt aber Overhead für einfaches CRUD
  • Telescope-Filter reduzieren Rauschen in der Produktion, indem nur Exceptions, langsame Queries und fehlgeschlagene Operationen aufgezeichnet werden
  • Der Service Container verwaltet Dependency Injection automatisch durch Type-Hints, mit expliziten Bindings für Interfaces und komplexe Setups
  • N+1-Probleme verschwinden mit Eager Loading via with(), und Model::preventLazyLoading() erkennt fehlende Eager Loads während der Entwicklung
  • Action-Klassen behandeln Einzelzweck-Operationen, die von Controllern, Commands, Tests und Jobs aufrufbar sind
  • Events entkoppeln "was passiert ist" von "was als nächstes passieren soll", wobei Queued Listeners Seiteneffekte asynchron behandeln
  • Interview-Antworten sollten Verständnis für Trade-offs demonstrieren: wann Patterns helfen versus wann sie unnötige Komplexität hinzufügen

Fang an zu üben!

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

Tägliche Challenge

Findest du den Bug in Laravel?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 25. August 2026

Tags

#laravel
#php
#debugging
#design-patterns
#interview

Teilen

Verwandte Artikel