Zum Inhalt

Beispielarbeiten · Code & Fehler

Laravel: Aufgabenverwaltung mit Migration, Eingabeprüfung und Tests

Eine kleine Laravel-Anwendung mit Datenbanktabelle, Controller, Formularprüfung und Ansicht. Geprüft mit den Laravel-Tests und mit echten HTTP-Aufrufen, dazu eine Gegenprobe mit kaputten Fassungen.

Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.

Was gebaut wurde

  • Migration, Modell, Controller und Routen im üblichen Laravel-Aufbau
  • Eingaben über eine eigene Request-Klasse geprüft, Fehler erscheinen im Formular
  • Feature-Tests mit artisan test und Aufrufe gegen den laufenden Server
  • Gegenprobe: jede absichtlich kaputte Fassung wird erkannt

laravel-anwendung-quellcode.zip herunterladen

Aktuelles Laravel auf PHP 8.3; ältere Laravel-Versionen sind nicht geprüft. Datenbank nur SQLite, MySQL und PostgreSQL liefen nicht. Eingebauter Server statt Apache oder nginx, ohne Queues, Scheduler und Redis. Anmeldung, Livewire, Inertia und der Frontend-Build sind nicht geprüft. Das Archiv enthält die eigenen Dateien, nicht den vendor-Ordner.

Wie geprüft wurde

Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):

  • artisan test 11 gruen, 0 rot
  • HTTP 29/29 gruen
  • Gegenprobe 4/4 kaputte Fassungen erkannt

Grenzen

Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:

  • Laravel 13 (aktuelles create-project) auf PHP 8.3 - aeltere Laravel-Versionen (5-10) mit eigenem Aufbau/Paketen nicht geprueft
  • Datenbank nur SQLite; MySQL/PostgreSQL nur ueber Konfiguration, nicht gelaufen
  • eingebauter Server (artisan serve), kein Apache/nginx/FPM; keine Queues/Scheduler/Broadcasting, kein Redis
  • nicht geprueft: Authentifizierung (Breeze/Jetstream/Sanctum), Livewire/Inertia/Vue-Frontend, npm/Vite-Build (Node nicht eingerichtet)
  • Pakete kommen aus Packagist (Netz noetig beim ersten Lauf); Kundenprojekte mit privaten Paketen brauchen Zugang
  • keine Arbeit auf dem Server des Kunden ohne Zugang; Ergebnis ist Quellcode + Testnachweis

Der Controller

<?php

namespace App\Http\Controllers;

use App\Http\Requests\AufgabeRequest;
use App\Models\Aufgabe;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class AufgabeController extends Controller
{
    public function index(): View
    {
        return view('aufgaben.index', ['aufgaben' => Aufgabe::orderBy('id')->get()]);
    }

    public function store(AufgabeRequest $request): RedirectResponse
    {
        Aufgabe::create($request->validated());

        return redirect()->route('aufgaben.index')->with('status', 'Gespeichert');
    }

    public function update(AufgabeRequest $request, Aufgabe $aufgabe): RedirectResponse
    {
        $aufgabe->update($request->validated());

        return redirect()->route('aufgaben.index')->with('status', 'Geaendert');
    }

    public function destroy(Aufgabe $aufgabe): RedirectResponse
    {
        $aufgabe->delete();

        return redirect()->route('aufgaben.index')->with('status', 'Geloescht');
    }
}

Die Tests

<?php

namespace Tests\Feature;

use App\Models\Aufgabe;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class AufgabenTest extends TestCase
{
    use RefreshDatabase;

    public function test_startseite_leitet_auf_liste_und_zeigt_formular_mit_token(): void
    {
        $this->get('/')->assertRedirect('/aufgaben');
        $r = $this->get('/aufgaben')->assertOk();
        $r->assertSee('name="_token"', false);
    }

    public function test_anlegen_speichert_und_zeigt_in_liste(): void
    {
        $this->post('/aufgaben', ['titel' => 'Jörg zurückrufen', 'prioritaet' => 2])
            ->assertRedirect('/aufgaben');
        $this->assertDatabaseHas('aufgaben', ['titel' => 'Jörg zurückrufen', 'prioritaet' => 2, 'erledigt' => false]);
        $this->get('/aufgaben')->assertSee('Jörg zurückrufen');
    }

    public static function ungueltige(): array
    {
        return [
            'Titel leer' => [['titel' => '', 'prioritaet' => 3], 'titel'],
            'Titel 101 Zeichen' => [['titel' => str_repeat('x', 101), 'prioritaet' => 3], 'titel'],
            'Prioritaet 0' => [['titel' => 'ok', 'prioritaet' => '0'], 'prioritaet'],
            'Prioritaet 6' => [['titel' => 'ok', 'prioritaet' => '6'], 'prioritaet'],
            'Prioritaet Text' => [['titel' => 'ok', 'prioritaet' => 'viel'], 'prioritaet'],
        ];
    }

    #[DataProvider('ungueltige')]
    public function test_validierung_lehnt_ab_und_speichert_nichts(array $daten, string $feld): void
    {
        $this->post('/aufgaben', $daten)->assertSessionHasErrors($feld);
        $this->assertDatabaseCount('aufgaben', 0);
    }

    public function test_titel_wird_escaped(): void
    {
        Aufgabe::create(['titel' => '<script>alert(1)</script>', 'prioritaet' => 3]);
        $this->get('/aufgaben')->assertDontSee('<script>alert(1)</script>', false)
            ->assertSee('&lt;script&gt;alert(1)&lt;/script&gt;', false);
    }

    public function test_aendern_und_loeschen(): void
    {
        $a = Aufgabe::create(['titel' => 'alt', 'prioritaet' => 3]);
        $this->put("/aufgaben/{$a->id}", ['titel' => 'neu', 'prioritaet' => 1, 'erledigt' => 1])->assertRedirect('/aufgaben');
        $this->assertDatabaseHas('aufgaben', ['id' => $a->id, 'titel' => 'neu', 'erledigt' => true]);
        $this->delete("/aufgaben/{$a->id}")->assertRedirect('/aufgaben');
        $this->assertDatabaseMissing('aufgaben', ['id' => $a->id]);
    }

    public function test_unbekannte_id_ist_404(): void
    {
        $this->put('/aufgaben/999', ['titel' => 'x', 'prioritaet' => 3])->assertNotFound();
        $this->delete('/aufgaben/999')->assertNotFound();
    }
}

So etwas für deinen Betrieb?

Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.

Zur Werkstatt