15 ausgewählte PHP Backend-Interviewfragen, gruppiert nach Erfahrungsstufe. Nutzen Sie diese, um Grundlagen, praktische Kompromisse und Überlegungen für die Produktionsumgebung auf Senior-Niveau zu überprüfen.
1Wie unterscheidet das Typsystem von PHP schwache und strikte Typisierung, und was ändert sich durch declare(strict_types=1)?
PHP erlaubt normalerweise kompatible skalare Typumwandlungen bei deklarierten Parameter- und Rückgabetypen, etwa eine numerische Zeichenkette für einen int-Parameter. declare(strict_types=1) aktiviert strikte skalare Prüfungen: Ein inkompatibles Argument oder ein inkompatibler Rückgabewert löst TypeError aus, wobei int weiterhin für float akzeptiert wird. Für Argumente benutzerdefinierter Funktionen bestimmt die aufrufende Datei die Striktheit; für Rückgabeprüfungen ist die Datei maßgeblich, die die Funktion definiert. Aufrufe aus internen PHP-Funktionen haben eigene Besonderheiten und unterliegen nicht einfach der strict_types-Einstellung des äußeren Aufrufers. Die Direktive muss am Anfang ihrer PHP-Datei stehen. Sie macht PHP weder statisch typisiert noch deaktiviert sie sprachweit Typumwandlungen durch Operatoren.
// THREE SEPARATE FILES: save each PHP block under its indicated filename.
// File: lib.php
<?php
function takesInt(int $x): int { return $x; }
?>
// File: weak.php
<?php
require __DIR__ . '/lib.php';
echo takesInt('123'); // 123: coercive caller
?>
// File: strict.php
<?php
declare(strict_types=1);
require __DIR__ . '/lib.php';
try {
echo takesInt('123');
} catch (TypeError $e) {
echo 'TypeError';
}
?>
2Erkläre den Unterschied zwischen == und === in PHP, einschließlich Fallstricken bei numerischen Zeichenketten, Arrays, Objekten und in_array().
PHP verwendet bei == einen losen Vergleich, gegebenenfalls mit Typumwandlung; === verlangt übereinstimmende Typen und Werte ohne skalare Typanpassung. Numerische Zeichenketten können numerisch verglichen werden. Verwende daher keinen losen Vergleich, wenn eine exakte Übereinstimmung unter Berücksichtigung des Typs erforderlich ist. Arrays sind mit == gleich, wenn sie nach den Vergleichsregeln dieselben Schlüssel-Wert-Paare enthalten, unabhängig von deren Reihenfolge. Array-=== verlangt zusätzlich dieselbe Reihenfolge und übereinstimmende Schlüssel- und Werttypen. Objekt-== vergleicht Eigenschaften von Objekten derselben Klasse; Objekt-=== verlangt dieselbe Instanz. in_array vergleicht standardmäßig lose; übergib true als drittes Argument für strikte Prüfungen. Das Verhalten loser Vergleiche zwischen Zahlen und Zeichenketten hat sich mit PHP 8 geändert. Verlasse dich daher nicht ohne Versionsangabe auf historische Beispiele für Typumwandlungen.
3Wie unterscheiden sich mixed, void und never als PHP-Rückgabetypen, und welche Garantien bieten sie?
mixed erlaubt jeden Rückgabewert, einschließlich null. void erklärt, dass eine Funktion keinen bedeutungsvollen Wert zurückgibt: Sie darf ihr Ende erreichen oder return; verwenden, aber keinen Ausdruck zurückgeben. Ein normaler Aufruf einer void-Funktion ergibt null. never ist seit PHP 8.1 verfügbar und bedeutet, dass die Funktion niemals regulär zum Aufrufer zurückkehrt. Sie kann eine Ausnahme auslösen, die Ausführung mit exit beenden oder in einer Endlosschleife bleiben; sie muss den Prozess nicht irgendwann beenden. Eine never-Funktion darf keine return-Anweisung enthalten. Der entscheidende Unterschied: Eine void-Funktion kann normal enden, sodass ihr Aufrufer fortfährt; eine never-Funktion hat keine normale Fortsetzung.
<?php
function parse(string $raw): mixed {
return json_decode($raw, true);
}
function logMessage(string $message): void {
error_log($message);
return; // allowed
}
function fail(string $message): never {
throw new RuntimeException($message);
}
logMessage('starting');
fail('stop');
echo 'unreachable';
4Was bedeutet iterable in PHP, und wie hängt es mit array, Traversable, Iterator und Generator zusammen?
iterable bedeutet array|Traversable. Generator implementiert Iterator, das Traversable erweitert, und erfüllt daher iterable. Benutzerdefinierte Klassen stellen Traversable-Verhalten über Iterator oder IteratorAggregate bereit, statt Traversable direkt in einer konkreten Klasse zu implementieren. Iterator bietet Methoden zur Steuerung der Iteration; IteratorAggregate liefert einen Iterator. Eine Generatorfunktion mit yield gibt einen Generator zurück. Wichtig ist die Unterscheidung: foreach kann auch sichtbare Eigenschaften eines gewöhnlichen Objekts durchlaufen, das Traversable nicht implementiert. Dadurch erfüllt dieses Objekt jedoch keine iterable-Parameterdeklaration.
function printItems(iterable $items): void {
foreach ($items as $item) {
echo $item, PHP_EOL;
}
}
printItems(['a', 'b']);
function letters(): Generator {
yield 'x';
yield 'y';
}
printItems(letters());
5Welche Sichtbarkeitsregeln gelten für public, protected und private in PHP, und wie wirken sie mit Vererbung und Traits zusammen?
public-Mitglieder sind überall zugänglich, wo auf das Objekt oder die Klasse zugegriffen werden kann. protected-Mitglieder sind innerhalb der deklarierenden Klasse und der zugehörigen Eltern-Kind-Klassenhierarchie zugänglich, nicht jedoch für beliebigen externen Code. private-Mitglieder sind innerhalb ihrer deklarierenden Klasse zugänglich; eine Unterklasse kann ein privates Mitglied der Elternklasse nicht als geerbte Schnittstelle verwenden. Eine gewöhnliche überschreibende Methode darf die geerbte Sichtbarkeit nicht einschränken. Konstruktoren haben jedoch besondere Vererbungsregeln und dürfen nicht als uneingeschränktes Beispiel für diese Regel gelten. Trait-Mitglieder werden in die verwendende Klasse eingebunden und folgen deren Sichtbarkeitsregeln. Die Anpassung mit as kann einen Alias erzeugen, die Sichtbarkeit einer Trait-Methode ändern oder beides. Das Hinzufügen eines Alias entfernt den ursprünglichen Namen nicht.
<?php
class Base {
public function a() {}
protected function b() {}
private function c() {}
}
trait Logs {
public function log(string $message): void {}
}
class Child extends Base {
use Logs { log as protected writeLog; }
public function test(): void {
$this->a(); // OK
$this->b(); // OK
// $this->c(); // Not accessible: private to Base
$this->writeLog('ok'); // OK inside class, protected alias
}
}
$child = new Child();
$child->a();
// $child->b(); // Not accessible from outside
// $child->writeLog('x'); // Not accessible from outside
6Wie funktionieren Traits in PHP, und wie werden Konflikte gelöst, wenn mehrere Traits dieselbe Methode definieren?
Traits ermöglichen horizontale Codewiederverwendung: Eine Klasse importiert Trait-Mitglieder mit use; ein Trait selbst lässt sich nicht instanziieren. Eine in der Klasse deklarierte Methode hat Vorrang vor Trait-Methoden, die wiederum Vorrang vor geerbten Methoden der Elternklasse haben. Liefern mehrere Traits dieselbe Methode und wird der Konflikt nicht bereits durch eine Klassenmethode aufgelöst, wählt insteadof die Implementierung aus. Mit as erhält eine Trait-Methode einen zusätzlichen Namen und optional eine andere Sichtbarkeit. Ein Alias benennt die ursprüngliche Methode weder um noch entfernt er sie. Namenskonflikte aufzulösen und die entstehende öffentliche Schnittstelle verständlich zu halten, sind getrennte Aufgaben.
<?php
trait JsonLogger {
public function log(string $message): void {
echo "json:$message\n";
}
}
trait FileLogger {
public function log(string $message): void {
echo "file:$message\n";
}
}
class Service {
use JsonLogger, FileLogger {
JsonLogger::log insteadof FileLogger;
FileLogger::log as logToFile;
JsonLogger::log as protected logJson;
}
}
$service = new Service();
$service->log('hello');
$service->logToFile('hello');
// $service->logJson('hello'); // not accessible from outside; protected alias
7Was sind PHP Enums, einschließlich reiner (pure) und Backed Enums, und wie verbessern sie die Domänenmodellierung?
PHP Enums definieren einen geschlossenen Satz benannter Fälle für einen Typ, wie z.B. Bestellstatus oder Benutzerrollen. Reine Enums (Pure Enums) haben nur benannte Fälle; Backed Enums weisen jedem Fall einen eindeutigen String- oder Int-Wert zu, was für die Speicherung, APIs (Application Programming Interfaces) oder Interoperabilität nützlich ist. Enums verbessern die Domänenmodellierung, indem sie anfällige rohe Strings, Ganzzahlen oder Konstanten durch typensichere, selbstdokumentierende Werte ersetzen. PHP Enums können auch Methoden haben und Schnittstellen implementieren; alle Enums unterstützen das Auflisten ihrer Fälle, und Backed Enums unterstützen die Konvertierung zu und von ihren skalaren Werten.
enum OrderState
{
case Draft;
case Paid;
case Shipped;
}
enum PaymentStatus: string
{
case Pending = 'pending';
case Complete = 'complete';
case Failed = 'failed';
}
$status = PaymentStatus::tryFrom('complete');
var_dump($status === PaymentStatus::Complete);
8Wie drücken skalare, nullable, Union-, Intersection- und DNF-Typen Schnittstellenverträge in PHP aus, und wann sollte welcher Typ verwendet werden?
PHP-Deklarationen beschreiben akzeptierte und zurückgegebene Werte. Skalare Deklarationen wie int, float, string und bool wählen eine Wertkategorie; ob kompatible skalare Typumwandlungen erlaubt sind, hängt auch von strict_types und dem Kontext ab. ?T oder T|null lässt null zu. Union-Typen, verfügbar seit PHP 8.0, erlauben Alternativen wie int|string. Reine Intersection-Typen, verfügbar seit PHP 8.1, verlangen, dass ein Objekt alle aufgeführten Klassen- oder Interfacetypen erfüllt, etwa Cacheable&JsonSerializable; int&string ist keine gültige Intersection. DNF-Typen (disjunktive Normalform), verfügbar seit PHP 8.2, verbinden Alternativen mit eingeklammerten Intersections, etwa (Cacheable&JsonSerializable)|string. Wähle den engsten verständlichen Vertrag, der gültige Eingaben beschreibt, statt unnötige Alternativen hinzuzufügen.
<?php
interface Cacheable {}
class Report implements Cacheable, JsonSerializable {
public function jsonSerialize(): mixed { return []; }
}
function findName(int $id): ?string {
return $id > 0 ? 'Alice' : null;
}
function normalize(int|string $id): string {
return (string) $id;
}
function store(Cacheable&JsonSerializable $item): void {
// must satisfy both interfaces
}
function handle((Cacheable&JsonSerializable)|string $input): void {
// either an object satisfying both contracts, or a string
}
9Was unterscheidet callable, Closure, First-Class-Callables, aufrufbare Objekte und Callable-Arrays in PHP?
callable beschreibt aufrufbare Werte, darunter Zeichenketten mit Funktionsnamen, Closure-Objekte, Verweise auf zugängliche Methoden und Objekte mit __invoke. Closure ist ein konkretes aufrufbares Objekt: Es kann aus einer anonymen Funktion oder aus einer benannten Funktion beziehungsweise Methode entstehen, die in eine Closure umgewandelt wird. Die First-Class-Callable-Syntax wie strlen(...) oder $object->method(...) erzeugt eine Closure, ohne das Ziel aufzurufen. Das Callable-Array [$object, 'method'] verweist auf eine zugängliche Instanzmethode; [ClassName::class, 'method'] setzt in modernem PHP eine passende zugängliche statische Methode voraus. is_callable prüft die Aufrufbarkeit im jeweiligen Gültigkeitsbereich. callable ist ein weiter Vertrag, Closure eine bestimmte Darstellungsform.
class Greeter {
public function hello(string $name): string { return "Hello $name"; }
public function __invoke(string $name): string { return "Hi $name"; }
}
$g = new Greeter();
$closure = fn(string $name) => "Hey $name";
$arrayCallable = [$g, 'hello'];
$firstClass = $g->hello(...);
$invokable = $g;
foreach ([$closure, $arrayCallable, $firstClass, $invokable] as $cb) {
echo $cb('Sam'), PHP_EOL;
}
10Wie geht PHP mit Ganzzahlüberläufen, Gleitkommagenauigkeit und exakten Dezimalberechnungen um?
Der Wertebereich nativer Ganzzahlen hängt von der Plattform ab; eine seiner Grenzen gibt PHP_INT_MAX an. Bei einem Überlauf wechselt PHP nicht automatisch zu Ganzzahlen beliebiger Genauigkeit. Das Ergebnis kann zu einem float werden und seine Ganzzahlgenauigkeit verlieren. PHP verwendet binäre Gleitkommazahlen, die viele Dezimalbrüche wie 0.1 nur näherungsweise darstellen. Rechne für exakte Dezimal- oder Geldbeträge etwa in ganzzahligen Untereinheiten wie Cent, sofern das zum Anwendungsfall passt, oder verwende eine Dezimalbibliothek wie BCMath. GMP eignet sich für Ganzzahlen beliebiger Genauigkeit.
11Wie funktionieren PHP-Referenzen (&) und Copy-on-Write, und wie können Referenzen unerwartete Nebenwirkungen verursachen?
PHP stellt Werte intern durch zvals dar. Bei Arrays und Strings nutzt PHP Copy-on-Write: Nach der Zuweisung $b = $a teilen sich die Variablen üblicherweise denselben zugrunde liegenden Wert, bis ein Schreibzugriff eine Trennung erfordert. Mit & werden die Variablen zu Aliasen desselben Containers. Eine Änderung über einen Alias wirkt sich auf den anderen aus. Auch eine Übergabe per Referenz erlaubt es der Funktion, die Variable des Aufrufers zu ändern. Bei foreach ($array as &$value) bleibt $value nach der Schleife eine Referenz auf das letzte Element. Wenn du $value später erneut zuweist, kannst du dieses Element versehentlich überschreiben. Löse die Bindung nach der Schleife mit unset($value).
12Wie funktionieren Objekt-Handles, Zuweisung, clone, __clone(), flache und tiefe Kopien in PHP?
Eine Objektvariable speichert ein Handle auf das Objekt. Bei $b = $a wird das Handle kopiert; beide Variablen verweisen danach auf dieselbe Instanz. Änderungen an deren Eigenschaften sind über beide Variablen sichtbar. Dafür brauchst du kein &: Ein Objekt-Handle ist kein Alias zwischen Variablen. clone $a erzeugt eine neue Instanz mit einer flachen Kopie der Eigenschaften. Eine Eigenschaft, die ein Objekt enthält, verweist weiterhin auf dieselbe verschachtelte Instanz, solange du sie nicht ausdrücklich klonst. Nach der flachen Kopie ruft PHP __clone() an der neuen Instanz auf. Dort kannst du IDs zurücksetzen, Ressourcen entkoppeln oder für eine tiefe Kopie alle veränderlichen Unterobjekte rekursiv kopieren, die nicht gemeinsam verwendet werden sollen.
class Address {
public function __construct(public string $city) {}
}
class User {
public function __construct(public string $name, public Address $address) {}
public function __clone() {
$this->address = clone $this->address; // make nested object independent
}
}
$u1 = new User('Ann', new Address('Paris'));
$u2 = $u1; // same object handle
$u2->name = 'Beth';
echo $u1->name, PHP_EOL;
$u3 = clone $u1; // new User object; __clone deep-clones address
$u3->address->city = 'Rome';
echo $u1->address->city, PHP_EOL;
echo $u3->address->city, PHP_EOL;
13Wie untersuchst du erschöpfte Datenbankverbindungen, und wie gestaltest du das Verbindungsmanagement für PHP-FPM und langlebige Worker?
Vergleiche Verbindungslimit und aktuelle Sitzungen mit der gesamten PHP-Parallelität. Jeder aktive FPM-Kindprozess kann eine eigene Verbindung halten; Pools, Hosts, Cronjobs, Queue-Worker, Migrationen und Deployments erhöhen den Bedarf. Prüfe Sitzungsansichten und -zustände, offene Transaktionen im Leerlauf, langsame Abfragen, Sperrwartezeiten, Fehlerprotokolle, Pool-Metriken sowie Änderungen am Verkehr oder an der Worker-Zahl. Halte pm.max_children und die Parallelität pro Host im Datenbankbudget. Nutze kurze anfragegebundene Verbindungen und Transaktionen, bestätige sie zeitnah oder rolle sie zurück, und halte keine Verbindung für langsame externe Aufrufe offen. Persistente PDO-Verbindungen sparen Aufbaukosten, können aber pro FPM-Prozess ungenutzte Verbindungen belegen und Sitzungszustand behalten. PgBouncer kann bei kompatibler Nutzung viele Clients auf weniger Serververbindungen verteilen. Langlebige Worker brauchen Verbindungsprüfungen, erneuten Verbindungsaufbau nach Leerlauf-Timeouts, Neustarts, Failover oder Forks, Bereinigung des Sitzungszustands und Wiederholungen nur für sichere oder idempotente Arbeit.
Connection-budget example:
- 6 application servers
- 2 PHP-FPM pools on EACH server
- pm.max_children = 40 for EACH pool
- Assume at most 1 database connection per child
Potential request connections = 6 * 2 * 40 = 480.
If 40 were the TOTAL children per server across both pools, it would be 240.
Budget separately for queue workers, cron, deployments and administrators.
A limit of 300 database connections cannot accommodate 480 simultaneous connections.
14Wie würdest du Lesereplikate, die Trennung von Lese- und Schreibzugriffen, Replikationsverzögerung und den Ausfall der primären Datenbank in PHP behandeln?
Trenne Verbindungen zur primären Datenbank und zu Replikaten nach der Bedeutung der Operation, nicht bloß danach, ob SQL mit SELECT beginnt. Sperrende Lesezugriffe, Schreibzugriffe und Transaktionen, die die Konsistenz der primären Datenbank benötigen, müssen diese verwenden. Rein lesende Transaktionen dürfen ein geeignetes Replikat nutzen, wenn ihre Konsistenzanforderungen es erlauben. Asynchrone Replikation kann verzögert sein. Laravels sticky-Einstellung leitet nach einem Schreibzugriff spätere Lesezugriffe derselben Anfrage zur Schreibverbindung. Eine feste Bindungsdauer über mehrere Anfragen hinweg mildert das Problem nur und garantiert nicht uneingeschränkt, eigene Schreibzugriffe anschließend lesen zu können. Für diese Garantie lies von der primären Datenbank oder warte, bis ein Replikat die erforderliche Schreibposition übernommen hat, mit Zeitlimit und Rückfall auf die primäre Datenbank. Proxys unterscheiden sich: RDS Proxy verwendet getrennte Lese- und Schreibendpunkte und teilt nicht allgemein automatisch jede SQL-Anweisung in Lese- oder Schreibverkehr auf. Verwirf beim Failover veraltete Verbindungen und verbinde dich mit begrenzten, zunehmenden Wartezeiten neu. Geht eine Commit-Antwort verloren, ist das Ergebnis ungewiss: Wiederhole nichtidempotente Schreibzugriffe nicht blind; verwende Idempotenzschlüssel oder kläre zuerst das Ergebnis.
15Wie lassen sich Datenbankmigrationen und Schemaänderungen in PHP-Anwendungen sicher ohne oder mit minimaler Ausfallzeit ausrollen?
Verwende Erweiterungs- und Rückbauänderungen, die während eines schrittweisen Rollouts sowohl die alte als auch die neue Anwendungsversion vertragen. Füge zuerst kompatible Strukturen hinzu und rolle dann Code aus, der beide Darstellungen nutzen kann. Ist doppeltes Schreiben nötig, halte es konsistent und plane wiederaufnehmbare, gedrosselte Nachbefüllungen, die neuere Schreibzugriffe nicht überschreiben. Prüfe die kopierten Daten und stelle Lesezugriffe um, bevor du alte Codepfade entfernst. Online-DDL (Datendefinitionssprache) reduziert bestimmte Blockierungsrisiken, ist aber nicht grundsätzlich sperrfrei: Prüfe datenbank- und versionsspezifische Sperren, Transaktionseinschränkungen, Ressourcenverbrauch und Auswirkungen auf die Replikation. PostgreSQLs CREATE INDEX CONCURRENTLY hat beispielsweise Einschränkungen und kann nach einem Fehler einen ungültigen Index hinterlassen. Entferne alte Spalten erst, wenn keine alten Anwendungsinstanzen mehr laufen und das erforderliche Rückrollfenster abgelaufen ist. Nach destruktiven Rückbauänderungen ist die Rückkehr zu Code, der gelöschte Daten benötigt, nicht mehr automatisch sicher.
<?php
// Step 1: Migration (Expand) -> Add new column as nullable
Schema::table('users', function (Blueprint $table) {
$table->string('full_name')->nullable()->after('name');
});
// Step 2: Code deployment -> Write to both, read with fallback
class User extends Model {
public function setNameAttribute($value) {
$this->attributes['name'] = $value;
$this->attributes['full_name'] = $value; // Dual write
}
}
// Step 3: Background worker backfills existing rows in small batches
// Step 4: Code deployment -> Read/write exclusively from 'full_name'
// Step 5: Migration (Contract) -> Drop legacy 'name' column