Start · Sprachen · PHP
PHP

PHP

8.4 · seit 1995 · Web

Die dominante Sprache des Web-Backends — pragmatisch, ausgereift und mit einem Ökosystem, das von WordPress bis zu Enterprise-Frameworks wie Laravel und Symfony reicht.

objektorientiert imperativ funktional (teilweise) dynamisch typisiert

// Steckbrief

Dateiendung
.php
Paket-Mgr
Composer
Offiziell
php.net
Docs
php.net/manual/de/
📚
4845 Einträge in der PHP-Referenz
Jede Funktion, Klasse und jedes Sprach-Konzept auf einer eigenen Seite — mit Signatur, Parametern, Beispielen und Fallstricken.

Übersicht

PHP (rekursives Akronym für „PHP: Hypertext Preprocessor") ist eine serverseitige Skriptsprache, die speziell für die Webentwicklung entworfen wurde. Seit 1995 hat sie sich vom simplen Template-System zu einer vollwertigen, objektorientierten Sprache mit strenger Typisierung, JIT-Compiler und einem der reichhaltigsten Ökosysteme der Web-Welt entwickelt.

PHP läuft heute auf über 75 % aller Websites, deren Serversprache bekannt ist — WordPress, Wikipedia, Slack, Etsy und Facebook nutzen (oder nutzten) PHP im Kern. Moderne Frameworks wie Laravel und Symfony haben die Sprache im Enterprise-Umfeld etabliert.

Mit PHP 8 ist die Sprache endgültig im modernen Zeitalter angekommen: readonly-Klassen, Enums, Union- und Intersection-Types, Named Arguments, Fibers für async I/O und ein Just-in-Time-Compiler machen aus dem einstigen „Skript-Werkzeug" eine seriöse Backend-Sprache.

Historie

1994 schrieb der dänisch-kanadische Entwickler Rasmus Lerdorf in C eine kleine Sammlung von CGI-Skripten, um Zugriffe auf seinen Online-Lebenslauf zu zählen. Er nannte sie Personal Home Page Tools (PHP Tools) — der Ursprung des Namens.

Mit PHP 3 (1998) übernahmen Andi Gutmans und Zeev Suraski die Weiterentwicklung und schrieben den Kern neu. PHP 4 (2000) brachte die Zend Engine, PHP 5 (2004) echte Objektorientierung, PHP 7 (2015) dramatische Performance-Sprünge, und PHP 8 (2020) den JIT-Compiler sowie moderne Sprachfeatures.

Aktuelle Version: PHP 8.4 (November 2024) — mit Property Hooks, asymmetrischer Sichtbarkeit und weiter verbesserter Performance.

Warum PHP?

  • Web-nativ: PHP ist von Grund auf für HTTP gebaut. Kein Webserver-Setup, keine Runtime-Bootstrap-Zeit — jedes Skript ist stateless und wird pro Request frisch geladen. Perfekt für skalierbare Webanwendungen.
  • Ökosystem: Über 400.000 Pakete auf Packagist, ausgereifte Frameworks (Laravel, Symfony, Slim), CMSe (WordPress, Drupal), Shop-Systeme (Shopware, Magento).
  • Hosting: Bei jedem Webhoster verfügbar — von 3-€-Shared-Hosting bis zu spezialisierten PHP-Cloud-Anbietern wie Laravel Forge oder Ploi.
  • Moderne Syntax: PHP 8+ bietet strikte Typisierung, Enums, Attributes, Match-Expressions, Named Arguments und Readonly-Klassen — nichts mehr, wofür man sich schämen müsste.
  • Community: Riesige, deutschsprachige Community. Antworten auf Stack Overflow, Konferenzen (International PHP Conference, PHPUnconference), YouTube-Kanäle wie Codecourse oder Laracasts.

Installation

Für die lokale Entwicklung unter macOS empfehlen wir Laravel Herd — ein Ein-Klick-Installer, der PHP, Nginx, MySQL, Redis, Node und alle gängigen PHP-Versionen mitbringt. Kein Docker, keine brew install-Orgie.

macOS via Herd

brew install --cask herdapp
# oder direkt von herd.laravel.com laden

macOS via Homebrew (klassisch)

brew install php
php --version   # PHP 8.4.x

Ubuntu / Debian

sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
sudo apt install -y php8.4 php8.4-cli php8.4-fpm php8.4-mysql php8.4-mbstring php8.4-xml

Windows

Herd ist auch für Windows verfügbar. Alternativ: XAMPP oder Laragon.

Composer (Paket-Manager) installieren

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version

Hello World

Das minimalste vollständige Programm in PHP:

<?php

declare(strict_types=1);

echo "Hallo, programmierung.net!\n";

Variablen & Datentypen

↗ Eigene Seite

PHP kennt acht primitive Datentypen. Seit PHP 7 lässt sich für jeden davon eine Typ-Deklaration erzwingen — nutze das immer.

Variablen in PHP beginnen mit $ und sind case-sensitive. PHP ist dynamisch typisiert, aber seit Version 7 lassen sich Typen für Funktionsparameter, Rückgabewerte und Properties strikt deklarieren.

Primitive Typen

  • string — Text, in einfachen oder doppelten Anführungszeichen
  • int — Ganzzahl (64 Bit auf modernen Systemen)
  • float — Fließkommazahl
  • booltrue oder false
  • array — indiziert oder assoziativ
  • object — Instanz einer Klasse
  • null — kein Wert
  • resource — externe Ressource (Dateihandle, DB-Verbindung)

Beispiel mit strict types

<?php

declare(strict_types=1);

$name    = 'PHP';           // string
$version = 8.4;              // float
$rank    = 1;                // int
$aktiv   = true;             // bool
$tags    = ['web', 'oop'];  // array
$leer    = null;             // null

// Typ prüfen
var_dump(gettype($version));  // string(6) "double"
var_dump(is_int($rank));      // bool(true)

String-Interpolation

In doppelten Anführungszeichen werden Variablen ersetzt, in einfachen nicht:

$name = 'Anna';
echo "Hallo, $name!";        // Hallo, Anna!
echo 'Hallo, $name!';        // Hallo, $name!
echo "Hallo, {$user->name}!"; // Für Objekt-/Array-Zugriffe: geschweifte Klammern

Type Juggling — die Falle

PHP konvertiert Typen automatisch beim Vergleich. Das ist bequem, aber gefährlich. Nutze immer === (strict comparison):

var_dump(0 == 'foo');   // bool(false) seit PHP 8, bool(true) davor (!)
var_dump(0 === 'foo');  // bool(false) — immer

var_dump('1' == 1);     // bool(true)  — Type Juggling
var_dump('1' === 1);    // bool(false) — strict

Operatoren

↗ Eigene Seite

PHP kennt alle üblichen Operatoren — plus einige Eigenheiten wie den Null-Coalescing-Operator und den Spaceship-Operator.

Arithmetik & Zuweisung

$sum  = 5 + 3;    // Addition
$mod  = 10 % 3;   // Modulo → 1
$pow  = 2 ** 8;   // Potenz → 256

$x = 10;
$x += 5;   // 15
$x -= 3;   // 12
$x *= 2;   // 24
$x **= 2;  // 576

String-Konkatenation mit Punkt

$greeting = 'Hallo, ' . $name . '!';
$log      = 'User ';
$log     .= $name;   // Anhängen mit .=

Vergleich — == vs. ===

// Immer strict verwenden!
if ($status === 'active') { ... }
if ($count !== 0) { ... }

Null-Coalescing (??)

Gibt den ersten nicht-null Wert zurück. Ersetzt Rufe wie isset($x) ? $x : 'default':

$username = $_GET['user'] ?? 'Gast';
$config   = $custom ?? $default ?? 'fallback';

// Zuweisung nur wenn null:
$cache['key'] ??= expensiveComputation();

Nullsafe-Operator (?->, PHP 8.0+)

Kettet Methodenaufrufe sicher, wenn ein Zwischenwert null sein könnte:

// Vorher: verschachtelte Null-Checks
$country = null;
if ($user !== null) {
    $address = $user->getAddress();
    if ($address !== null) {
        $country = $address->getCountry();
    }
}

// Jetzt:
$country = $user?->getAddress()?->getCountry();

Spaceship (<=>)

Gibt -1, 0 oder 1 zurück — praktisch beim Sortieren:

usort($items, fn($a, $b) => $a->price <=> $b->price);

Kontrollstrukturen

↗ Eigene Seite

if/else, switch und der moderne match-Ausdruck von PHP 8.

if / elseif / else

if ($alter < 18) {
    echo 'minderjährig';
} elseif ($alter < 65) {
    echo 'erwerbsfähig';
} else {
    echo 'im Ruhestand';
}

Ternärer Operator & Kurzform

$status = $isPaid ? 'bezahlt' : 'offen';

// Elvis-Operator (Kurzform):
$name = $user->name ?: 'Anonym';   // wenn $user->name falsy → 'Anonym'

switch (klassisch)

switch ($rolle) {
    case 'admin':
    case 'moderator':
        $level = 10;
        break;
    case 'user':
        $level = 1;
        break;
    default:
        $level = 0;
}

match (PHP 8.0+, moderner & sicherer)

match ist ein Ausdruck — er gibt einen Wert zurück. Er nutzt strict comparison (===), kein fall-through, und wirft einen Fehler bei nicht gematchten Werten:

$level = match ($rolle) {
    'admin', 'moderator' => 10,
    'user'               => 1,
    'guest'              => 0,
    default              => throw new InvalidArgumentException("Unbekannte Rolle: $rolle"),
};

Faustregel: Nutze match statt switch. Es ist prägnanter, typ-sicher und funktional.

Schleifen

↗ Eigene Seite

for, foreach, while, do-while und Generatoren für Memory-effiziente Iteration.

for

for ($i = 0; $i < 10; $i++) {
    echo $i;
}

foreach — die häufigste Schleife in PHP

$users = ['Anna', 'Ben', 'Clara'];

foreach ($users as $user) {
    echo $user;
}

// Mit Index:
foreach ($users as $i => $user) {
    echo "$i: $user";
}

// Assoziatives Array:
$prices = ['brot' => 3.50, 'milch' => 1.20];
foreach ($prices as $produkt => $preis) {
    echo "$produkt kostet $preis €\n";
}

while & do-while

while ($queue->hasJobs()) {
    $queue->processNext();
}

do {
    $eingabe = readline('Fortfahren? (j/n) ');
} while ($eingabe !== 'n');

break & continue

foreach ($items as $item) {
    if ($item->deleted) continue;      // überspringen
    if ($item->critical) break;        // Schleife verlassen
    process($item);
}

Generatoren mit yield — Memory-effizient

Wenn du Millionen Zeilen aus einer Datei liest, ist ein Array-Return ein Speicher-Problem. Generatoren produzieren Werte on-demand:

function readCsv(string $path): Generator {
    $handle = fopen($path, 'r');
    while (($row = fgetcsv($handle)) !== false) {
        yield $row;
    }
    fclose($handle);
}

foreach (readCsv('gross.csv') as $row) {
    // verarbeitet 1 Zeile nach der anderen, egal wie groß die Datei
}

Funktionen

↗ Eigene Seite

Klassische Funktionen, Arrow-Functions, Named Arguments und Typ-Deklarationen — alles was du für saubere APIs brauchst.

Grundform mit Typ-Deklaration

function berechneMwst(float $netto, float $satz = 0.19): float {
    return $netto * $satz;
}

echo berechneMwst(100.00);           // 19
echo berechneMwst(100.00, 0.07);     // 7

Named Arguments (PHP 8.0+)

Erlauben Argumente per Namen zu übergeben. Perfekt bei Funktionen mit vielen optionalen Parametern:

function createUser(
    string $email,
    string $name,
    bool $sendWelcome = true,
    ?string $referral = null,
    string $locale = 'de',
): User {
    // ...
}

// Statt Reihenfolge zu merken:
createUser(
    email: 'anna@example.com',
    name: 'Anna',
    locale: 'en',
    sendWelcome: false,
);

Variadic Functions (...$args)

function summe(int ...$zahlen): int {
    return array_sum($zahlen);
}

echo summe(1, 2, 3, 4);   // 10

// Unpacking beim Aufruf:
$werte = [1, 2, 3];
echo summe(...$werte);    // 6

Arrow-Functions (PHP 7.4+)

Kurz-Syntax für einzeilige Callbacks. Der äußere Scope ist automatisch verfügbar (kein use()):

$faktor = 1.19;

$brutto = array_map(fn(float $netto) => $netto * $faktor, $preise);

// Klassische Closure zum Vergleich:
$brutto = array_map(
    function (float $netto) use ($faktor) {
        return $netto * $faktor;
    },
    $preise
);

Union-, Nullable- und Intersection-Types

function findUser(int|string $identifier): ?User {
    // Akzeptiert int ODER string, gibt User oder null zurück
}

// Intersection (PHP 8.1+): Muss beide Interfaces implementieren
function process(Countable&Iterator $items): void { ... }

Arrays & Kollektionen

↗ Eigene Seite

Arrays sind in PHP die Universalstruktur: Liste, Dictionary, Stack, Queue — alles in einem.

Array erstellen

// Indiziert (Liste)
$sprachen = ['PHP', 'Python', 'Go'];

// Assoziativ (Dictionary)
$user = [
    'name'  => 'Anna',
    'alter' => 28,
    'admin' => true,
];

// Verschachtelt
$db = [
    'host'    => 'localhost',
    'options' => ['charset' => 'utf8mb4', 'timeout' => 5],
];

Zugriff, Anfügen, Löschen

echo $sprachen[0];             // PHP
echo $user['name'];            // Anna

$sprachen[]         = 'Rust';  // ans Ende anhängen
$user['locale']     = 'de';    // Key hinzufügen
unset($user['admin']);         // Key entfernen

array_push($sprachen, 'Zig');  // funktional identisch zu $arr[] = ...

Wichtige Array-Funktionen

// Prüfen
in_array('PHP', $sprachen);    // bool
array_key_exists('name', $user);
count($sprachen);

// Transformieren
$upper = array_map(strtoupper(...), $sprachen);       // ['PHP','PYTHON','GO','RUST']
$long  = array_filter($sprachen, fn($s) => strlen($s) > 2);
$sum   = array_reduce([1,2,3,4], fn($acc,$n) => $acc + $n, 0);   // 10

// Sortieren (verändern das Array direkt)
sort($sprachen);              // aufsteigend, neue Keys
rsort($sprachen);             // absteigend
usort($users, fn($a,$b) => $a['alter'] <=> $b['alter']);

// Kombinieren
$alle = array_merge($a, $b);
$kombi = [...$a, ...$b];      // Spread-Operator (PHP 7.4+)

Destructuring

// Liste
[$erster, $zweiter] = $sprachen;

// Assoziativ (PHP 7.1+)
['name' => $name, 'alter' => $alter] = $user;

// In foreach
foreach ($users as ['name' => $name, 'email' => $email]) {
    // ...
}

PHP hat die vielleicht umfassendste String-Bibliothek aller Sprachen. Achte auf UTF-8 und nutze die mb_*-Funktionen.

String-Operationen

$s = 'programmierung.net';

strlen($s);              // 18 (Byte-Länge, nicht Zeichen!)
mb_strlen($s);           // 18 (Zeichen-Länge, UTF-8-sicher)
strtoupper($s);          // 'PROGRAMMIERUNG.NET'
str_replace('.net', '.de', $s);
str_contains($s, 'net'); // true (PHP 8.0+)
str_starts_with($s, 'prog');
str_ends_with($s, '.net');
substr($s, 0, 12);       // 'programmieru'
explode('.', $s);        // ['programmierung', 'net']
implode(', ', $arr);
trim("  hallo  ");       // 'hallo'

Vorsicht: UTF-8

strlen() zählt Bytes, nicht Zeichen. Für internationalen Text immer mb_*-Funktionen nutzen:

$s = 'Größe';
strlen($s);       // 6 (weil ö = 2 Bytes in UTF-8)
mb_strlen($s);    // 5 (korrekt)

Heredoc & Nowdoc

Für mehrzeilige Strings mit oder ohne Variablen-Interpolation:

$name = 'Anna';

// Heredoc — mit Interpolation (wie doppelte Anführungszeichen)
$mail = <<<MAIL
Hallo $name,

willkommen auf programmierung.net.

Gruß
MAIL;

// Nowdoc — OHNE Interpolation (wie einfache Anführungszeichen)
$sql = <<<'SQL'
SELECT * FROM users WHERE email = ?
SQL;

Formatierung mit sprintf

$preis = 19.99;
echo sprintf('%.2f €', $preis);           // 19.99 €
echo sprintf('%05d', 42);                 // 00042
echo sprintf('%s hat %d Punkte', 'Anna', 100);

Objektorientierung

↗ Eigene Seite

PHP hat ein vollwertiges OOP-System mit Klassen, Interfaces, Traits, abstrakten Klassen und seit PHP 8.2 auch readonly-Klassen.

Klasse mit Konstruktor-Property-Promotion (PHP 8.0+)

<?php

declare(strict_types=1);

final class Product
{
    public function __construct(
        public readonly string $name,
        public readonly float $price,
        private int $stock = 0,
    ) {}

    public function increaseStock(int $amount): void
    {
        $this->stock += $amount;
    }

    public function isAvailable(): bool
    {
        return $this->stock > 0;
    }
}

$p = new Product('T-Shirt', 24.90);
$p->increaseStock(50);
echo $p->name;      // T-Shirt (public readonly)
// $p->name = 'x'; // Error: Cannot modify readonly property

Vererbung

class Animal
{
    public function __construct(public string $name) {}

    public function speak(): string
    {
        return "$this->name macht Geräusche";
    }
}

class Dog extends Animal
{
    public function speak(): string
    {
        return "$this->name bellt";
    }
}

$d = new Dog('Bello');
echo $d->speak();   // Bello bellt

Interfaces

interface PaymentGateway
{
    public function charge(int $amountInCents, string $currency): bool;
    public function refund(string $transactionId): bool;
}

final class StripeGateway implements PaymentGateway
{
    public function charge(int $amountInCents, string $currency): bool { /* ... */ }
    public function refund(string $transactionId): bool { /* ... */ }
}

Abstrakte Klassen

Können nicht instanziiert werden. Erzwingen, dass Kinder bestimmte Methoden implementieren, teilen aber gemeinsamen Code:

abstract class Report
{
    public function generate(): string
    {
        $data = $this->collect();
        return $this->format($data);
    }

    abstract protected function collect(): array;
    abstract protected function format(array $data): string;
}

Traits — horizontale Wiederverwendung

PHP hat keine Mehrfachvererbung. Traits sind der Ersatz: Methoden-Bausteine, die in mehrere Klassen eingemischt werden:

trait Loggable
{
    public function log(string $msg): void
    {
        error_log('[' . static::class . '] ' . $msg);
    }
}

class User { use Loggable; }
class Order { use Loggable; }

(new User)->log('Registriert');    // [User] Registriert

Statische Methoden & Properties

class Config
{
    private static array $data = [];

    public static function set(string $key, mixed $value): void
    {
        self::$data[$key] = $value;
    }

    public static function get(string $key): mixed
    {
        return self::$data[$key] ?? null;
    }
}

Config::set('app.name', 'programmierung.net');
echo Config::get('app.name');

Readonly-Klassen (PHP 8.2+)

Alle Properties sind implizit readonly. Ideal für Value Objects und DTOs:

final readonly class Money
{
    public function __construct(
        public int $cents,
        public string $currency,
    ) {}

    public function plus(Money $other): Money
    {
        return new Money($this->cents + $other->cents, $this->currency);
    }
}

Namespaces & Autoloading

↗ Eigene Seite

Namespaces trennen Code in logische Gruppen. PSR-4-Autoloading via Composer sorgt dafür, dass du nie wieder require_once schreibst.

Namespace deklarieren

// app/Models/User.php
<?php

namespace App\Models;

class User
{
    // ...
}

Namespace nutzen

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Services\Mailer as MailService;
use Illuminate\Support\Str;

class RegisterController
{
    public function store(): void
    {
        $user = User::create([...]);
        MailService::welcome($user);
    }
}

PSR-4-Autoloading mit Composer

In der composer.json mappst du Namespaces auf Verzeichnisse:

{
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Seeders\\": "database/seeders/"
        }
    }
}

Nach composer dump-autoload findet PHP jede Klasse automatisch. App\Http\Controllers\RegisterControllerapp/Http/Controllers/RegisterController.php.

Aliasing bei Namenskollisionen

use App\Models\User;
use App\Services\User as UserService;   // Alias
use function Str\slug;                   // Funktion importieren
use const HTTP\STATUS_OK;                // Konstante importieren

Enums (PHP 8.1+)

↗ Eigene Seite

Enums sind typsichere Wertelisten. Sie ersetzen die alte Konvention, Konstanten in Klassen zu bündeln.

Basic Enum

enum Status
{
    case Pending;
    case Active;
    case Suspended;
    case Deleted;
}

function updateUser(User $u, Status $newStatus): void
{
    $u->status = $newStatus;
}

updateUser($user, Status::Active);
// updateUser($user, 'active');  // TypeError

Backed Enum — mit skalarem Wert

Für Serialisierung (z. B. in DB oder JSON) brauchst du einen zugrundeliegenden Wert:

enum Status: string
{
    case Pending   = 'pending';
    case Active    = 'active';
    case Suspended = 'suspended';
    case Deleted   = 'deleted';
}

// Wert lesen
Status::Active->value;              // 'active'

// Aus Wert erzeugen
Status::from('active');             // Status::Active
Status::tryFrom('invalid');         // null (statt Exception)

// Alle Cases
foreach (Status::cases() as $s) {
    echo $s->name . ' = ' . $s->value . "\n";
}

Methoden in Enums

enum Status: string
{
    case Pending   = 'pending';
    case Active    = 'active';
    case Suspended = 'suspended';

    public function label(): string
    {
        return match ($this) {
            self::Pending   => 'Wartet auf Bestätigung',
            self::Active    => 'Aktiv',
            self::Suspended => 'Gesperrt',
        };
    }

    public function color(): string
    {
        return match ($this) {
            self::Pending   => 'yellow',
            self::Active    => 'green',
            self::Suspended => 'red',
        };
    }
}

echo Status::Active->label();   // Aktiv

Attributes (PHP 8.0+)

↗ Eigene Seite

Attributes sind strukturierte Metadaten für Klassen, Methoden und Properties. Sie ersetzen DocBlock-Annotationen.

Attributes stehen in eckigen Klammern mit #[Attribute]-Syntax und werden per Reflection ausgelesen:

Ein einfaches Attribute definieren

#[\Attribute(\Attribute::TARGET_METHOD)]
class Route
{
    public function __construct(
        public string $method,
        public string $path,
    ) {}
}

Verwendung an einer Methode

class UserController
{
    #[Route(method: 'GET', path: '/users')]
    public function index(): array
    {
        return User::all();
    }

    #[Route(method: 'POST', path: '/users')]
    public function store(array $data): User
    {
        return User::create($data);
    }
}

Attribute per Reflection auslesen

$reflection = new ReflectionClass(UserController::class);

foreach ($reflection->getMethods() as $method) {
    foreach ($method->getAttributes(Route::class) as $attr) {
        $route = $attr->newInstance();
        echo "{$route->method} {$route->path} → {$method->name}\n";
    }
}

// GET /users → index
// POST /users → store

Praxisbeispiele in freier Wildbahn: Symfony Routing, Doctrine ORM, PHPUnit-Test-Attribute (#[Test], #[DataProvider]), Laravel Route Attributes.

Fehlerbehandlung & Exceptions

↗ Eigene Seite

PHP wirft für Fehler Exceptions. Fange nur was du behandeln kannst — den Rest den Framework-Handler übernehmen lassen.

try / catch / finally

try {
    $response = $api->fetchUser($id);
} catch (NetworkException $e) {
    logger()->warning('Netzwerkfehler: ' . $e->getMessage());
    return null;
} catch (AuthException $e) {
    logger()->error('Authentifizierung fehlgeschlagen', ['token' => '***']);
    throw $e;   // weiterreichen
} finally {
    $api->disconnect();   // wird immer ausgeführt
}

Mehrere Exception-Typen in einem catch (PHP 8.0+)

try {
    // ...
} catch (NotFoundException | ValidationException $e) {
    return response()->json(['error' => $e->getMessage()], 400);
}

Eigene Exceptions

final class InsufficientFundsException extends \DomainException
{
    public static function forAmount(int $requested, int $available): self
    {
        return new self("Fehlbetrag: $requested Cent angefragt, nur $available verfügbar.");
    }
}

// Aufruf:
throw InsufficientFundsException::forAmount(5000, 2000);

Exceptions als Ausdruck werfen (PHP 8.0+)

throw ist seit PHP 8 ein Ausdruck — nutzbar in ternären Operatoren, match-Blöcken und Null-Coalescing:

$user = User::find($id) ?? throw new NotFoundException("User $id");
$role = match ($input) {
    'admin', 'user' => $input,
    default => throw new InvalidArgumentException("Ungültige Rolle: $input"),
};

Faustregel

  • Werfe eine präzise Exception (nicht \Exception).
  • Fange nur, wenn du sinnvoll darauf reagieren kannst.
  • Nutze DomainException für Business-Fehler, RuntimeException für Umgebungsfehler.
  • Logge inkl. Kontext, aber niemals Passwörter, Tokens, PII.

Composer & Pakete

↗ Eigene Seite

Composer ist der de-facto Standard-Paketmanager für PHP. Ohne ihn kein modernes PHP-Projekt.

Neues Projekt starten

composer init                # interaktiv
composer create-project laravel/laravel my-app   # Skeleton für Laravel

Paket installieren

composer require guzzlehttp/guzzle
composer require --dev pestphp/pest
composer require symfony/console:^7.0    # Version pinnen

composer.json — die wichtigsten Felder

{
    "name": "reisener/programmierung",
    "type": "project",
    "require": {
        "php": "^8.2",
        "laravel/framework": "^12.0"
    },
    "require-dev": {
        "pestphp/pest": "^4.1"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "test": "pest",
        "cs":   "pint"
    }
}

Häufige Befehle

composer install                       # Alle Abhängigkeiten aus composer.lock installieren
composer update                        # composer.lock aktualisieren
composer update laravel/framework      # Nur ein Paket updaten
composer remove some/package
composer dump-autoload                 # Autoloader neu aufbauen
composer show --outdated               # Was ist veraltet?
composer audit                         # Security-Advisories prüfen
composer run test                      # Custom Script

composer.lock committen — immer!

Die composer.lock pinnt exakte Versionen. Sie muss ins Git — nur so ist eure Umgebung reproduzierbar. Ohne Lock-File installiert jeder Entwickler potenziell andere Versionen.

Best Practices

↗ Eigene Seite

Was in Production wirklich zählt — jenseits von „funktioniert". Diese Regeln machen den Unterschied zwischen Hobby-Code und Enterprise-Code.

1. declare(strict_types=1) in jeder Datei

Erzwingt Typ-Prüfung ohne Silent Conversion. Kein '5' das plötzlich zu int wird.

<?php

declare(strict_types=1);   // erste Zeile nach dem <?php-Tag

2. Immer ===, niemals ==

Type Juggling führt zu Bugs, die niemand mehr nachvollzieht. Strict-Comparison ist immer richtig.

3. Typ-Deklarationen überall

// Nein
function f($a, $b) { return $a + $b; }

// Ja
function summe(int $a, int $b): int {
    return $a + $b;
}

4. readonly für Value Objects & DTOs

Unveränderliche Objekte eliminieren eine ganze Kategorie von Bugs. Ab PHP 8.2 auch als Klassen-Modifier.

final readonly class Address
{
    public function __construct(
        public string $street,
        public string $city,
        public string $zip,
        public string $country,
    ) {}
}

5. PSR-12 als Code Style

Der offizielle Coding-Standard. Nutze Laravel Pint oder PHP-CS-Fixer, um ihn automatisch anzuwenden.

6. Composer statt require_once

Nutze PSR-4-Autoloading. Manuelle require-Ketten sind ein Signal für schlechten Code.

7. Niemals User-Input direkt in SQL

// Nein — SQL-Injection!
$pdo->query("SELECT * FROM users WHERE id = " . $_GET['id']);

// Ja
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$_GET['id']]);

8. Ausgabe immer escapen

// Nein — XSS!
echo $userInput;

// Ja
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// In Twig/Blade automatisch durch {{ }}

9. Fehler NIE mit @ unterdrücken

// Nein — verbirgt echte Bugs
@file_get_contents($url);

// Ja — explizit prüfen
$content = file_get_contents($url);
if ($content === false) {
    throw new RuntimeException("Konnte $url nicht laden");
}

10. Secrets aus .env, nicht aus Code

// Nein
$apiKey = 'sk_live_abc123...';

// Ja
$apiKey = $_ENV['STRIPE_SECRET'] ?? throw new RuntimeException('STRIPE_SECRET fehlt');

11. Composer-Audit im CI-Pipeline

composer audit --format=json

12. Testing ist nicht optional

Nutze Pest oder PHPUnit. Ohne Tests wirst du dich in 6 Monaten fürchten, deinen eigenen Code zu ändern.

Code-Snippets

Kopierfertige Rezepte für den Alltag — mit Erklärung und erwartetem Output.

JSON & Serialisierung

JSON in ein PHP-Array wandeln und umgekehrt. Immer mit JSON_THROW_ON_ERROR, damit fehlerhaftes JSON eine Exception wirft statt still null zurückzugeben.

<?php

declare(strict_types=1);

// Array → JSON
$data = ['name' => 'Anna', 'roles' => ['admin', 'editor']];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);

// JSON → Array
$input = '{"user":"Ben","alter":32}';
$parsed = json_decode($input, associative: true, flags: JSON_THROW_ON_ERROR);

echo $parsed['user']; // Ben
Ben

HTTP & Web

Ein GET-Request an eine JSON-API. Für produktiven Code besser Guzzle nutzen; für Skripte reicht cURL.

<?php

declare(strict_types=1);

$ch = curl_init('https://api.github.com/repos/laravel/laravel');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Accept: application/vnd.github+json',
        'User-Agent: programmierung.net',
    ],
]);

$response = curl_exec($ch);
$status   = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("HTTP $status");
}

$repo = json_decode($response, true, flags: JSON_THROW_ON_ERROR);
echo $repo['full_name'] . ' — ' . $repo['stargazers_count'] . ' Stars';
laravel/laravel — 79000 Stars

Ein- & Ausgabe

Text-Datei komplett laden, zeilenweise iterieren oder atomar schreiben. Für große Dateien besser zeilenweise mit fgets.

<?php

declare(strict_types=1);

// Komplett lesen
$content = file_get_contents(__DIR__ . '/config.txt');

// Zeilenweise (Memory-schonend)
$handle = fopen(__DIR__ . '/big.log', 'r');
while (($line = fgets($handle)) !== false) {
    processLine(trim($line));
}
fclose($handle);

// Atomar schreiben (kein halbfertiger Zustand)
$tmp = tempnam(sys_get_temp_dir(), 'data');
file_put_contents($tmp, $content);
rename($tmp, __DIR__ . '/data.txt');

Datum & Zeit

DateTimeImmutable ist der moderne Weg. Vergiss date() und globale Timezones.

<?php

declare(strict_types=1);

// Jetzt
$now = new DateTimeImmutable('now', new DateTimeZone('Europe/Berlin'));

echo $now->format('d.m.Y H:i');   // 18.09.2026 09:42

// Parsen
$geburt = DateTimeImmutable::createFromFormat('Y-m-d', '1990-04-15');

// Rechnen (immer neues Objekt zurück, nie mutiert)
$morgen  = $now->modify('+1 day');
$diff    = $now->diff($geburt);
echo $diff->y . ' Jahre alt';
35 Jahre alt

Krypto & Passwörter

Niemals MD5, SHA1 oder gar Klartext. password_hash nutzt bcrypt oder Argon2 mit sicherem Salt.

<?php

declare(strict_types=1);

// Beim Registrieren
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
// In DB speichern

// Beim Login
$stored = fetchHashFromDb($username);

if (password_verify($input, $stored)) {
    // Optional: Hash neu berechnen wenn Cost gestiegen
    if (password_needs_rehash($stored, PASSWORD_DEFAULT)) {
        $newHash = password_hash($input, PASSWORD_DEFAULT);
        updateHashInDb($username, $newHash);
    }
    // Login erfolgreich
} else {
    // Login abgelehnt — generische Fehlermeldung
}

Für Session-Tokens, Reset-Links, API-Keys. random_bytes nutzt CSPRNG des Betriebssystems.

<?php

declare(strict_types=1);

// 32 Byte = 256 Bit = 64 Hex-Zeichen
$token = bin2hex(random_bytes(32));

// URL-safe Base64 (kürzer)
$urlToken = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');

// Zufallszahl in Bereich
$number = random_int(1000, 9999);

echo $token;      // 64 Hex-Zeichen
echo $urlToken;   // 32 URL-safe Zeichen
echo $number;     // z. B. 4218

Reguläre Ausdrücke

preg_match für Ja/Nein, preg_match_all für alle Treffer. Delimiter üblich / oder #.

<?php

declare(strict_types=1);

$text = 'Kontakt: anna@example.com, ben@site.de';

// Alle E-Mails extrahieren
if (preg_match_all('/[\w.+-]+@[\w.-]+\.[a-z]{2,}/i', $text, $matches)) {
    foreach ($matches[0] as $email) {
        echo $email . "\n";
    }
}

// Mit Named Groups
if (preg_match('#(?<jahr>\d{4})-(?<monat>\d{2})-(?<tag>\d{2})#', '2026-09-18', $m)) {
    echo "{$m['tag']}.{$m['monat']}.{$m['jahr']}";
}

// Ersetzen mit Callback
$masked = preg_replace_callback(
    '/\b(\d{3})(\d+)\b/',
    fn($m) => $m[1] . str_repeat('*', strlen($m[2])),
    'Karte: 1234567890'
);
anna@example.com

Datenbank

PDO ist die vanilla-PHP-Datenbankschicht. Prepared Statements sind Pflicht — keine Ausnahme.

<?php

declare(strict_types=1);

$pdo = new PDO(
    'mysql:host=127.0.0.1;dbname=programmierung;charset=utf8mb4',
    'root',
    '',
    [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]
);

// Named Parameters
$stmt = $pdo->prepare('SELECT id, name FROM languages WHERE is_published = :pub AND category = :cat');
$stmt->execute([':pub' => 1, ':cat' => 'web']);

foreach ($stmt->fetchAll() as $row) {
    echo "{$row['id']}: {$row['name']}\n";
}

// Insert
$stmt = $pdo->prepare('INSERT INTO logs (level, message, created_at) VALUES (?, ?, NOW())');
$stmt->execute(['info', 'System gestartet']);
$id = (int) $pdo->lastInsertId();

Umgebung & Konfig

Secrets gehören in .env, nicht in den Code. In Laravel/Symfony schon eingebaut — Vanilla-PHP mit vlucas/phpdotenv.

<?php

declare(strict_types=1);

// Mit vlucas/phpdotenv (composer require vlucas/phpdotenv)
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

// Pflicht-Werte erzwingen
$dotenv->required(['DB_HOST', 'DB_USER', 'STRIPE_SECRET']);

// Lesen
$dbHost   = $_ENV['DB_HOST'];
$stripe   = $_ENV['STRIPE_SECRET'] ?? throw new RuntimeException('STRIPE_SECRET fehlt');
$debug    = filter_var($_ENV['APP_DEBUG'] ?? 'false', FILTER_VALIDATE_BOOLEAN);

Strings & Regex

Für SEO-freundliche URLs. Umlaute transliterieren, Sonderzeichen entfernen, Wörter mit Bindestrich verbinden.

<?php

declare(strict_types=1);

function slugify(string $text): string
{
    // Umlaute vor iconv → sonst gehen sie verloren
    $map = ['ä'=>'ae','ö'=>'oe','ü'=>'ue','Ä'=>'ae','Ö'=>'oe','Ü'=>'ue','ß'=>'ss'];
    $text = strtr($text, $map);

    // Weitere Diakritika transliterieren
    $text = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);

    // Kleinbuchstaben, Sonderzeichen zu Bindestrich
    $text = strtolower($text);
    $text = preg_replace('/[^a-z0-9]+/', '-', $text);

    return trim($text, '-');
}

echo slugify('Warum PHP 8.4 großartig ist!');
// warum-php-8-4-grossartig-ist
warum-php-8-4-grossartig-ist

Referenz-Bibliothek

Jede Funktion, Klasse und jedes Sprach-Konstrukt lebt auf einer eigenen Seite — mit Signatur, Parameter-Tabelle, Beispielen und Fallstricken.