Start Prinzipien Session 1 Session 2 Session 3 Session 4

Session 0: Laravel-Prinzipien

Ziel dieser Session

Service Container, Dependency Injection, MVC, SOLID und Design Patterns.

MVC: Model-View-Controller

Das wichtigste Architekturmuster in Laravel. MVC trennt deine Anwendung in drei Verantwortungsbereiche:

Model — Zuständig für Daten und Geschäftslogik. Ein Model repräsentiert eine Datenbanktabelle und definiert, wie Daten gelesen, gespeichert und validiert werden. In Laravel heißt das ORM "Eloquent".

View — Zuständig für die Darstellung. Die View bekommt Daten und zeigt sie als HTML an. In Laravel sind das Blade-Templates. Eine View weiß nichts über die Datenbank.

Controller — Der Vermittler. Er nimmt die Anfrage entgegen, holt über das Model die nötigen Daten und übergibt sie an die View. Der Controller enthält keine HTML-Ausgabe und keine SQL-Queries.

Warum diese Trennung?

Stell dir ein Restaurant vor:

  • Die Küche (Model) bereitet das Essen zu und kennt die Rezepte
  • Der Teller (View) präsentiert das Ergebnis dem Gast
  • Der Kellner (Controller) koordiniert: nimmt die Bestellung, gibt sie an die Küche, bringt das Ergebnis zum Tisch

Niemand will einen Kellner, der gleichzeitig kocht und Teller bemalt. Genauso will niemand einen Controller, der SQL-Queries feuert und HTML zusammenbaut.

In den folgenden Sessions wirst du diesen MVC-Zyklus immer wieder durchlaufen — er ist der rote Faden der gesamten Schulung. Aber bevor wir dort ankommen, schauen wir uns an, was zwischen dem Browser und dem Controller eigentlich passiert.

Der Request Lifecycle

Wenn ein Browser https://deine-app.de/posts aufruft, passiert eine ganze Menge, bevor dein Controller-Code läuft. Diesen Ablauf zu kennen hilft dir zu verstehen, wo Middleware, der Service Container und dein eigener Code ins Spiel kommen.

Der Weg einer Anfrage durch Laravel

Code
1. public/index.php        ← Einstiegspunkt: Jede Anfrage landet hier
2. Bootstrap / Service Container erstellen
3. HTTP Kernel
4. Globale Middleware (z.B. CSRF-Schutz, Cookies, Sessions)
5. Router → Route matchen
6. Route-Middleware (z.B. auth, admin)
7. Controller-Methode ausführen
8. Response zurück durch die Middleware-Kette
9. Response an den Browser senden

Schritt für Schritt

1. public/index.php — Der Webserver (Apache/Nginx) leitet alle Anfragen an diese eine Datei weiter. Sie ist der Eingang zum gesamten Framework. Hier passieren zwei Dinge: Composer's Autoloader wird geladen, und die Laravel-Application wird erstellt.

2. Application Bootstrap — Laravel erstellt den Service Container — das zentrale Objekt, das alles zusammenhält. Alle Service Provider werden registriert (Datenbank, Auth, Cache, Mail, Routing, etc.). Nach der Registrierung wird boot() auf allen Providern aufgerufen. Am Ende dieses Schritts ist das Framework einsatzbereit.

3. HTTP Kernel — Der Kernel (app/Http/Kernel.php bzw. in Laravel 13 bootstrap/app.php) ist die Schaltzentrale. Er definiert, welche globalen Middleware bei jeder Anfrage durchlaufen werden, und leitet die Anfrage dann an den Router weiter.

4. Globale Middleware — Middleware sind Filter, die die Anfrage durchläuft, bevor (und nachdem) dein Code läuft. Globale Middleware gilt für jede Anfrage:

  • EncryptCookies — Verschlüsselt Cookies
  • StartSession — Startet die PHP-Session
  • VerifyCsrfToken — Prüft das CSRF-Token bei POST-Requests
  • ShareErrorsFromSession — Macht Validierungsfehler in Views verfügbar

Stell dir Middleware als Sicherheitsschleusen vor: Die Anfrage muss durch jede einzelne durch. Jede Schleuse kann die Anfrage weiterleiten, verändern oder ablehnen.

5. Router — Der Router gleicht die URL und HTTP-Methode mit deinen definierten Routen ab. GET /posts wird z.B. auf PostController@index gemappt.

6. Route-Middleware — Zusätzliche Middleware, die nur für bestimmte Routen gilt. In unserer Schulung nutzen wir auth (nur eingeloggte User) und später admin (nur Admins). Diese Middleware läuft erst, wenn die Route bereits gematcht wurde.

7. Controller — Endlich: Dein Code läuft. Der Controller bekommt das Request-Objekt (automatisch per Dependency Injection), arbeitet mit Models, und gibt eine Response zurück — meistens eine View.

8. Response zurück — Die Response wandert die Middleware-Kette rückwärts. Middleware kann auch auf dem Rückweg aktiv werden (z.B. Headers setzen, Cookies anhängen).

9. Browser — Der Webserver sendet die fertige HTTP-Response an den Browser.

Warum das wichtig ist

Wenn du später einen 419-Fehler bekommst (CSRF-Token fehlt) oder einen 403 (Middleware blockt), weißt du jetzt, an welcher Stelle im Lifecycle das passiert. Der Fehler liegt nicht in deinem Controller — die Anfrage kommt gar nicht so weit.

Auch Dependency Injection wird klarer: Der Service Container existiert ab Schritt 2. Wenn der Controller in Schritt 7 einen Type-Hint hat (Request $request, Post $post), löst der Container diese Abhängigkeit auf — weil er längst bereit ist.

SOLID-Prinzipien

SOLID ist ein Akronym für fünf Designprinzipien der objektorientierten Programmierung. Laravel hält sich konsequent daran. Du musst sie nicht auswendig können, aber ein Grundverständnis hilft enorm.

S — Single Responsibility Principle

Eine Klasse sollte nur einen Grund haben, sich zu ändern.

In Laravel: Ein Controller kümmert sich um eine Ressource. Ein Model kümmert sich um eine Tabelle. Ein Form Request kümmert sich um Validierung. Wenn du merkst, dass eine Klasse "zu viel" macht, ist das ein Zeichen, sie aufzuteilen.

Beispiel: Statt Validierung direkt im Controller zu schreiben, bietet Laravel Form Requests als eigene Klasse an — jede Verantwortung in ihrer eigenen Datei.

O — Open/Closed Principle

Klassen sollten offen für Erweiterung sein, aber geschlossen für Änderung.

In Laravel: Du erweiterst das Framework durch eigene Service Provider, Middleware oder Events — ohne den Core-Code anzufassen. Laravel selbst ist so aufgebaut, dass du Verhalten ändern kannst, ohne Quellcode zu editieren.

L — Liskov Substitution Principle

Unterklassen sollten überall dort funktionieren, wo die Basisklasse erwartet wird.

In Laravel: Alle Eloquent-Models erben von Model und sind untereinander austauschbar, wenn es um grundlegende Datenbankoperationen geht. Ein Post-Model funktioniert genauso wie ein Comment-Model, wenn du find(), create() oder delete() aufrufst.

I — Interface Segregation Principle

Clients sollten nicht gezwungen sein, Interfaces zu implementieren, die sie nicht nutzen.

In Laravel: Statt eines monolithischen Interface gibt es viele kleine Contracts (z.B. Cache, Queue, Mail). Du implementierst nur das, was du brauchst.

D — Dependency Inversion Principle

Module sollten von Abstraktionen abhängen, nicht von konkreten Implementierungen.

In Laravel: Das gesamte Framework basiert auf diesem Prinzip. Statt direkt eine MySQLConnection-Klasse zu nutzen, arbeitest du mit dem DB-Interface. Laravel entscheidet zur Laufzeit, welche konkrete Implementierung dahintersteckt. Das bringt uns zum nächsten Thema.

Service Container

Der Service Container ist das Herzstück von Laravel. Er ist ein Dependency Injection Container — ein Werkzeug, das Objekte erstellt und ihre Abhängigkeiten automatisch auflöst.

Das Problem ohne Container

Stell dir vor, dein Controller braucht einen Service, der E-Mails verschickt:

Ohne Container: Du erstellst alles selbst
// Ohne Container: Du erstellst alles selbst
class PostController
{
    public function store()
    {
        $mailer = new SmtpMailer('smtp.example.com', 587, 'user', 'pass');
        $logger = new FileLogger('/var/log/app.log');
        $notifier = new NotificationService($mailer, $logger);
        $notifier->notify('Neuer Post erstellt');
    }
}

Das ist unübersichtlich. Dein Controller muss wissen, wie der Mailer konfiguriert wird, welcher Logger verwendet wird und wie der Notifier zusammengebaut wird. Das verstößt gegen das Single Responsibility Principle.

Die Lösung mit Dependency Injection

Mit Dependency Injection: Laravel gibt dir was du brauchst
// Mit Dependency Injection: Laravel gibt dir was du brauchst
class PostController
{
    public function store(NotificationService $notifier)
    {
        $notifier->notify('Neuer Post erstellt');
    }
}

Du deklarierst in der Methodensignatur, was du brauchst (NotificationService), und Laravel kümmert sich um den Rest. Der Container weiß, wie er den Service erstellt, welche Abhängigkeiten er hat und wie sie konfiguriert sind.

Wie der Container das auflöst

  1. Laravel sieht: Die Methode store() erwartet ein NotificationService-Objekt
  2. Der Container prüft: Wurde NotificationService irgendwo registriert?
  3. Falls ja: Er erstellt die Instanz mit allen nötigen Abhängigkeiten
  4. Falls nein: Er versucht, die Klasse automatisch zu instanziieren (Auto-Resolution)

Das ist Inversion of Control — nicht dein Code entscheidet, welche Implementierung verwendet wird, sondern die Konfiguration des Containers.

Wo du das in der Praxis siehst

In den folgenden Sessions wirst du das ständig nutzen, ohne es zu merken:

Route Model Binding — Laravel injiziert das Post-Objekt automatisch
// Route Model Binding — Laravel injiziert das Post-Objekt automatisch
public function show(Post $post) { ... }

// Request-Objekt — Laravel injiziert den aktuellen Request
public function store(Request $request) { ... }

Jedes Mal, wenn du einen Type-Hint in einer Controller-Methode schreibst, nutzt du den Service Container.

Dependency Injection

Dependency Injection (DI) ist ein Muster, bei dem eine Klasse ihre Abhängigkeiten nicht selbst erstellt, sondern von außen bekommt. Laravel nutzt DI durchgehend — in Controllern, Middleware, Commands, Jobs und Service Providern.

Drei Arten von Dependency Injection

Constructor Injection — Abhängigkeiten werden über den Konstruktor übergeben:

php
class PostController extends Controller
{
    public function __construct(
        private PostRepository $posts
    ) {}

    public function index()
    {
        return view('posts.index', [
            'posts' => $this->posts->getLatest()
        ]);
    }
}

Method Injection — Abhängigkeiten werden direkt in die Methode injiziert:

php
public function store(Request $request)
{
    // Laravel injiziert das Request-Objekt automatisch
}

Interface Binding — Du programmierst gegen ein Interface, der Container liefert die Implementierung:

In einem Service Provider registrieren
// In einem Service Provider registrieren
$this->app->bind(PaymentGateway::class, StripeGateway::class);

// Im Controller nutzen
public function pay(PaymentGateway $gateway)
{
    // $gateway ist automatisch eine StripeGateway-Instanz
}

Der Vorteil: Wenn du später von Stripe zu PayPal wechselst, änderst du eine Zeile im Service Provider — nicht jeden Controller.

Design Patterns in Laravel

Laravel setzt mehrere bekannte Design Patterns ein. Hier sind die wichtigsten, die dir in der Praxis begegnen werden.

Facade

Eine Facade bietet eine statisch aussehende Syntax für Services, die im Container registriert sind:

Sieht statisch aus...
// Sieht statisch aus...
Cache::get('key');
Route::get('/url', ...);
DB::table('posts')->get();

// ...ist es aber nicht. Hinter Cache:: steckt:
app('cache')->get('key');

Eine Facade ist eine "schöne Fassade" (daher der Name) vor dem Service Container. Statt app('cache')->get('key') schreibst du Cache::get('key'). Kürzer, lesbarer, aber unter der Haube läuft dasselbe.

Der Vorteil gegenüber echten statischen Methoden: Facades sind testbar. In Tests kannst du Cache::fake() aufrufen und das Verhalten mocken.

Singleton

Ein Singleton stellt sicher, dass von einer Klasse nur eine einzige Instanz existiert. In Laravel registrierst du Singletons im Service Container:

In einem Service Provider
// In einem Service Provider
$this->app->singleton(PaymentService::class, function ($app) {
    return new PaymentService($app->make(Config::class));
});

Egal wie oft PaymentService angefordert wird — es wird immer dasselbe Objekt zurückgegeben. Laravel nutzt das intern für Datenbankverbindungen, den Logger, das Cache-System und viele andere Services.

Repository Pattern

Das Repository Pattern abstrahiert den Datenzugriff. Statt direkt Eloquent im Controller zu nutzen, geht die Abfrage über ein Repository:

Ohne Repository — direkt im Controller
// Ohne Repository — direkt im Controller
$posts = Post::where('published', true)->latest()->get();

// Mit Repository — abstrahiert
$posts = $this->postRepository->getPublished();

Laravel erzwingt dieses Pattern nicht, aber in größeren Projekten hilft es, die Datenzugriffslogik an einem Ort zu bündeln. Für unsere Schulung arbeiten wir direkt mit Eloquent — das ist für die meisten Projekte ausreichend.

Observer Pattern

Laravel nutzt Events und Listeners als Implementierung des Observer Patterns:

Ein Event wird ausgelöst
// Ein Event wird ausgelöst
event(new PostCreated($post));

// Ein Listener reagiert darauf
class SendNotification
{
    public function handle(PostCreated $event)
    {
        // E-Mail senden, Log schreiben, etc.
    }
}

Das entkoppelt Aktion und Reaktion. Der Controller weiß nicht, wer auf das Event reagiert — er löst es nur aus.

Strategy Pattern

Durch Interface Binding und den Service Container nutzt Laravel das Strategy Pattern. Verschiedene Implementierungen für dasselbe Interface — die Auswahl passiert über Konfiguration:

config/cache.php definiert den Driver
// config/cache.php definiert den Driver
'default' => env('CACHE_STORE', 'file'),

// Laravel wählt automatisch die richtige Implementierung:
// 'file' → FileStore, 'redis' → RedisStore, 'database' → DatabaseStore

Du arbeitest immer mit Cache::get() — welcher Store dahintersteckt, bestimmt die .env-Datei.

Factory Pattern

Eine Factory kapselt die Erstellung von Objekten. Statt Objekte direkt mit new zu erzeugen, delegierst du das an eine Factory-Klasse. In Laravel begegnet dir das an zwei prominenten Stellen:

Model Factories — zum Erzeugen von Testdaten:

database/factories/PostFactory.php
// database/factories/PostFactory.php

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title' => fake()->sentence(),
            'content' => fake()->paragraphs(3, true),
        ];
    }
}

Nutzung:

Einen Post erzeugen
// Einen Post erzeugen
$post = Post::factory()->create();

// 50 Posts auf einmal
$posts = Post::factory()->count(50)->create();

// Mit bestimmten Werten
$post = Post::factory()->create(['title' => 'Mein Titel']);

Model Factories sind unverzichtbar für Tests und Seeding. Statt manuell Testdaten zusammenzubauen, definierst du einmal die Regeln und lässt die Factory beliebig viele realistische Datensätze erzeugen. In unserer Schulung nutzen wir Seeder — aber in einem professionellen Projekt würden Factories die bessere Wahl sein.

Manager Pattern — Laravel nutzt Factories auch intern, um den richtigen Driver zu instanziieren. Der CacheManager ist eine Factory: Er entscheidet anhand der Konfiguration, ob ein FileStore, RedisStore oder DatabaseStore erstellt wird. Dasselbe Prinzip gilt für SessionManager, DatabaseManager und andere.

Das Factory Pattern hält deinen Code sauber: Der Konsument fragt nach einem Objekt, ohne zu wissen (oder wissen zu müssen), wie es gebaut wird.

Service Provider

Service Provider sind die zentrale Stelle, an der Laravel konfiguriert wird. Jeder Provider hat zwei Methoden:

  • register() — Bindings im Container registrieren
  • boot() — Code ausführen, nachdem alle Provider registriert sind
php
class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Services im Container registrieren
        $this->app->bind(
            PaymentGateway::class,
            StripeGateway::class
        );
    }

    public function boot(): void
    {
        // Wird ausgeführt nachdem alles registriert ist
        // Hier z.B. View Composers, Event Listener, etc.
    }
}

Laravels eigene Features (Routing, Auth, Cache, Mail) sind alle als Service Provider implementiert. Du findest sie in config/app.php unter providers. Das Framework bootstrappt sich selbst über Provider — ein eleganter, erweiterbarer Mechanismus.

Zusammenfassung

Konzept Kernidee Laravel-Umsetzung
MVC Trennung von Daten, Logik, Darstellung Models, Controllers, Blade Views
Service Container Automatische Objekterstellung und -verdrahtung app(), Type-Hints, Bindings
Dependency Injection Abhängigkeiten von außen erhalten, nicht selbst erstellen Constructor/Method Injection
Facade Lesbare statische Syntax für Container-Services Cache::, Route::, DB::
Singleton Eine Instanz pro Service $app->singleton(...)
Factory Objekterstellung kapseln Post::factory(), Manager-Klassen
Service Provider Zentrale Konfiguration und Bootstrapping register(), boot()
SOLID Fünf Prinzipien für wartbaren Code Durchzieht die gesamte Architektur

Diese Konzepte sind kein Selbstzweck. Sie machen Code wartbar, testbar und erweiterbar. In den folgenden vier Sessions wirst du sie in der Praxis erleben — auch wenn du sie nicht jedes Mal bewusst anwendest. Laravel tut das für dich.

Weiter geht es mit Session 1: Das erste sichtbare Ergebnis, wo wir den MVC-Zyklus zum ersten Mal durchlaufen.