Ü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 --versionHello World
Das minimalste vollständige Programm in PHP:
<?php
declare(strict_types=1);
echo "Hallo, programmierung.net!\n";
Variablen & Datentypen
↗ Eigene SeitePHP 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ührungszeichenint— Ganzzahl (64 Bit auf modernen Systemen)float— Fließkommazahlbool—trueoderfalsearray— indiziert oder assoziativobject— Instanz einer Klassenull— kein Wertresource— 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) — strictOperatoren
↗ Eigene SeitePHP 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 Seiteif/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 Seitefor, 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 SeiteKlassische 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 SeiteArrays 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]) {
// ...
}Strings
↗ Eigene SeitePHP 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 SeitePHP 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 SeiteNamespaces 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 importierenEnums (PHP 8.1+)
↗ Eigene SeiteEnums 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(); // AktivAttributes (PHP 8.0+)
↗ Eigene SeiteAttributes 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 SeitePHP 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
DomainExceptionfür Business-Fehler,RuntimeExceptionfür Umgebungsfehler. - Logge inkl. Kontext, aber niemals Passwörter, Tokens, PII.
Composer & Pakete
↗ Eigene SeiteComposer 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 SeiteWas 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
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';
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';
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'
);
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
Referenz-Bibliothek
Jede Funktion, Klasse und jedes Sprach-Konstrukt lebt auf einer eigenen Seite — mit Signatur, Parameter-Tabelle, Beispielen und Fallstricken.