Ir al contenido
getnextpdf.com

Tipos estrictos en todas partes

Spec: ISO 32000-2, §7.5.5PHPStan: Level 10, no src baseline

NextPDF ejecuta PHPStan en Level 10 sobre el código fuente del motor, sin línea base de supresiones. Esta página explica por qué «sin línea base» es una decisión de diseño y no un detalle de las herramientas, y qué aporta en realidad ese rigor a un flujo cuyo cometido irrenunciable es no gestionar mal los datos.

En la mayoría de las aplicaciones, el tipado estricto es higiene. En un motor de PDF se acerca más a un mecanismo de correctitud. El formato no perdona. Se espera que un lector localice el contenido leyendo el archivo desde el final, a través del tráiler y la tabla de referencias cruzadas, por lo que los desplazamientos de bytes del escritor deben ser exactos. Piénsese en un tipo que se ensancha sin avisar a mixed, un int que se convierte silenciosamente en un string, o un valor anulable que se desreferencia sin comprobar. Cualquiera de estos casos puede producir un archivo que se abre sin problemas en un visor y no supera la validación en otro, semanas después, sin ninguna traza de pila que señale la causa.

En este dominio, los fallos costosos son los silenciosos. El tipado estricto, sumado a un analizador estricto, es la forma en que el motor convierte una clase de fallos silenciosos en tiempo de ejecución en fallos ruidosos en tiempo de compilación.

  • El código fuente del motor se analiza en PHPStan Level 10 —el nivel más estricto— verificado en phpstan.neon.dist.
  • No existe ninguna línea base de supresión del código fuente. La configuración fija el análisis del código fuente en cero errores. Una regresión hace fallar la compilación en lugar de quedar absorbida en un archivo de exclusiones cada vez mayor.
  • Las pocas entradas de ignoreErrors que existen son estrechas, acotadas por identificador y por ruta, y justificadas individualmente en la configuración (límites de dependencia blanda entre paquetes y costuras de prueba orientadas a la reflexión), no una línea base masiva.
  • Un perfil estricto independiente ejecuta level: max y prohíbe cualquier entrada de exclusión nueva, de modo que el código nuevo se mantiene bajo un umbral aún más exigente.
  • El efecto buscado es la presión de diseño: el código que no puede expresarse con honestidad de tipos no pasa, así que se rediseña en lugar de suprimirse.

La diferencia entre «usamos un analizador estricto» y «usamos un analizador estricto sin línea base» es precisamente el quid de la cuestión, así que conviene ser preciso.

Una línea base registra cada infracción existente y le indica al analizador que ignore exactamente esas infracciones. Es una forma pragmática de adoptar el análisis estático en una base de código heredada, pero tiene un costo. La línea base se convierte en un registro silencioso de deuda que el sistema de tipos ha acordado no mirar. Las infracciones nuevas del mismo tipo pueden colarse junto a las heredadas. La promesa del analizador se debilita de «este código está limpio de tipos» a «este código no está peor de lo que estaba».

NextPDF no acepta ese acuerdo para el código fuente del motor. La configuración fija el análisis del código fuente en cero errores y activa reportUnmatchedIgnoredErrors, de modo que incluso una supresión obsoleta —una que ya no coincide con nada— hace fallar la compilación. Las exclusiones estrechas que permanecen están acotadas a un identificador de error y un archivo específicos. Cada una lleva una explicación en línea de por qué el límite es intencionado (por ejemplo, cuando el núcleo se programa contra una interfaz Pro/Enterprise de la que deliberadamente no tiene una dependencia concreta). Un revisor puede leer cada una y juzgarla. No hay ninguna lista opaca cuyo rastro pueda perderse.

El flujo que mantiene honesta esta disciplina:

  1. Change proposedNew or modified engine code.
  2. Level 10 analysisStrictest PHPStan level over src/, treatPhpDocTypesAsCertain on.
  3. Zero-error gateNo source baseline; unmatched ignores also fail.
  4. Strict profilelevel: max; no new ignore entries permitted.
  5. Redesign, not suppressIf it cannot be expressed honestly, the design changes.
Cómo llega un cambio al código fuente del motor: un cambio deshonesto en cuanto a tipos no puede pasar la barrera, así que se rediseña en lugar de suprimirse.

treatPhpDocTypesAsCertain forma parte de esto. Las anotaciones de PHPDoc se tratan como fuente de verdad, así que un @param list<T> o @return non-empty-string no es un comentario que el analizador pueda ignorar sin más. Es una promesa verificada. La anotación y el tipo en tiempo de ejecución quedan obligados a coincidir.

El tipado estricto es más visible cuando una regla de dominio se codifica como un tipo en lugar de como una comprobación en tiempo de ejecución. El discriminador de conformidad resuelve preguntas de nivel de especificación con un match exhaustivo, de modo que un caso no gestionado es un error de tipos, no un PDF incorrecto:

declare(strict_types=1);
enum ConformanceMode: string
{
case Plain = 'plain';
case PdfUa2 = 'pdfua2';
case PdfA4 = 'pdfa4';
/** @return 2|3|4|null */
public function pdfaPart(): ?int
{
return match ($this) {
self::PdfA4 => 4,
default => null,
};
}
}

El @return 2|3|4|null no es documentación. Con treatPhpDocTypesAsCertain, se verifica. Quien lo llame asumiendo que el resultado es siempre un int recibirá una advertencia en tiempo de análisis, antes de que se escriba un solo byte de un número de parte de PDF/A no conforme.

La trampa es leer «sin línea base» como «el código no tiene infracciones por casualidad». Eso está al revés. La ausencia de una línea base es la causa, no un resultado afortunado. Como no hay ningún lugar donde alojar una infracción, el código que produciría una tiene que escribirse de otra manera. Level 10 sin línea base de código fuente es una restricción que da forma al diseño, no un informe de evaluación que lo describe a posteriori.

Un segundo concepto erróneo: que el puñado de entradas de ignoreErrors es una línea base con otro nombre. No lo son. Una línea base se genera de forma masiva y es opaca. Estas se escriben individualmente, están acotadas por identificador, se explican y están protegidas por reportUnmatchedIgnoredErrors para que no puedan quedar obsoletas sin que nadie lo note.

Esta página trata sobre el análisis del código fuente del motor. El conjunto de pruebas se analiza bajo un alcance y una configuración separados y deliberadamente distintos; «sin línea base» aquí es una afirmación sobre src/, no la afirmación de que todo análisis auxiliar del repositorio esté libre de línea base. PHPStan demuestra la solidez de tipos, no la correctitud del comportamiento. No sustituye a la pirámide de pruebas: solo elimina una categoría de fallos que, de lo contrario, las pruebas tendrían que perseguir. El nivel exacto, los indicadores y el conjunto de exclusiones son precisos a fecha de la revisión de esta página. La fuente autorizada es siempre phpstan.neon.dist y phpstan-strict.neon.dist en el repositorio principal.

La edición del producto no cambia esta disciplina. Cada edición se construye a partir del mismo código fuente de Level 10:

Level 10 source analysis — edition availability
EditionAvailability
CoreEl código fuente del núcleo se analiza en Level 10 sin línea base para el código fuente.
ProPro se construye sobre la misma disciplina de código fuente de Level 10.
EnterpriseEnterprise se construye sobre la misma disciplina de código fuente de Level 10.
  • PHPStan Level 10: el nivel de análisis más estricto, que trata los valores sin tipar y con tipado laxo como errores en lugar de como advertencias.
  • Línea base (baseline): un registro generado de infracciones existentes que se indica al analizador que ignore. NextPDF no usa ninguna para el código fuente del motor.
  • treatPhpDocTypesAsCertain: un ajuste de PHPStan que trata las anotaciones de tipo de PHPDoc como hechos verificados, no como comentarios orientativos.
  • reportUnmatchedIgnoredErrors: un ajuste que hace fallar la compilación cuando una entrada de exclusión ya no coincide con nada, evitando supresiones obsoletas.
  • Presión de diseño: el efecto de una restricción que obliga a escribir el código de una manera determinada, en contraposición a una comprobación que solo lo mide.