15 udvalgte PHP backend-interviewspørgsmål grupperet efter anciennitetsniveau. Brug dem til at gennemgå grundlæggende principper, praktiske afvejninger og ræsonnement på seniorniveau i produktion.
1Hvordan skelner PHPs typesystem mellem svag og stærk typning, og hvad ændres, når `declare(strict_types=1)` aktiveres?
PHP er som standard svagt/tvangskonverterende for skalære typeerklæringer: når en funktion erklærer skalære parameter- eller returtyper, kan PHP konvertere kompatible værdier, såsom numeriske strenge, til tal i stedet for at afvise dem. `declare(strict_types=1)` gør skalær typekontrol streng for den pågældende fil, så inkompatible skalære værdier ikke typekonverteres, og en `TypeError` kastes. Direktivet er fil-specifikt: for brugerdefinerede funktionsargumenter kontrollerer kalderens fil strengheden; for retursætninger kontrollerer filen, hvor funktionen er defineret, returværdiens strenghed. Det gør ikke PHP globalt strengt eller deaktiverer alle konverteringer i sproget.
<?php
// lib.php
function takesInt(int $x): int {
return $x;
}
// weak.php, no strict_types
echo takesInt('123'); // coerces string to int
// strict.php
declare(strict_types=1);
echo takesInt('123'); // TypeError
2Forklar forskellen mellem `==` og `===` i PHP, herunder faldgruber med numeriske strenge, arrays, objekter og `in_array()`.
`==` udfører løs sammenligning, så PHP kan typekonvertere værdier før sammenligning; `===` udfører streng sammenligning, hvilket kræver både samme værdi og samme type uden typeomdannelse (coercion). Numeriske strenge er en almindelig faldgrube, fordi de kan behandles som tal i løse sammenligninger. Arrays og objekter har deres egne sammenligningsregler: arrays sammenlignes ud fra nøgler og værdier, mens objekt `==` sammenligner objekter af samme klasse ud fra deres egenskaber, og objekt `===` kræver den nøjagtig samme instans. `in_array()` bruger løs sammenligning som standard, så send `true` som det tredje argument, når typen skal matche.
3Hvordan adskiller `mixed`, `void` og `never` sig som PHP-returtyper, og hvilke garantier giver de?
Som returtyper giver `mixed`, `void` og `never` forskellige løfter. `mixed` betyder, at funktionen kan returnere enhver værdi, inklusive `null`. `void` betyder, at funktionen ikke er beregnet til at returnere en meningsfuld værdi; den kan bruge `return;` men kan ikke returnere et udtryk. `never` betyder, at funktionen ikke returnerer normalt til sin kalder: den skal altid kaste en undtagelse, afslutte, eller på anden vis terminere kontrolflowet. `never` er stærkere end `void`, fordi eksekveringen ikke kan fortsætte efter et normalt kald til en `never`-funktion.
<?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';
4Hvad er `iterable` i PHP, og hvordan forholder det sig til `array`, `Traversable`, `Iterator` og `Generator`?
I PHP er `iterable` en typeerklæring (type declaration), der betyder "en `array` eller et objekt, der implementerer `Traversable`." `Traversable` er det grundlæggende markeringsinterface (marker interface) for objekter, der kan bruges med `foreach`; brugerdefineret kode (userland code) implementerer normalt `Iterator` eller `IteratorAggregate`, som udvider `Traversable`. En `Generator` er det objekt, der returneres fra en generatorfunktion ved brug af `yield`; den implementerer `Iterator`, så den er også `Traversable` og accepteres overalt, hvor `iterable` kræves.
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());
5Hvad er `public`, `protected` og `private` synlighedsreglerne (visibility rules) i PHP, og hvordan interagerer de med nedarvning (inheritance) og traits?
I PHP er `public`-medlemmer tilgængelige overalt, `protected`-medlemmer er tilgængelige fra den erklærende klasse og underklasser, og `private`-medlemmer er kun tilgængelige fra den klasse, der erklærer dem. Ved nedarvning kan underklasser bruge nedarvede `public`- og `protected`-medlemmer, men en forældres `private`-medlemmer er ikke tilgængelige som nedarvet API. Når metoder overriders, kan et barn generelt ikke reducere synligheden; for eksempel skal en `public` forældremetode forblive `public`. Traits er sammensat i den anvendende klasse, og trait-medlemmer følger PHP's synlighedsregler. Trait-metodes synlighed kan også tilpasses eller aliaseres med `as` ved import af traiten.
<?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
6Hvordan fungerer traits i PHP, og hvordan løses konflikter, når flere traits definerer den samme metode?
Traits er PHP's mekanisme for horisontal genbrug af kode: en klasse kan bruge et eller flere traits, og traitens medlemmer inkorporeres i klassen. Traits er ikke enkeltstående klasser og kan ikke instansieres direkte. Hvis flere traits giver metoder med samme navn, kræver PHP eksplicit konfliktløsning. `insteadof`-operatoren vælger, hvilken trait-metode der bruges til det modstridende navn. `as`-operatoren kan oprette et alias for en trait-metode og kan også ændre den importerede metodes synlighed. Klassemetoder tilsidesætter trait-metoder, og trait-metoder har forrang over nedarvede forældremetoder.
<?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
7Hvad er PHP enums, herunder rene og backed enums, og hvordan forbedrer de domænemodellering?
PHP enums definerer et lukket sæt af navngivne værdier for en type, såsom ordrestatuser eller brugerroller. Rene enums har kun navngivne værdier; backed enums giver hver værdi en unik streng- eller int-værdi, nyttigt til lagring, API'er (Application Programming Interfaces) eller interoperabilitet. Enums forbedrer domænemodellering ved at erstatte skrøbelige rå strenge, heltal eller konstanter med typesikre, selv-dokumenterende værdier. PHP enums kan også have metoder og implementere interfaces; alle enums understøtter at liste deres værdier, og backed enums understøtter konvertering til og fra deres skalare værdier.
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);
8Hvordan udtrykker skalar-, nullable-, union-, intersection- og DNF-typer PHP API-kontrakter, og hvornår skal hver bruges?
PHP-typedeklarationer beskriver de værdier, en API accepterer eller returnerer. Skalartyper som `int`, `float`, `string` og `bool` bruges, når der forventes præcis én primitiv type. Nullable-typer, skrevet `?T` eller `T|null`, tillader enten en værdi af type `T` eller `null`. Union-typer som `A|B` tillader et af flere alternativer. Intersection-typer som `A&B` kræver, at en værdi opfylder alle angivne typer, typisk flere interfaces. DNF (Disjunctive Normal Form)-typer kombinerer unioner og intersections, for eksempel `(A&B)|C`, for at udtrykke mere komplekse kontrakter. Hver bør bruges, når den matcher det reelle API-løfte, og undgå unødvendigt brede eller forvirrende typer.
<?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
}
9Hvad er forskellen mellem `callable`, `Closure`, first-class callables, invokable objekter og callable arrays i PHP?
`callable` er en bred type/kontrakt for enhver værdi, PHP kan kalde, såsom en funktionens navn som streng, en `Closure`, et invokable objekt eller et metodereference-array som `[$object, 'method']` eller `[ClassName::class, 'method']`. En `Closure` er et konkret anonym funktionsobjekt og er selv callable. First-class callable syntaks, såsom `$obj->method(...)` eller `strlen(...)`, opretter en `Closure` fra et callable udtryk. Et invokable objekt er callable, fordi det definerer `__invoke()`. Callable arrays er den traditionelle to-elementers metodereference-form. `is_callable()` kan bruges til at kontrollere callability ved runtime.
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;
}
10Hvordan håndterer PHP heltalsoverløb, flydende-komma-præcision og præcise decimalberegninger?
PHP-heltal er platform-afhængige, med grænser eksponeret af konstanter som `PHP_INT_MAX`. PHP bevarer ikke automatisk native heltalresultater som heltal med vilkårlig præcision efter overløb; heltalregning med overløb kan resultere i `float` eller på anden måde miste præcis heltal-semantik. PHP-floats er binære flydende-komma-værdier, så mange decimalbrøker, f.eks. `0.1`, er omtrentlige. For præcise decimal- eller penge-beregninger skal man undgå floats og bruge en præcis tilgang, såsom mindre heltal-enheder, hvor det er passende, eller værktøjer til faste/vilkårlig præcision decimaler som BCMath. GMP er nyttigt til heltalregning med vilkårlig præcision.
11Forklar PHP-referencer (`&`), copy-on-write, og hvordan referencer kan skabe uventede sideeffekter.
PHP-variabler repræsenteres internt af `zvals`. For værdier som arrays og strenge bruger PHP `copy-on-write`: at tildele `$b = $a` deler normalt den samme underliggende værdi, indtil en variabel ændres, hvorpå PHP adskiller/kopierer den. En reference, der oprettes med `&`, gør variabler til aliaser til den samme container, så at ændre den ene ændrer den anden og kan omgå den adskillelsesadfærd, folk forventer af normal tildeling. Passing by reference lader en funktion ændre kalderens variabel. Referencer kan forårsage overraskende sideeffekter, især med `foreach ($array as &$value)`: løkkevariablen forbliver en reference til det sidste element efter løkken, så genbrug af den senere kan uforvarende overskrive dette element, medmindre du `unset($value)`.
12Hvordan fungerer objekt-referencer, tildeling, kloning, `__clone`, overfladisk kopi og dyb kopi i PHP?
I PHP gemmer en objekt-variabel en reference til et objekt, ikke en fuld objektkopi. Tildelingen `$b = $a` kopierer referencen, så begge variabler refererer til det samme objekt; ændringer af objektet via en af variablerne er synlige gennem den anden, uden brug af `&`. `clone $a` opretter en ny objektinstans med en overfladisk kopi af det originale objekts egenskaber. Hvis en egenskab selv er et objekt, peger det klonede objekts egenskab stadig på det samme indlejrede objekt, medmindre du eksplicit kloner det. `__clone()` kaldes på det nye objekt efter den overfladiske kopi og er stedet til at nulstille ID'er, frigøre ressourcer eller udføre en dyb kopi af indlejrede objekter. En dyb kopi betyder rekursivt at kopiere de indlejrede foranderlige objekter, der ikke bør deles.
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;
13Hvordan ville du fejlfinde udtømning af databaseforbindelser og designe forbindelsesstyring for PHP-FPM (PHP FastCGI Process Manager) og langkørende arbejdere?
For at fejlfinde udtømning af forbindelser ville jeg sammenligne grænser for databaseforbindelser og nuværende sessioner med den samlede PHP-konkurrence: hvert aktive PHP-FPM-barn kan opretholde sin egen databaseforbindelse, og flere applikationspuljer, værter, cron-jobs, kø-arbejdere, migreringer og udrulninger mangedobler efterspørgslen. Jeg ville inspicere database-sessionsvisninger, forbindelsestilstande, idle-in-transaction-sessioner, langsomme forespørgsler, låseventetider, fejllogs, pool-metrics og nylige trafik- eller arbejderantalændringer. For PHP-FPM skal `max_children` og konkurrence pr. vært holdes inden for databasebudgettet, korte anmodningsspecifikke transaktioner/forbindelser skal anvendes, commit eller rollback skal udføres prompte, og man skal undgå at holde forbindelser åbne under langsomme eksterne kald. Vedvarende PDO (PHP Data Objects)-forbindelser kan reducere forbindelses-overhead, men kan fastlåse mange inaktive forbindelser pr. FPM-proces og bevare sessionstilstand, så de kræver forsigtighed. En pooler eller proxy som PgBouncer kan multiplexe mange PHP-klienter til færre databaseserverforbindelser, når applikationen er kompatibel. Langkørende arbejdere har brug for eksplicit håndtering af forbindelsens livscyklus: sundhedstjek, genforbindelse efter inaktivitets-timeouts/genstarter/failover/forks, nulstilling af sessionstilstand og kun genforsøg af sikkert eller idempotent arbejde.
Example:
- 6 application servers
- 40 PHP-FPM max_children per server
- 2 separate FPM pools that can hit the database
Potential concurrent PHP request DB connections = 6 * 40 * 2 = 480
If PostgreSQL max_connections is 300 and queues/cron/admin tools also connect,
connection exhaustion is expected during traffic spikes.
14Hvordan ville du håndtere læsekopier, opdeling af læse/skrive-trafik, replikationsforsinkelse og failover af primær database i PHP?
Håndtering af læsekopier og failover i PHP kræver opdeling af læse- og skrivetrafik, afbødning af replikationsforsinkelse og styring af forbindelsesafbrydelser under failover. På applikationsniveau dirigerer databasekonfiguration eller middleware læseforespørgsler (`SELECT`) til læsekopier og skriveforespørgsler (`INSERT`, `UPDATE`, `DELETE`) til den primære instans (alternativt styres dette via databaseproxies som ProxySQL eller AWS RDS Proxy). Fordi replikering er asynkron, kan replikationsforsinkelse forårsage forældede læsninger. For at garantere 'read-your-writes' konsistens bør applikationen dirigere læsninger inden for en aktiv transaktion til den primære, og midlertidigt lade efterfølgende læsninger i en specifik brugersession eller forespørgselslivscyklus umiddelbart efter en skrivning gå til den primære. Under primær failover promoverer moderne databaseklynger en replika og opdaterer DNS (Domain Name System)/proxy-endpoints. I PHP skal forbindelseslaget fange forbindelsestab og skrivebeskyttede fejl, slette forældede PDO (PHP Data Objects)-sockets og anvende kontrollerede genforsøg med eksponentiel backoff.
15Hvordan implementeres databasemigreringer og skemaændringer sikkert med nul eller minimal nedetid i PHP-applikationer?
Implementering af databasemigreringer med nul nedetid i PHP-applikationer kræver frakobling af skemaudvikling fra applikationskodeudrulninger ved hjælp af Expand and Contract (Parallel Run)-mønsteret sammen med ikke-blokerende online DDL (Data Definition Language)-praksis. I Expand-fasen anvendes bagudkompatible skemaændringer (f.eks. tilføjelse af null-able kolonner, nye tabeller eller samtidige indekser ved hjælp af værktøjer som `pt-online-schema-change`, `gh-ost` eller native online DDL i MySQL/PostgreSQL). Applikationen udrulles derefter til at understøtte dobbelt-skrivning (skrivning til både gamle og nye skemastrukturer), mens der læses fra en af dem. Historiske data fyldes derefter asynkront op i droslede baggrundspartier for at forhindre låsning af tabeller eller induktion af replikeringsforsinkelse. I Contract-fasen, når datakonsistens er valideret, og læsninger er fuldstændigt skiftet til den nye struktur, fjerner en kodeudrulning referencer til det gamle skema. Til sidst sletter en oprydningsmigrering ældre kolonner eller tabeller. Sikker rollback kræver, at hver mellemliggende skemastatus forbliver fuldt kompatibel med både den tidligere og den kommende applikationsversion under rullende udgivelser.
<?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