PHP interviewvoorbereiding

PHP interviewvragen

15 geselecteerde PHP backend interviewvragen gegroepeerd op senioriteitsniveau. Gebruik ze om basisprincipes, praktische afwegingen en het redeneren op senior-niveau voor productie te herzien.

Start een PHP AI InterviewGeen creditcard nodig. 1 gratis sessie beschikbaar.
Oefen technische interviews in het EngelsEen modus waarin anderstaligen kunnen oefenen met het afleggen van technische interviews.

Junior vragen

1Hoe onderscheidt het typesysteem van PHP zwakke en strikte typering, en wat verandert er wanneer `declare(strict_types=1)` is ingeschakeld?

PHP is standaard zwak/coercief voor scalaire typeaanduidingen: wanneer een functie scalaire parameter- of retourtypen declareert, kan PHP compatibele waarden zoals numerieke strings naar getallen converteren in plaats van ze af te wijzen. `declare(strict_types=1)` maakt scalaire typecontrole strikt voor dat bestand, zodat incompatibele scalaire waarden niet worden geconverteerd en er een `TypeError` wordt gegenereerd. De directive is bestand-gebonden: voor door de gebruiker gedefinieerde functieargumenten regelt het bestand van de aanroeper de striktheid; voor return-statements regelt het bestand waarin de functie is gedefinieerd de striktheid van de retourwaarde. Het maakt PHP niet globaal strikt of schakelt niet alle conversies in de taal uit.

<?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
Probeer deze vraag te beantwoorden met een AI-begeleider

2Leg het verschil uit tussen `==` en `===` in PHP, inclusief valkuilen met numerieke strings, arrays, objecten en `in_array()`.

`==` voert een losse vergelijking uit, dus PHP kan typen converteren voordat ze worden vergeleken; `===` voert een strikte vergelijking uit, waarbij zowel dezelfde waarde als hetzelfde type vereist zijn zonder conversie. Numerieke strings zijn een veelvoorkomende valkuil omdat ze in losse vergelijkingen als getallen kunnen worden behandeld. Arrays en objecten hebben hun eigen vergelijkingsregels: arrays worden vergeleken op sleutels en waarden, terwijl object `==` objecten van dezelfde klasse vergelijkt op eigenschappen en object `===` de exact dezelfde instantie vereist. `in_array()` gebruikt standaard een losse vergelijking, dus geef `true` door als derde argument wanneer het type moet overeenkomen.

<?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
Probeer deze vraag te beantwoorden met een AI-begeleider

3Hoe verschillen `mixed`, `void` en `never` als PHP returntypen, en welke garanties bieden ze?

Als returntypen doen `mixed`, `void` en `never` verschillende beloften. `mixed` betekent dat de functie elke waarde mag retourneren, inclusief `null`. `void` betekent dat de functie niet bedoeld is om een zinvolle waarde te retourneren; deze mag `return;` gebruiken, maar kan geen expressie retourneren. `never` betekent dat de functie niet normaal terugkeert naar zijn aanroeper: de functie moet altijd een uitzondering werpen (throw), afsluiten (exit), of anderszins de controlestroom beëindigen. `never` is sterker dan `void` omdat de uitvoering niet kan doorgaan na een normale aanroep van een `never`-functie.

<?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';
Probeer deze vraag te beantwoorden met een AI-begeleider

4Wat is `iterable` in PHP, en hoe verhoudt het zich tot `array`, `Traversable`, `Iterator` en `Generator`?

In PHP is `iterable` een typedeklaratie die 'een `array` of een object dat `Traversable` implementeert' betekent. `Traversable` is de basis marker-interface voor objecten die bruikbaar zijn met `foreach`; gebruikerscode implementeert normaal gesproken `Iterator` of `IteratorAggregate`, die beide `Traversable` uitbreiden. Een `Generator` is het object dat wordt geretourneerd door een generatorfunctie die `yield` gebruikt; het implementeert `Iterator`, en is daarom ook `Traversable` en wordt geaccepteerd overal waar `iterable` vereist is.

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());
Probeer deze vraag te beantwoorden met een AI-begeleider

5Wat zijn de `public`, `protected` en `private` zichtbaarheidsregels in PHP, en hoe werken ze samen met overerving en traits?

In PHP zijn `public`-leden overal toegankelijk, `protected`-leden zijn toegankelijk vanuit de declarerende klasse en subklassen, en `private`-leden zijn alleen toegankelijk vanuit de klasse die ze declareert. Bij overerving kunnen subklassen overgeërfde `public` en `protected` leden gebruiken, maar `private`-leden van een ouder zijn niet toegankelijk als overgeërfde API. Bij het overschrijven van methoden kan een kindklasse over het algemeen de zichtbaarheid niet verminderen; een `public`-methode van de ouder moet bijvoorbeeld `public` blijven. Traits worden samengesteld in de gebruikende klasse, en trait-leden volgen de PHP-zichtbaarheidsregels. De zichtbaarheid van trait-methoden kan ook worden aangepast of gealiaset met `as` bij het importeren van de trait.

<?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
Probeer deze vraag te beantwoorden met een AI-begeleider

6Hoe werken traits in PHP, en hoe worden conflicten opgelost wanneer meerdere traits dezelfde methode definiëren?

Traits zijn PHP's mechanisme voor horizontale codehergebruik: een klasse kan één of meerdere traits gebruiken, en de leden van de trait worden in de klasse opgenomen. Traits zijn geen op zichzelf staande klassen en kunnen niet direct worden geïnstantieerd. Als meerdere traits methoden met dezelfde naam bieden, vereist PHP expliciete conflictoplossing. De `insteadof`-operator kiest welke trait-methode wordt gebruikt voor de conflicterende naam. De `as`-operator kan een alias creëren voor een trait-methode en kan ook de zichtbaarheid van de geïmporteerde methode wijzigen. Klasse-methoden overschrijven trait-methoden, en trait-methoden hebben voorrang op geërfde oudermethoden.

<?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
Probeer deze vraag te beantwoorden met een AI-begeleider

7Wat zijn PHP enums, inclusief pure en backed enums, en hoe verbeteren ze domeinmodellering?

PHP enums definiëren een gesloten verzameling van benoemde cases voor een type, zoals orderstatussen of gebruikersrollen. Pure enums hebben alleen benoemde cases; backed enums geven elke case een unieke string- of int-waarde, wat nuttig is voor opslag, API's of interoperabiliteit. Enums verbeteren de domeinmodellering door kwetsbare ruwe strings, integers of constanten te vervangen door typeveilige, zelfdocumenterende waarden. PHP enums kunnen ook methoden hebben en interfaces implementeren; alle enums ondersteunen het oplijsten van hun cases, en backed enums ondersteunen conversie naar en van hun scalaire waarden.

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);
Probeer deze vraag te beantwoorden met een AI-begeleider

Medior vragen

8Hoe drukken scalaire, nullable, union, intersectie en DNF (Disjunctive Normal Form) typen PHP API (Application Programming Interface)-contracten uit, en wanneer moet elk worden gebruikt?

PHP-type declaraties beschrijven de waarden die een API accepteert of retourneert. Scalaire typen zoals `int`, `float`, `string` en `bool` worden gebruikt wanneer precies één primitief type wordt verwacht. Nullable typen, geschreven als `?T` of `T|null`, staan een waarde van type `T` of `null` toe. Union typen zoals `A|B` staan een van meerdere alternatieven toe. Intersectie typen zoals `A&B` vereisen dat een waarde aan alle vermelde typen voldoet, vaak meerdere interfaces. DNF (Disjunctive Normal Form) typen combineren unions en intersecties, bijvoorbeeld `(A&B)|C`, om complexere contracten uit te drukken. Elk type moet worden gebruikt wanneer het overeenkomt met de werkelijke API-belofte, om onnodig brede of verwarrende typen te voorkomen.

<?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
}
Probeer deze vraag te beantwoorden met een AI-begeleider

9Wat is het verschil tussen `callable`, `Closure`, eersteklas oproepbare objecten (first-class callables), aanroepbare objecten (invokable objects) en oproepbare arrays (callable arrays) in PHP?

`callable` is een breed type/contract voor elke waarde die PHP kan aanroepen, zoals een string met functienaam, een `Closure`, een aanroepbaar object, of een methode-referentie-array zoals `[$object, 'method']` of `[ClassName::class, 'method']`. Een `Closure` is een concreet anoniem-functieobject en is zelf oproepbaar. De syntaxis voor eersteklas oproepbare objecten, zoals `$obj->method(...)` of `strlen(...)`, creëert een `Closure` uit een aanroepbare expressie. Een aanroepbaar object is oproepbaar omdat het `__invoke()` definieert. Oproepbare arrays zijn de traditionele twee-elementen methode-referentievorm. `is_callable()` kan worden gebruikt om de aanroepbaarheid tijdens uitvoering te controleren.

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;
}
Probeer deze vraag te beantwoorden met een AI-begeleider

10Hoe gaat PHP om met integer-overflow, de precisie van floating-pointgetallen en exacte decimaalberekeningen?

PHP integers zijn platform-afhankelijk van grootte, met limieten die worden weergegeven door constanten zoals `PHP_INT_MAX`. PHP behoudt niet automatisch native integer-resultaten als integers met willekeurige precisie na een overflow; overlopende integer-arithmetiek kan `float` worden of anderszins exacte integer-semantiek verliezen. PHP floats zijn binaire floating-pointwaarden, dus veel decimale breuken, zoals `0.1`, zijn benaderend. Voor exacte decimaal- of geldberekeningen, vermijd floats en gebruik een exacte aanpak zoals gehele getallen als 'kleinere eenheden' waar van toepassing, of hulpmiddelen voor vaste/willekeurige precisie-decimalen zoals BCMath. GMP is nuttig voor integer-arithmetiek met willekeurige precisie.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
Probeer deze vraag te beantwoorden met een AI-begeleider

11Leg PHP-referenties (&), copy-on-write, en hoe referenties onverwachte neveneffecten kunnen creëren uit.

PHP-variabelen worden intern gerepresenteerd door zvals. Voor waarden zoals arrays en strings gebruikt PHP copy-on-write: het toewijzen van `$b = $a` deelt gewoonlijk dezelfde onderliggende waarde totdat één variabele wordt gewijzigd, waarna PHP deze scheidt/kopieert. Een referentie gemaakt met `&` maakt variabelen aliassen naar dezelfde container, dus het wijzigen van de ene wijzigt de andere en kan het scheidingsgedrag omzeilen dat men verwacht bij normale toewijzing. Doorgeven via referentie stelt een functie in staat de variabele van de aanroeper te wijzigen. Referenties kunnen verrassende neveneffecten veroorzaken, vooral bij `foreach ($array as &$value)`: de lusvariabele blijft een referentie naar het laatste element na de lus, dus het later hergebruiken ervan kan dat element per ongeluk overschrijven, tenzij u `unset($value)` gebruikt.

$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);
Probeer deze vraag te beantwoorden met een AI-begeleider

12Hoe werken objectverwijzingen, toewijzing, `clone`, `__clone`, ondiepe kopieën en diepe kopieën in PHP?

In PHP slaat een objectvariabele een verwijzing naar een object op, niet een volledige kopie van het object. Wanneer `$b = $a` wordt toegewezen, wordt de verwijzing gekopieerd, zodat beide variabelen naar hetzelfde object verwijzen; wijzigingen aan het object via de ene variabele zijn zichtbaar via de andere, zonder gebruik te maken van `&`. `clone $a` creëert een nieuwe objectinstantie met een ondiepe kopie van de eigenschappen van het oorspronkelijke object. Als een eigenschap zelf een object is, wijst de eigenschap van het gekloonde object nog steeds naar hetzelfde geneste object, tenzij je het expliciet kloont. `__clone()` wordt aangeroepen op het nieuwe object na de ondiepe kopie en is de plek om ID's te resetten, resources los te koppelen of een diepe kopie van geneste objecten uit te voeren. Een diepe kopie betekent het recursief kopiëren van de geneste veranderlijke objecten die niet gedeeld moeten worden.

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;
Probeer deze vraag te beantwoorden met een AI-begeleider

Senior vragen

13Hoe zou u problemen met uitputting van databaseverbindingen oplossen en verbindingbeheer ontwerpen voor PHP-FPM en langlopende workers?

Om problemen met verbindinguitputting op te lossen, zou ik de limieten voor databaseverbindingen en de huidige sessies vergelijken met de totale PHP-concurrentie: elk actief PHP-FPM-childproces kan zijn eigen databaseverbinding vasthouden, en meerdere applicatiepools, hosts, cronjobs, wachtrij-workers, migraties en deployments vermenigvuldigen de vraag. Ik zou database-sessieoverzichten, verbindingsstatussen, inactieve-in-transactie-sessies, trage queries, vergrendelingswachten, foutlogboeken, poolmetrieken en recente verkeers- of worker-aantalwijzigingen inspecteren. Voor PHP-FPM: houd `max_children` en de concurrentie per host binnen het databasebudget, gebruik korte, aanvraag-specifieke transacties/verbindingen, commit of draai snel terug, en vermijd het vasthouden van verbindingen tijdens trage externe oproepen. Persistente PDO-verbindingen kunnen de verbindingsoverhead verminderen, maar kunnen veel inactieve verbindingen per FPM-proces vasthouden en de sessiestatus behouden, dus ze vereisen voorzichtigheid. Een pooler of proxy zoals PgBouncer kan meerdere PHP-clients multiplexen naar minder databaseserververbindingen wanneer de applicatie compatibel is. Langlopende workers hebben expliciete afhandeling van de verbindingslevenscyclus nodig: gezondheidscontroles, opnieuw verbinden na inactiviteitstime-outs/herstarts/failover/forks, sessiestatus resetten en alleen veilige of idempotente taken opnieuw proberen.

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.
Probeer deze vraag te beantwoorden met een AI-begeleider

14Hoe zou u leesreplica's, lees-/schrijfopsplitsing, replicatievertraging en failover van de primaire database in PHP afhandelen?

Het afhandelen van leesreplica's en failover in PHP vereist het opsplitsen van lees- en schrijfverkeer, het mitigeren van replicatievertraging en het beheren van verbindingsonderbrekingen tijdens failover. Op applicatieniveau leiden databaseconfiguratie of middleware leesqueries (`SELECT`) naar leesreplica's en schrijfqueries (`INSERT`, `UPDATE`, `DELETE`) naar de primaire instantie (alternatief beheerd via databaseproxy's zoals ProxySQL of AWS RDS Proxy). Omdat replicatie asynchroon is, kan replicatievertraging leiden tot verouderde leesacties. Om een 'read-your-writes'-consistentie te garanderen, moet de applicatie leesacties binnen een actieve transactie naar de primaire instantie leiden, en tijdelijk daaropvolgende leesacties naar de primaire instantie sturen voor een specifieke gebruikerssessie of request-levenscyclus direct na een schrijfactie. Tijdens failover van de primaire instantie promoveren moderne databaseclusters een replica en werken DNS-/proxy-endpoints bij. In PHP moet de verbindingslaag verbindingsverlies en alleen-lezen fouten opvangen, verouderde PDO-sockets verwijderen en gecontroleerde herhalingen toepassen met exponentiële back-off.

<?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',
    ],
];
Probeer deze vraag te beantwoorden met een AI-begeleider

15Hoe worden databasemigraties en schemawijzigingen veilig geïmplementeerd met nul of minimale downtime in PHP-applicaties?

Het implementeren van databasemigraties met nul downtime in PHP-applicaties vereist het ontkoppelen van schema-evolutie van implementaties van applicatiecode met behulp van het Expand and Contract (Parallelle Uitvoering) patroon, samen met niet-blokkerende online DDL (Data Definition Language)-praktijken. In de Expand-fase worden achterwaarts compatibele schemawijzigingen toegepast (bijv. het toevoegen van nullable kolommen, nieuwe tabellen of concurrente indexen met behulp van tools zoals `pt-online-schema-change`, `gh-ost`, of native online DDL in MySQL/PostgreSQL). De applicatie wordt vervolgens geïmplementeerd om dubbel schrijven te ondersteunen (schrijven naar zowel oude als nieuwe schemastructuren) terwijl uit beide wordt gelezen. Historische records worden daarna asynchroon aangevuld in gereguleerde achtergrondbatches om te voorkomen dat tabellen worden vergrendeld of replicatievertraging wordt veroorzaakt. In de Contract-fase, zodra dataconsistentie is gevalideerd en lezingen volledig zijn overgeschakeld naar de nieuwe structuur, verwijdert een code-implementatie verwijzingen naar het oude schema. Tot slot verwijdert een opschoonmigratie verouderde kolommen of tabellen. Veilige rollback vereist dat elke tussenliggende schemastatus volledig compatibel blijft met zowel de vorige als de inkomende applicatieversies tijdens rollende releases.

<?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
Probeer deze vraag te beantwoorden met een AI-begeleider