Forberedelse til PHP-interview

PHP-interviewspørgsmål

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.

Start et PHP AI-interviewIntet kreditkort kræves. 1 gratis session er tilgængelig.
Øv tekniske jobsamtaler på engelskEn tilstand, hvor ikke-modersmålstalende kan øve sig i at bestå tekniske interviews.

Junior spørgsmål

1Hvordan skelner PHP mellem svag og streng typning, og hvad ændrer declare(strict_types=1)?

PHP tillader normalt kompatible skalære konverteringer for erklærede parameter- og returtyper, eksempelvis en numerisk streng til en int-parameter. declare(strict_types=1) aktiverer streng skalær kontrol: Et inkompatibelt argument eller en inkompatibel returværdi udløser TypeError, men int accepteres stadig som float. For argumenter til brugerdefinerede funktioner bestemmer den kaldende fil strengheden; filen med definitionen bestemmer returkontrollen. Kald fra interne PHP-funktioner har særlige forbehold og styres ikke blot af den ydre kalders strict_types. Direktivet skal stå i begyndelsen af PHP-filen. Det gør ikke PHP statisk typet og slår ikke operatorernes konverteringer fra i hele sproget.

// 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';
}
?>
Prøv at besvare dette spørgsmål med en AI-coach

2Forklar forskellen mellem == og === i PHP, herunder faldgruber med numeriske strenge, arrays, objekter og in_array().

== bruger løs sammenligning med typekonvertering, hvor det er relevant; === kræver samme typer og værdier uden skalær konvertering. Numeriske strenge kan sammenlignes numerisk, så undgå løs sammenligning, når præcis typefølsom identitet kræves. Arrays er ens med ==, hvis de indeholder samme nøgle/værdi-par efter sammenligningsreglerne, uanset rækkefølge. === kræver desuden samme rækkefølge samt matchende nøgle- og værdityper. Objekt-== sammenligner egenskaber hos objekter af samme klasse; === kræver samme instans. in_array sammenligner løst som standard; giv true som tredje argument for streng kontrol. Løs tal/streng-sammenligning ændrede sig i PHP 8, så undgå historiske konverteringseksempler uden versionsangivelse.

<?php
var_dump(123 == '123');      // loose: numeric string is converted
var_dump(123 === '123');     // strict: int !== string

$values = [0, 1, 2];
var_dump(in_array('0', $values));       // loose
var_dump(in_array('0', $values, true)); // strict
Prøv at besvare dette spørgsmål med en AI-coach

3Hvordan adskiller mixed, void og never sig som PHP-returtyper, og hvad garanterer de?

mixed tillader enhver returværdi, inklusive null. void angiver, at funktionen ikke returnerer en meningsfuld værdi: Den kan nå slutningen eller bruge return;, men må ikke returnere et udtryk. Et normalt kald af en void-funktion evalueres til null. never, tilgængelig siden PHP 8.1, betyder, at funktionen aldrig returnerer normalt til kalderen. Den kan kaste en undtagelse, afslutte med exit eller blive i en uendelig løkke; den behøver ikke til sidst afslutte processen. En never-funktion må ikke indeholde return. Forskellen er, at void kan afslutte normalt, så kalderen fortsætter, mens never ikke har en normal fortsættelse.

<?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';
Prøv at besvare dette spørgsmål med en AI-coach

4Hvad er iterable i PHP, og hvordan hænger det sammen med array, Traversable, Iterator og Generator?

iterable betyder array|Traversable. Generator implementerer Iterator, som udvider Traversable, og opfylder derfor iterable. Brugerdefinerede klasser leverer Traversable-adfærd gennem Iterator eller IteratorAggregate frem for direkte implementering af Traversable i en konkret klasse. Iterator tilbyder metoder, der styrer iterationen; IteratorAggregate leverer en iterator. En generatorfunktion med yield returnerer en Generator. En vigtig forskel: foreach kan også gennemløbe synlige egenskaber på et almindeligt objekt uden Traversable, men objektet opfylder ikke dermed en iterable-parametererklæring.

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());
Prøv at besvare dette spørgsmål med en AI-coach

5Hvad er PHP's synlighedsregler for public, protected og private, og hvordan virker de med nedarvning og traits?

public-medlemmer er tilgængelige overalt, hvor objektet eller klassen kan tilgås. protected-medlemmer er tilgængelige i den erklærende klasse og det tilknyttede hierarki af forældre- og underklasser, ikke fra vilkårlig ekstern kode. private-medlemmer er tilgængelige i deres erklærende klasse; en underklasse kan ikke bruge en forælders private medlem som nedarvet grænseflade. En almindelig overskrivende metode må ikke indsnævre nedarvet synlighed, men konstruktører har særlige nedarvningsregler og er ikke et ubetinget eksempel på reglen. Trait-medlemmer indarbejdes i den brugende klasse og følger dens synlighedsregler. as kan skabe et alias, ændre en trait-metodes synlighed eller begge dele; et alias fjerner ikke i sig selv det oprindelige navn.

<?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
Prøv at besvare dette spørgsmål med en AI-coach

6Hvordan virker traits i PHP, og hvordan løses konflikter, når flere traits definerer samme metode?

Traits giver vandret kodegenbrug: En klasse importerer medlemmer med use, og en trait kan ikke selv instantieres. En metode erklæret i klassen har forrang over trait-metoder, som igen har forrang over nedarvede forældremetoder. Leverer flere traits samme metode, og er konflikten ikke allerede løst af en klassemetode, vælges implementeringen med insteadof. Brug as til at give en trait-metode et ekstra navn og eventuelt anden synlighed. Et alias omdøber eller fjerner ikke den oprindelige metode. At løse navnekollisioner og holde den resulterende offentlige grænseflade klar er forskellige opgaver.

<?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
Prøv at besvare dette spørgsmål med en AI-coach

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);
Prøv at besvare dette spørgsmål med en AI-coach

Mellem spørgsmål

8Hvordan udtrykker skalære, nullable-, union-, intersection- og DNF-typer PHP-grænsefladers kontrakter, og hvornår bruges de?

PHP-erklæringer beskriver accepterede og returnerede værdier. Skalære erklæringer som int, float, string og bool vælger en værdikategori; om kompatible skalære konverteringer er tilladt, afhænger også af strict_types og konteksten. ?T eller T|null tillader null. Unioner, tilgængelige siden PHP 8.0, tillader alternativer som int|string. Rene intersection-typer siden PHP 8.1 kræver, at et objekt opfylder alle angivne klasse-/interfacetyper, eksempelvis Cacheable&JsonSerializable; int&string er ikke gyldigt. DNF-typer (disjunktiv normalform), tilgængelige siden PHP 8.2, kombinerer alternativer og parentesindrammede intersection-typer, eksempelvis (Cacheable&JsonSerializable)|string. Vælg den snævreste forståelige kontrakt, der beskriver gyldigt input, uden unødvendige alternativer.

<?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
}
Prøv at besvare dette spørgsmål med en AI-coach

9Hvad adskiller callable, Closure, førsteklasses callables, kaldelige objekter og callable-arrays i PHP?

callable beskriver kaldelige værdier: strenge med funktionsnavne, Closure-objekter, tilgængelige metodereferencer og objekter med __invoke. Closure er et konkret kaldeligt objekt, som kan komme fra en anonym funktion eller en navngiven funktion/metode konverteret til Closure. Førsteklasses callable-syntaks som strlen(...) eller $object->method(...) skaber en Closure uden at kalde målet. [$object, 'method'] henviser til en tilgængelig instansmetode; [ClassName::class, 'method'] kræver i moderne PHP en passende tilgængelig statisk metode. is_callable kontrollerer kaldelighed i det relevante synlighedsområde. callable er en bred kontrakt, mens Closure er en bestemt repræsentation.

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;
}
Prøv at besvare dette spørgsmål med en AI-coach

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.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
Prøv at besvare dette spørgsmål med en AI-coach

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)`.

$a = [1, 2];
$b = $a;      // shared until modification
$b[0] = 99;   // separates; $a is unchanged
var_dump($a, $b);

$c =& $a;     // alias/reference
$c[0] = 42;   // modifies $a too
var_dump($a, $c);
Prøv at besvare dette spørgsmål med en AI-coach

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;
Prøv at besvare dette spørgsmål med en AI-coach

Senior spørgsmål

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.

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.
Prøv at besvare dette spørgsmål med en AI-coach

14Hvordan vil du håndtere læsereplikaer, opdeling af læsning/skrivning, replikeringsforsinkelse og failover af den primære database i PHP?

Adskil forbindelser til primær og replika efter operationens betydning, ikke blot om SQL starter med SELECT: Låsende læsninger, skrivninger og transaktioner, der kræver primærens konsistens, skal bruge primæren. Skrivebeskyttede transaktioner kan bruge en passende replika, hvis konsistenskravene tillader det. Asynkron replikering kan være forsinket. Laravels sticky-indstilling sender læsninger efter en skrivning til skriveforbindelsen under den aktuelle forespørgsel; fast tidsmæssig binding på tværs af forespørgsler er kun en afbødning, ikke en ubetinget garanti for at læse egne skrivninger. En garanti kræver læsning fra primæren eller venten på, at replikaen har anvendt den krævede skriveposition, med timeout og tilbagefald til primæren. Proxyadfærd varierer: RDS Proxy bruger separate læse-/skriveendpoints og er ikke en generel automatisk fordeler af hver SQL-sætning. Kassér forældede forbindelser ved failover og genopret med begrænset stigende ventetid. En mistet commit-respons efterlader et tvetydigt resultat: Gentag ikke ikke-idempotente skrivninger blindt; brug idempotensnøgler eller afklar først resultatet.

<?php
// config/database.php
return [
    'mysql' => [
        'read' => [
            'host' => [env('DB_REPLICA_HOST_1'), env('DB_REPLICA_HOST_2')],
        ],
        'write' => [
            'host' => [env('DB_PRIMARY_HOST')],
        ],
        'sticky'    => true,
        'driver'    => 'mysql',
        'database'  => env('DB_DATABASE'),
        'username'  => env('DB_USERNAME'),
        'password'  => env('DB_PASSWORD'),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
    ],
];
Prøv at besvare dette spørgsmål med en AI-coach

15Hvordan udrulles databasemigrationer og skemaændringer sikkert med ingen eller minimal nedetid i PHP-applikationer?

Brug udvidelse efterfulgt af fjernelse af gamle strukturer, som tåler både gamle og nye applikationsversioner under gradvis udrulning. Tilføj først kompatible strukturer, og udrul så kode, der kan bruge begge repræsentationer. Er dobbeltskrivning nødvendig, skal den holdes konsistent; historiske data skal efterfyldes med genoptagelig, hastighedsbegrænset behandling, som ikke overskriver nyere skrivninger. Validér kopierede data, og skift læsninger før gamle kodeveje fjernes. Online DDL (datadefinitionssprog) reducerer bestemte blokeringsrisici, men er ikke altid låsefrit: Undersøg database- og versionsspecifikke låse, transaktionsbegrænsninger, ressourceforbrug og replikeringseffekt. PostgreSQL CREATE INDEX CONCURRENTLY har eksempelvis begrænsninger og kan efterlade et ugyldigt indeks efter fejl. Fjern gamle kolonner, når gamle instanser er væk, og den nødvendige tilbagerulningsperiode er udløbet. Efter destruktiv fjernelse er tilbagerulning til kode, der kræver slettede data, ikke automatisk sikker.

<?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
Prøv at besvare dette spørgsmål med en AI-coach