Préparation aux entretiens PHP

Questions d'entretien PHP

15 questions d'entretien PHP backend sélectionnées, groupées par niveau d'ancienneté. Utilisez-les pour réviser les fondamentaux, les compromis pratiques et le raisonnement de production de niveau senior.

Démarrer un entretien IA PHPAucune carte bancaire requise. 1 session gratuite disponible.
Préparation aux entretiens techniques en anglaisUn mode où les non-natifs peuvent s'entraîner à passer des entretiens techniques.

Questions Junior

1Comment le système de types de PHP distingue-t-il typage faible et typage strict, et que change declare(strict_types=1) ?

PHP autorise normalement les conversions scalaires compatibles pour les types déclarés des paramètres et des retours, par exemple une chaîne numérique passée à un paramètre int. declare(strict_types=1) active des contrôles scalaires stricts : un argument ou une valeur de retour incompatible provoque TypeError, bien qu'un int reste accepté pour float. Pour les arguments des fonctions définies par l'utilisateur, le fichier appelant détermine la rigueur du contrôle ; le fichier définissant la fonction détermine celle des retours. Les appels provenant des fonctions internes de PHP ont leurs propres particularités et ne sont pas simplement régis par le réglage strict_types de l'appelant extérieur. La directive doit apparaître au début de son fichier PHP. Elle ne rend pas PHP statiquement typé et ne désactive pas les conversions effectuées par les opérateurs dans tout le langage.

// 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';
}
?>
Essayer de répondre à cette question avec un coach IA

2Explique la différence entre == et === en PHP, notamment les pièges liés aux chaînes numériques, aux tableaux, aux objets et à in_array().

En PHP, == effectue une comparaison non stricte, avec conversion de type lorsqu'elle s'applique ; === exige des types et des valeurs identiques sans conversion implicite des scalaires. Les chaînes numériques peuvent être comparées numériquement : évite donc les comparaisons non strictes lorsqu'une correspondance exacte tenant compte du type est nécessaire. Deux tableaux sont égaux avec == s'ils contiennent les mêmes paires clé/valeur selon les règles de comparaison, quel que soit l'ordre des paires. === exige aussi le même ordre et les mêmes types de clés et de valeurs. Pour les objets, == compare les propriétés d'objets de la même classe ; === exige la même instance. in_array est non strict par défaut ; passe true comme troisième argument pour une vérification stricte. Le comportement des comparaisons non strictes entre nombres et chaînes a changé dans PHP 8 : évite de te fonder sur des exemples historiques de conversion sans préciser la version.

<?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
Essayer de répondre à cette question avec un coach IA

3En quoi mixed, void et never diffèrent-ils comme types de retour PHP, et quelles garanties donnent-ils ?

mixed autorise toute valeur de retour, y compris null. void déclare qu'une fonction ne renvoie pas de valeur significative : elle peut atteindre sa fin ou utiliser return;, mais ne peut pas renvoyer une expression. Un appel normal à une fonction void produit null. never, disponible depuis PHP 8.1, signifie que la fonction ne rend jamais normalement la main à son appelant. Elle peut lever une exception, terminer l'exécution avec exit ou rester dans une boucle infinie ; elle n'est pas obligée de finir par terminer le processus. Une fonction never ne peut contenir aucune instruction return. La différence essentielle est qu'une fonction void peut se terminer normalement et laisser l'appelant continuer, tandis qu'une fonction never n'a pas de continuation normale.

<?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';
Essayer de répondre à cette question avec un coach IA

4Qu'est-ce que iterable en PHP et quel est son lien avec array, Traversable, Iterator et Generator ?

iterable signifie array|Traversable. Generator implémente Iterator, qui étend Traversable : il satisfait donc iterable. Les classes définies par l'utilisateur offrent un comportement Traversable par Iterator ou IteratorAggregate plutôt qu'en implémentant directement Traversable dans une classe concrète. Iterator expose les méthodes qui contrôlent l'itération ; IteratorAggregate fournit un itérateur. Une fonction génératrice contenant yield renvoie un Generator. Une distinction est importante : foreach peut aussi parcourir les propriétés visibles d'un objet ordinaire qui n'implémente pas Traversable, mais cela ne lui permet pas pour autant de satisfaire une déclaration de paramètre iterable.

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());
Essayer de répondre à cette question avec un coach IA

5Quelles sont les règles de visibilité public, protected et private en PHP, et comment interagissent-elles avec l'héritage et les traits ?

Les membres public sont accessibles partout où l'objet ou la classe est accessible. Les membres protected sont accessibles dans la classe qui les déclare et dans sa hiérarchie liée de classes parentes et enfants, mais pas depuis du code externe quelconque. Les membres private sont accessibles dans leur classe déclarante ; une sous-classe ne peut pas utiliser un membre privé du parent comme une interface héritée. Une méthode ordinaire qui en redéfinit une autre ne peut pas réduire la visibilité héritée. Les constructeurs ont toutefois des règles d'héritage particulières et ne doivent pas être présentés comme un exemple inconditionnel de cette règle. Les membres d'un trait sont incorporés à la classe utilisatrice et suivent ses règles de visibilité. L'adaptation as peut créer un alias, modifier la visibilité d'une méthode du trait, ou les deux ; l'ajout d'un alias ne supprime pas à lui seul le nom d'origine.

<?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
Essayer de répondre à cette question avec un coach IA

6Comment fonctionnent les traits en PHP, et comment résoudre les conflits lorsque plusieurs traits définissent la même méthode ?

Les traits permettent la réutilisation horizontale du code : une classe importe leurs membres avec use, et un trait ne peut pas être instancié seul. Une méthode déclarée dans la classe prévaut sur les méthodes des traits, qui prévalent à leur tour sur les méthodes héritées du parent. Si plusieurs traits fournissent la même méthode et qu'une méthode de la classe ne résout pas déjà le conflit, utilise insteadof pour choisir l'implémentation. Utilise as pour donner un nom supplémentaire à une méthode de trait et, éventuellement, une visibilité différente. Un alias ne renomme ni ne supprime la méthode d'origine. Résoudre les collisions et garder une interface publique claire sont deux responsabilités distinctes.

<?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
Essayer de répondre à cette question avec un coach IA

7Que sont les énumérations PHP (PHP enums), y compris les énumérations pures (pure enums) et adossées (backed enums), et comment améliorent-elles la modélisation de domaine ?

Une énumération PHP définit un ensemble fermé de cas nommés, comme des statuts de commande. Une énumération pure se limite à ces cas. Une énumération adossée associe à chaque cas une valeur unique, entière ou textuelle selon le type choisi, utile pour le stockage ou les échanges. Un type d’énumération explicite remplace les chaînes ou entiers utilisés directement et permet de vérifier le contrat. Les énumérations peuvent définir des méthodes, implémenter des interfaces et lister leurs cas. Les énumérations adossées permettent aussi de retrouver un cas à partir de sa valeur scalaire.

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);
Essayer de répondre à cette question avec un coach IA

Questions Intermédiaires

8Comment les types scalaires, nullables, union, intersection et DNF expriment-ils les contrats des interfaces PHP, et quand utiliser chacun d'eux ?

Les déclarations PHP décrivent les valeurs acceptées et renvoyées. Les déclarations scalaires comme int, float, string et bool sélectionnent une catégorie de valeurs ; l'autorisation de conversions scalaires compatibles dépend également de strict_types et du contexte. ?T ou T|null admet null. Les unions, disponibles depuis PHP 8.0, permettent des alternatives comme int|string. Les intersections pures, disponibles depuis PHP 8.1, imposent qu'un objet satisfasse tous les types de classes ou d'interfaces indiqués, comme Cacheable&JsonSerializable ; int&string n'est pas une intersection valide. Les types DNF (forme normale disjonctive), disponibles depuis PHP 8.2, combinent des alternatives et des intersections entre parenthèses, par exemple (Cacheable&JsonSerializable)|string. Choisis le contrat compréhensible le plus précis qui représente les entrées valides, sans ajouter d'alternatives inutiles.

<?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
}
Essayer de répondre à cette question avec un coach IA

9Quelle est la différence entre callable, Closure, les callables de première classe, les objets invocables et les tableaux callable en PHP ?

callable décrit des valeurs invocables, notamment les chaînes contenant des noms de fonctions, les objets Closure, les références à des méthodes accessibles et les objets dotés de __invoke. Closure est un objet invocable concret : il peut provenir d'une fonction anonyme ou d'une fonction ou méthode nommée convertie en Closure. La syntaxe des callables de première classe, comme strlen(...) ou $object->method(...), crée une Closure sans invoquer la cible. Un tableau callable [$object, 'method'] fait référence à une méthode d'instance accessible ; [ClassName::class, 'method'] nécessite une méthode statique appropriée et accessible dans les versions modernes de PHP. is_callable vérifie la possibilité d'appel dans la portée concernée. callable est un contrat large, tandis que Closure est une représentation spécifique.

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;
}
Essayer de répondre à cette question avec un coach IA

10Comment PHP gère-t-il le dépassement d'entier, la précision des nombres à virgule flottante et les calculs décimaux exacts ?

La taille des entiers dépend de la plateforme, avec des bornes comme `PHP_INT_MAX`. Un calcul entier qui dépasse ces bornes peut produire un `float` et perdre l’exactitude entière. Les flottants étant binaires, des fractions décimales comme `0.1` sont approximatives. Pour les montants ou d’autres calculs décimaux exacts, utilisez des unités monétaires mineures entières si elles conviennent, ou un outil décimal comme BCMath. GMP fournit des entiers de précision arbitraire.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
Essayer de répondre à cette question avec un coach IA

11Expliquez les références PHP (`&`), le mécanisme de copie à l'écriture (*copy-on-write*) et comment les références peuvent créer des effets secondaires inattendus.

PHP représente les variables par des structures internes `zval`. Pour des tableaux ou chaînes, `$b = $a` permet souvent de partager la représentation jusqu’à une écriture, moment où PHP sépare les valeurs. Avec `&`, les variables deviennent des alias du même conteneur et une modification touche les deux. Une fonction peut ainsi modifier une variable de l’appelant. Après `foreach ($array as &$value)`, `$value` reste liée au dernier élément. Faites `unset($value)` avant de réutiliser cette variable pour éviter d’écraser l’élément.

$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);
Essayer de répondre à cette question avec un coach IA

12Comment fonctionnent les descripteurs d’objet, l’affectation, `clone`, `__clone()` et les copies superficielle et profonde en PHP ?

Une variable d’objet contient un descripteur vers l’objet. `$b = $a` copie ce descripteur : les mutations de l’objet sont visibles via les deux variables, mais réaffecter `$b` ne réaffecte pas `$a`. `clone $a` crée une instance distincte avec une copie superficielle des propriétés. Une propriété qui contient un objet continue donc à désigner cet objet. PHP appelle `__clone()` sur la nouvelle instance après cette copie. Cette méthode peut réinitialiser des identifiants ou cloner les objets mutables à séparer.

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;
Essayer de répondre à cette question avec un coach IA

Questions Senior

13Comment dépanneriez-vous l'épuisement des connexions à la base de données et concevriez-vous la gestion des connexions pour PHP-FPM et les workers de longue durée ?

Comparez la limite de connexions et les sessions ouvertes à la concurrence totale : processus FPM, groupes et hôtes, tâches planifiées, consommateurs de files, migrations et déploiements. Examinez l’état des sessions, notamment celles qui restent inactives dans une transaction, ainsi que les requêtes lentes, attentes de verrous, journaux et métriques de connexion. Vérifiez les changements récents de trafic ou de capacité. Bornez `pm.max_children` et la concurrence par hôte selon le budget de la base. Gardez les transactions courtes, validez ou annulez rapidement et libérez la connexion avant un appel externe lent si le contrat le permet. Les connexions PDO persistantes économisent l’établissement, mais peuvent retenir des sessions inactives et leur état. Un intermédiaire compatible comme PgBouncer peut mutualiser les connexions serveur. Pour les processus durables, vérifiez la connexion, reconnectez après coupure, bascule ou création d’un processus enfant, nettoyez l’état de session et ne rejouez que les opérations sûres.

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.
Essayer de répondre à cette question avec un coach IA

14Comment gérerais-tu les réplicas de lecture, la séparation lectures/écritures, le retard de réplication et le basculement de la base principale en PHP ?

Sépare les connexions à la base principale et aux réplicas selon la sémantique des opérations, et pas seulement selon que le SQL commence par SELECT : les lectures verrouillantes, les écritures et les transactions exigeant la cohérence de la base principale doivent l'utiliser. Les transactions en lecture seule peuvent utiliser un réplica adapté si leurs exigences de cohérence le permettent. La réplication asynchrone peut être en retard. Le réglage sticky de Laravel dirige les lectures suivant une écriture vers la connexion d'écriture pendant la requête courante ; maintenir cette affinité pendant une durée fixe entre requêtes atténue seulement le problème, sans garantir inconditionnellement la lecture de ses propres écritures. Pour obtenir cette garantie, lis sur la base principale ou attends qu'un réplica ait appliqué la position d'écriture requise, avec un délai maximal et un repli sur la principale. Les proxys diffèrent : RDS Proxy utilise des points de connexion distincts pour lecteurs et écrivains et n'est pas un répartiteur automatique général de chaque instruction entre lecture et écriture. Lors d'un basculement, abandonne les connexions périmées et reconnecte-toi avec une temporisation progressive bornée. La perte d'une réponse de validation laisse le résultat incertain : ne rejoue pas aveuglément des écritures non idempotentes ; utilise des clés d'idempotence ou vérifie d'abord le résultat.

<?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',
    ],
];
Essayer de répondre à cette question avec un coach IA

15Comment déployer en sécurité des migrations de base de données et des changements de schéma dans une application PHP avec une interruption nulle ou minimale ?

Utilise des changements d'expansion puis de contraction qui tolèrent les anciennes et les nouvelles versions de l'application pendant un déploiement progressif. Ajoute d'abord des structures compatibles, puis déploie du code capable d'utiliser les deux représentations. Si une double écriture est nécessaire, assure sa cohérence et conçois des reprises des données historiques à débit limité et reprenables, sans écraser les écritures plus récentes. Valide les données copiées et bascule les lectures avant de supprimer les anciens chemins de code. Le DDL (langage de définition de données) en ligne réduit certains risques de blocage, mais n'est pas universellement sans verrous : examine les verrous propres à la base et à sa version, les restrictions transactionnelles, la consommation de ressources et l'effet sur la réplication. Par exemple, CREATE INDEX CONCURRENTLY dans PostgreSQL a des restrictions et peut laisser un index invalide après un échec. Ne supprime les anciennes colonnes qu'après disparition des anciennes instances applicatives et expiration de la période requise de retour arrière. Après une contraction destructive, revenir à du code nécessitant les données supprimées n'est plus automatiquement sûr.

<?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
Essayer de répondre à cette question avec un coach IA

Préparez vos entretiens sur mobile : PHP

Répondez à voix haute et recevez les retours de l’IA dans l’app Android.

Télécharger EngineerSpeak sur Google Play
Scannez pour télécharger