Preparación para entrevista de PHP

Preguntas de entrevista de PHP

15 preguntas seleccionadas para entrevistas backend de PHP, agrupadas por nivel de experiencia. Úsalas para repasar fundamentos, compromisos prácticos y el razonamiento de producción a nivel senior.

Iniciar una entrevista de IA de PHPNo se requiere tarjeta de crédito. 1 sesión gratuita disponible.
Práctica de entrevistas técnicas en inglésDiseñado para hablantes no nativos que desean practicar entrevistas técnicas en inglés.

Preguntas para Junior

1¿Cómo distingue el sistema de tipos de PHP entre tipado débil y estricto, y qué cambia cuando se habilita `declare(strict_types=1)`?

PHP es débil/coercitivo por defecto para las declaraciones de tipos escalares: cuando una función declara tipos escalares de parámetros o de retorno, PHP puede convertir valores compatibles, como cadenas numéricas a números, en lugar de rechazarlos. `declare(strict_types=1)` hace que la verificación de tipos escalares sea estricta para ese archivo, por lo que los valores escalares incompatibles no son coercidos y se lanza un `TypeError`. La directiva tiene ámbito de archivo: para los argumentos de funciones definidas por el usuario, el archivo del llamador controla la estrictez; para las sentencias `return`, el archivo donde se define la función controla la estrictez del valor de retorno. No hace que PHP sea globalmente estricto ni deshabilita todas las conversiones en el lenguaje.

<?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
Probar responder esta pregunta con un coach de IA

2Explicar la diferencia entre `==` y `===` en PHP, incluyendo las trampas con cadenas numéricas, arrays, objetos y `in_array()`.

`==` realiza una comparación flexible, por lo que PHP puede manipular tipos antes de comparar; `===` realiza una comparación estricta, requiriendo tanto el mismo valor como el mismo tipo sin coerción. Las cadenas numéricas son una trampa común porque pueden tratarse como números en comparaciones flexibles. Los arrays y objetos tienen sus propias reglas de comparación: los arrays se comparan por claves y valores, mientras que `==` en objetos compara objetos de la misma clase por propiedades y `===` en objetos requiere la misma instancia exacta. `in_array()` usa comparación flexible por defecto, por lo que se debe pasar `true` como tercer argumento cuando el tipo debe coincidir.

<?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
Probar responder esta pregunta con un coach de IA

3¿En qué se diferencian `mixed`, `void` y `never` como tipos de retorno de PHP, y qué garantías ofrecen?

Como tipos de retorno, `mixed`, `void` y `never` hacen diferentes promesas. `mixed` significa que la función puede devolver cualquier valor, incluyendo `null`. `void` significa que la función no está destinada a devolver un valor significativo; puede usar `return;` pero no puede devolver una expresión. `never` significa que la función no regresa normalmente a su llamador: siempre debe lanzar una excepción, salir o terminar el flujo de control de otra manera. `never` es más estricto que `void` porque la ejecución no puede continuar después de una llamada normal a una función con tipo de retorno `never`.

<?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';
Probar responder esta pregunta con un coach de IA

4¿Qué es `iterable` en PHP y cómo se relaciona con `array`, `Traversable`, `Iterator` y `Generator`?

En PHP, `iterable` es una declaración de tipo que significa "un `array` o un objeto que implementa `Traversable`". `Traversable` es la interfaz marcadora base para objetos utilizables por `foreach`; el código de usuario normalmente implementa `Iterator` o `IteratorAggregate`, que extienden `Traversable`. Un `Generator` es el objeto devuelto por una función generadora que usa `yield`; implementa `Iterator`, por lo que también es `Traversable` y es aceptado donde se requiera `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());
Probar responder esta pregunta con un coach de IA

5¿Cuáles son las reglas de visibilidad `public`, `protected` y `private` en PHP, y cómo interactúan con la herencia y los traits?

En PHP, los miembros `public` son accesibles desde cualquier lugar, los miembros `protected` son accesibles desde la clase que los declara y sus subclases, y los miembros `private` son accesibles solo desde la clase que los declara. En la herencia, las subclases pueden usar miembros `public` y `protected` heredados, pero los miembros `private` de un padre no son accesibles como API heredada. Al sobrescribir métodos, una clase hija generalmente no puede reducir la visibilidad; por ejemplo, un método `public` del padre debe permanecer `public`. Los traits se componen en la clase que los utiliza, y los miembros del trait siguen las reglas de visibilidad de PHP. La visibilidad de los métodos del trait también puede adaptarse o ser aliada con `as` al importar el 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
Probar responder esta pregunta con un coach de IA

6¿Cómo funcionan los traits en PHP, y cómo se resuelven los conflictos cuando múltiples traits definen el mismo método?

Los traits (rasgos) son el mecanismo de PHP para la reutilización de código horizontal: una clase puede usar uno o más traits, y los miembros del trait se incorporan a la clase. Los traits no son clases independientes y no pueden ser instanciados directamente. Si múltiples traits proporcionan métodos con el mismo nombre, PHP requiere una resolución explícita de conflictos. El operador `insteadof` elige qué método de trait se utiliza para el nombre en conflicto. El operador `as` puede crear un alias para un método de trait y también puede cambiar la visibilidad del método importado. Los métodos de clase sobrescriben a los métodos de trait, y los métodos de trait tienen precedencia sobre los métodos parent heredados.

<?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
Probar responder esta pregunta con un coach de IA

7¿Qué son las enums de PHP, incluyendo las enums puras y las enums respaldadas, y cómo mejoran el modelado de dominio?

Las enums de PHP definen un conjunto cerrado de casos con nombre para un tipo, como estados de pedido o roles de usuario. Las enums puras solo tienen casos con nombre; las enums respaldadas asignan a cada caso un valor único de cadena (string) o entero (int), útil para el almacenamiento, las API (Application Programming Interface) o la interoperabilidad. Las enums mejoran el modelado de dominio al reemplazar cadenas (strings) sin procesar frágiles, enteros o constantes con valores seguros en cuanto a tipos y autodocumentados. Las enums de PHP también pueden tener métodos e implementar interfaces; todas las enums admiten la lista de sus casos, y las enums respaldadas admiten la conversión hacia y desde sus valores escalares.

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);
Probar responder esta pregunta con un coach de IA

Preguntas para Middle

8Cómo expresan los tipos escalares, anulables, de unión, de intersección y DNF (Disjunctive Normal Form) los contratos de la API (Application Programming Interface) de PHP, y cuándo se debe usar cada uno?

Las declaraciones de tipo de PHP describen los valores que una API acepta o devuelve. Los tipos escalares como `int`, `float`, `string` y `bool` se utilizan cuando se espera exactamente un tipo primitivo. Los tipos anulables, escritos `?T` o `T|null`, permiten un valor de tipo `T` o `null`. Los tipos de unión como `A|B` permiten una de varias alternativas. Los tipos de intersección como `A&B` requieren que un valor satisfaga todos los tipos listados, comúnmente múltiples interfaces. Los tipos DNF (Disjunctive Normal Form) combinan uniones e intersecciones, por ejemplo `(A&B)|C`, para expresar contratos más complejos. Cada uno debe usarse cuando coincida con la promesa real de la API, evitando tipos innecesariamente amplios o confusos.

<?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
}
Probar responder esta pregunta con un coach de IA

9¿Cuál es la diferencia entre `callable`, `Closure`, callables de primera clase, objetos invocables y arrays `callable` en PHP?

`callable` es un tipo/contrato amplio para cualquier valor que PHP pueda invocar, como una cadena de nombre de función, un `Closure`, un objeto invocable, o un array de referencia a método como `[$object, 'method']` o `[ClassName::class, 'method']`. Un `Closure` es un objeto de función anónima concreto y es en sí mismo `callable`. La sintaxis de `callable` de primera clase, como `$obj->method(...)` o `strlen(...)`, crea un `Closure` a partir de una expresión `callable`. Un objeto invocable es `callable` porque define `__invoke()`. Los arrays `callable` son la forma tradicional de referencia a método de dos elementos. Se puede usar `is_callable()` para comprobar la capacidad de invocación en tiempo de ejecución.

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;
}
Probar responder esta pregunta con un coach de IA

10¿Cómo maneja PHP el desbordamiento de enteros, la precisión de punto flotante y los cálculos decimales exactos?

Los enteros de PHP tienen un tamaño que depende de la plataforma, con límites expuestos por constantes como `PHP_INT_MAX`. PHP no mantiene automáticamente los resultados de enteros nativos como enteros de precisión arbitraria después de un desbordamiento; la aritmética de enteros con desbordamiento puede convertirse en un `float` o perder la semántica exacta de los enteros. Los números flotantes de PHP son valores de punto flotante binarios, por lo que muchas fracciones decimales, como `0.1`, son aproximadas. Para cálculos decimales exactos o de dinero, evite los flotantes y utilice un enfoque exacto como unidades menores enteras cuando sea apropiado, o herramientas decimales de precisión fija/arbitraria como BCMath. GMP es útil para la aritmética de enteros de precisión arbitraria.

var_dump(0.1 + 0.2 === 0.3);
printf('%.17f\n', 0.1 + 0.2);
Probar responder esta pregunta con un coach de IA

11Explique las referencias de PHP (`&`), la copia en escritura (*copy-on-write*) y cómo las referencias pueden crear efectos secundarios inesperados.

Las variables de PHP están representadas internamente por *zvals*. Para valores como arrays y strings, PHP utiliza la copia en escritura (*copy-on-write*): asignar `$b = $a` usualmente comparte el mismo valor subyacente hasta que una variable es modificada, momento en el cual PHP la separa/copia. Una referencia creada con `&` convierte las variables en alias del mismo contenedor, por lo que cambiar una cambia la otra y puede eludir el comportamiento de separación que se espera de una asignación normal. Pasar por referencia permite que una función modifique la variable del llamador. Las referencias pueden causar efectos secundarios sorprendentes, especialmente con `foreach ($array as &$value)`: la variable del bucle sigue siendo una referencia al último elemento después del bucle, por lo que reutilizarla más tarde puede sobrescribir accidentalmente ese elemento a menos que se use `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);
Probar responder esta pregunta con un coach de IA

12¿Cómo funcionan los manejadores de objetos, la asignación, la clonación (`clone`), `__clone`, la copia superficial y la copia profunda en PHP?

En PHP, una variable de objeto almacena una referencia a un objeto, no una copia completa del objeto. Asignar `$b = $a` copia la referencia, por lo que ambas variables se refieren al mismo objeto; modificar el objeto a través de cualquiera de las variables es visible a través de la otra, sin usar `&`. `clone $a` crea una nueva instancia de objeto con una copia superficial de las propiedades del objeto original. Si una propiedad es en sí misma un objeto, la propiedad del objeto clonado seguirá apuntando al mismo objeto anidado a menos que lo clones explícitamente. Se llama a `__clone()` en el nuevo objeto después de la copia superficial y es el lugar para restablecer IDs, desvincular recursos o realizar una copia profunda de objetos anidados. Una copia profunda significa copiar recursivamente los objetos mutables anidados que no deben compartirse.

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;
Probar responder esta pregunta con un coach de IA

Preguntas para Senior

13¿Cómo solucionarías el agotamiento de conexiones a la base de datos y diseñarías la gestión de conexiones para PHP-FPM y workers de larga duración?

Para solucionar el agotamiento de conexiones, compararía los límites de conexiones de la base de datos y las sesiones actuales con la concurrencia total de PHP: cada proceso hijo activo de PHP-FPM (FastCGI Process Manager) puede mantener su propia conexión a la base de datos, y múltiples pools de aplicaciones, hosts, tareas cron, workers de cola, migraciones y despliegues multiplican la demanda. Inspeccionaría las vistas de sesión de la base de datos, los estados de conexión, las sesiones inactivas en transacción, las consultas lentas, las esperas de bloqueo, los registros de errores, las métricas del pool y los cambios recientes de tráfico o de número de workers. Para PHP-FPM, mantendría `max_children` y la concurrencia por host dentro del presupuesto de la base de datos, usaría transacciones/conexiones de ámbito de solicitud cortas, confirmaría (commit) o revertiría (roll back) rápidamente, y evitaría mantener conexiones durante llamadas externas lentas. Las conexiones PDO persistentes pueden reducir la sobrecarga de conexión, pero pueden mantener muchas conexiones inactivas por proceso FPM y retener el estado de la sesión, por lo que requieren precaución. Un 'pooler' o 'proxy' como PgBouncer puede multiplexar muchos clientes PHP en menos conexiones de servidor de base de datos cuando la aplicación es compatible. Los workers de larga duración necesitan un manejo explícito del ciclo de vida de la conexión: comprobaciones de estado, reconexión después de tiempos de espera de inactividad/reinicios/conmutaciones por error (failover)/bifurcaciones (forks), restablecimiento del estado de la sesión y reintentar solo el trabajo seguro o idempotente.

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.
Probar responder esta pregunta con un coach de IA

14¿Cómo manejaría las réplicas de lectura, la separación de lectura/escritura, el retraso de replicación y la conmutación por error de la base de datos principal en PHP?

El manejo de réplicas de lectura y la conmutación por error en PHP requiere dividir el tráfico de lectura y escritura, mitigar el retraso de replicación y gestionar las interrupciones de conexión durante la conmutación por error. A nivel de aplicación, la configuración de la base de datos o el *middleware* dirigen las consultas de lectura (`SELECT`) a las réplicas de lectura y las consultas de escritura (`INSERT`, `UPDATE`, `DELETE`) a la instancia principal (alternativamente gestionado a través de *proxies* de base de datos como ProxySQL o AWS RDS Proxy). Dado que la replicación es asíncrona, el retraso de replicación puede causar lecturas desactualizadas. Para garantizar la consistencia 'leer-tus-escrituras', la aplicación debe dirigir las lecturas dentro de una transacción activa a la principal y, temporalmente, mantener las lecturas posteriores en la principal para una sesión de usuario o ciclo de vida de solicitud específico inmediatamente después de una escritura. Durante la conmutación por error de la principal, los clústeres de bases de datos modernos promueven una réplica y actualizan los puntos finales de DNS/*proxy*. En PHP, la capa de conexión debe detectar la pérdida de conexión y los errores de solo lectura, eliminar los *sockets* PDO obsoletos y aplicar reintentos controlados con retroceso exponencial.

<?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',
    ],
];
Probar responder esta pregunta con un coach de IA

15¿Cómo se despliegan de forma segura las migraciones de base de datos y los cambios de esquema con cero o mínimo tiempo de inactividad en aplicaciones PHP?

El despliegue de migraciones de base de datos con cero tiempo de inactividad en aplicaciones PHP requiere desacoplar la evolución del esquema de las implementaciones del código de la aplicación utilizando el patrón Expandir y Contraer (Ejecución Paralela) junto con prácticas DDL (Data Definition Language) en línea no bloqueantes. En la fase de Expansión, se aplican cambios de esquema retrocompatibles (por ejemplo, añadir columnas anulables, nuevas tablas o índices concurrentes utilizando herramientas como `pt-online-schema-change`, `gh-ost` o DDL en línea nativo en MySQL/PostgreSQL). La aplicación se despliega entonces para soportar la escritura dual (escribiendo tanto a las estructuras de esquema antiguas como a las nuevas) mientras se lee de cualquiera de ellas. Los registros históricos se rellenan de forma asíncrona en lotes en segundo plano con limitación de velocidad para evitar bloquear tablas o inducir retraso de replicación. En la fase de Contracción, una vez que se valida la consistencia de los datos y las lecturas se cambian completamente a la nueva estructura, una implementación de código elimina las referencias al esquema antiguo. Finalmente, una migración de limpieza elimina columnas o tablas heredadas. La reversión segura requiere que cada estado de esquema intermedio permanezca totalmente compatible con las versiones de aplicación anteriores y entrantes durante las implementaciones continuas.

<?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
Probar responder esta pregunta con un coach de IA