Alguien por fin hizo la tarea que el resto seguimos posponiendo: compiló la extensión VLD contra PHP 8.3.6 y volcó los opcodes compilados de la misma función dos veces, una con declare(strict_types=1) al inicio del archivo y otra sin él. Los dos listados coinciden. El mismo RECV recogiendo el argumento, el mismo VERIFY_RETURN_TYPE a la salida, estructura idéntica del primer opcode al último. La estrictez nunca llega al bytecode. Vive como una bandera en el archivo compilado, y el runtime consulta esa bandera dentro de una comprobación de tipos que el motor realiza de todos modos.
Function greet:
line #* E I O op fetch ext return operands
2 0 E > RECV !0
3 1 ROPE_INIT 3 ~2 'You+are+'
2 ROPE_ADD 1 ~2 ~2, !0
3 ROPE_END 2 ~1 ~2, '+years+old'
4 VERIFY_RETURN_TYPE ~1
5 > RETURN ~1Esa única medición entierra en silencio la objeción del rendimiento. La comparación entre el tipo declarado y el valor real ocurre en cada llamada tipada, estricta o no; la bandera solo decide si un desajuste lanza un TypeError o intenta una conversión. Lanzar es, en todo caso, la rama más barata. Así que el coste nunca fue la verdadera pregunta. La verdadera pregunta es la topología, porque la bandera pertenece al archivo donde se origina la llamada, y ese detalle es el que hace daño a los equipos. Mi postura es esta: adopción total o ninguna. Una base de código medio estricta es peor que una totalmente laxa, porque al menos la laxa se equivoca de forma predecible.
Repasa lo que significa en la práctica eso de por archivo. Para los argumentos decide el archivo del llamador: si el archivo A declara strict_types y llama a un takesInt() definido en un archivo laxo B, esa llamada es estricta. Con los valores de retorno se invierte, la comprobación se ejecuta donde vive la sentencia return, así que una función en un archivo laxo puede convertir tan tranquila el string "5" en el int que prometió, por muy estricto que sea su llamador. Internamente es consistente, la comprobación obedece al archivo donde se ejecuta. Desde fuera significa que exactamente la misma función acepta "25" cuando la llamas desde admin/report.php y lanza una excepción cuando la llamas desde cron/rebuild.php. Intenta explicárselo a quien esté de guardia cuando esos dos caminos del código discrepen a las 2 de la madrugada.
Y el modo de fallo del modo laxo no es ruidoso, es aritmético. Pásale un string de fecha como "2024-01-01" a un parámetro declarado int y PHP se queda con los dígitos iniciales y descarta el resto, así que tu función ahora calcula con 2024. Dos fechas del mismo año colapsan en el mismo número, un conteo de días sale a cero, un informe se renderiza vacío y nada te avisa. El artículo que hizo el experimento con VLD rastreó exactamente este patrón en un servicio de informes financieros, y la factura de depuración fue de dos días. Con la bandera activada, el mismo error es un TypeError con una traza que apunta al llamador, arreglado en menos de un minuto.
El contraargumento honesto merece su espacio. El código heredado puede depender de la coerción a propósito: la entrada de formularios llega como strings, los viejos cargadores de configuración te entregan "1" donde se espera un bool, y un cambio de golpe hace aflorar de una vez todos los desajustes tolerados, en producción si te descuidas. También hay límites reales a lo que la bandera te da. Las funciones internas la ignoran por completo, strlen(123) hace coerción incluso en tu archivo más estricto. Los tipos de clase siempre fueron estrictos, igual que los arrays y los callables. Y el autor de una librería no puede imponer estrictez a sus consumidores, porque gana el archivo del llamador. Así que no, strict_types no es un campo de fuerza. Gobierna las declaraciones escalares de tus propias funciones, nada más.
Aun así defiendo la adopción universal, precisamente porque el alcance es así de estrecho y el coste está así de cerca de cero. Los tipos declarados que de verdad se aplican son el suelo sobre el que se apoyan PHPStan y Psalm; sin esa aplicación, tus firmas son comentarios optimistas. La migración es aburrida pero abordable. Los archivos nuevos llevan la línea declare sin discusión. Los viejos la reciben de uno en uno con la suite de tests corriendo. Los TypeError que aparecen se arreglan en la frontera real, es decir, un cast explícito (int) donde los datos de la petición entran al sistema, no casts espolvoreados por cada punto de llamada. Si te descubres casteando en cada llamador, la firma miente de todas formas y probablemente lo que pide es un union type.
Hay algo del volcado de opcodes que me dejó dándole vueltas: si la estrictez es solo una rama distinta en tiempo de ejecución, entonces el opt-in por archivo fue una decisión de diseño del lenguaje, no una necesidad técnica. PHP pudo haberla hecho global en el motor y decidió no hacerlo, presumiblemente para que veinte años de código existente sigan funcionando, lo cual es justo. Pero esa decisión nos traslada a nosotros la carga de la consistencia. Así que cuéntame cómo la llevas tú. ¿Está tu base de código al 100 por ciento con strict_types y, si no, qué archivo no recibirá nunca la línea declare y cuál es la razón honesta? Sospecho que las respuestas dicen más de nuestros sistemas que cualquier volcado de bytecode.
Comentarios
Aún no hay comentarios — escribe el primero.
Inicia la conversación
Sin cuenta ni contraseña — introduce tu correo y te enviamos un enlace de acceso de un solo uso. ¿Primera vez? Todo se configura automáticamente.
Tu valoración se aplicará automáticamente al iniciar sesión.
Revisa tu bandeja de entrada
Hemos enviado un enlace de acceso a …. Ábrelo en este dispositivo — esta pestaña te conectará automáticamente.
¿No llega nada? Mira en la carpeta de spam — y marca el correo como «No es spam» para que la próxima vez llegue directo.