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.

Was ist PHP?

PHP ist eine serverseitige Skriptsprache für Webanwendungen, erstmals 1995 veröffentlicht. Sie läuft auf nahezu jedem Webserver, ist leicht zu erlernen und treibt rund 74 % aller Websites mit bekannter Server-Technologie an — darunter WordPress, Wikipedia und Facebook. Die aktuelle Version ist PHP 8.3.

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
  • bool — true 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\RegisterController ⇒ app/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.

Häufige Fragen zu PHP

Wofür wird PHP verwendet?

PHP wird hauptsächlich für serverseitige Webentwicklung eingesetzt: dynamische Webseiten, Content-Management-Systeme wie WordPress, E-Commerce-Shops (Shopware, Magento), REST-APIs und Webanwendungen mit Frameworks wie Laravel oder Symfony.

Ist PHP noch zeitgemäß?

Ja. PHP 8.x bietet strikte Typisierung, Named Arguments, Enums, Readonly-Properties und JIT-Kompilierung. Die Sprache treibt den größten Teil des Webs an und wird aktiv weiterentwickelt. Für neue Projekte ist moderne PHP mit Laravel oder Symfony eine solide Wahl.

Was ist der Unterschied zwischen PHP und JavaScript?

PHP läuft serverseitig und erzeugt HTML, bevor es zum Browser gelangt. JavaScript läuft (klassisch) im Browser und steuert Interaktionen auf der Seite. Mit Node.js läuft JavaScript auch serverseitig — PHP bleibt aber im Webhosting-Bereich günstiger und einfacher deploybar.

Welche PHP-Version soll ich nutzen?

Für neue Projekte PHP 8.3 (aktuellstes Stable Release). PHP 8.2 erhält bis Dezember 2026 Security-Updates, PHP 8.3 bis November 2027. PHP 8.1 und älter sollten migriert werden.

Wie installiere ich PHP lokal?

Unter macOS mit Homebrew: brew install php. Unter Linux mit APT: sudo apt install php8.3. Für die lokale Entwicklung empfehlen sich XAMPP (Windows/Mac), Laravel Herd (macOS/Windows) oder DDEV.