La guía definitiva para transpilar código PHP
Publicado: 2021-09-22En circunstancias ideales, deberíamos usar PHP 8.0 (la última versión al momento de escribir esto) para todos nuestros sitios y actualizarlo tan pronto como se publique una nueva versión. Sin embargo, los desarrolladores a menudo necesitarán trabajar con versiones anteriores de PHP, como cuando crean un complemento público para WordPress o trabajan con código heredado que impide la actualización del entorno del servidor web.
En estas situaciones, podríamos perder la esperanza de usar el último código PHP. Pero hay una mejor alternativa: aún podemos escribir nuestro código fuente con PHP 8.0 y transpilarlo a una versión anterior de PHP, incluso a PHP 7.1.
En esta guía, le enseñaremos todo lo que necesita saber sobre la transpilación de código PHP.
¿Qué es transpilar?
La transpilación convierte el código fuente de un lenguaje de programación en un código fuente equivalente del mismo lenguaje de programación o de uno diferente.
La transpilación no es un concepto nuevo dentro del desarrollo web: es muy probable que los desarrolladores del lado del cliente estén familiarizados con Babel, un transpilador de código JavaScript.
Babel convierte el código JavaScript de la versión moderna ECMAScript 2015+ en una versión heredada compatible con navegadores más antiguos. Por ejemplo, dada una función de flecha ES2015:
[2, 4, 6].map((n) => n * 2);
…Babel lo convertirá a su versión ES5:
[2, 4, 6].map(function(n) { return n * 2; });
¿Qué es transpilar PHP?
Lo que es potencialmente nuevo dentro del desarrollo web es la posibilidad de transpilar código del lado del servidor, en particular PHP.
La transpilación de PHP funciona de la misma manera que la transpilación de JavaScript: el código fuente de una versión moderna de PHP se convierte en un código equivalente para una versión anterior de PHP.
Siguiendo el mismo ejemplo que antes, una función de flecha de PHP 7.4:
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
…se puede transpilar a su versión PHP 7.3 equivalente:
$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );
Las funciones de flecha se pueden transpilar porque son azúcar sintáctica, es decir, una nueva sintaxis para producir un comportamiento existente. Esta es la fruta madura.
Sin embargo, también hay nuevas funciones que crean un nuevo comportamiento y, como tal, no habrá un código equivalente para las versiones anteriores de PHP. Ese es el caso de los tipos de unión, introducidos en PHP 8.0:
function someFunction(float|int $param): string|float|int|null { // ... }
En estas situaciones, aún se puede realizar la transpilación siempre que la nueva característica sea necesaria para el desarrollo pero no para la producción. Luego, podemos simplemente eliminar la función por completo del código transpilado sin consecuencias graves.
Un ejemplo de ello son los tipos de unión. Esta característica se usa para verificar que no haya discrepancias entre el tipo de entrada y su valor proporcionado, lo que ayuda a prevenir errores. Si hay un conflicto con los tipos, ya habrá un error en desarrollo, y debemos detectarlo y corregirlo antes de que el código llegue a producción.
Por lo tanto, podemos darnos el lujo de eliminar la función del código para la producción:
function someFunction($param) { // ... }
Si el error aún ocurre en producción, el mensaje de error arrojado será menos preciso que si tuviéramos tipos de unión. Sin embargo, esta desventaja potencial se compensa al poder usar tipos de unión en primer lugar.
twittear
Ventajas de transpilar código PHP
La transpilación permite codificar una aplicación utilizando la última versión de PHP y producir una versión que también funciona en entornos que ejecutan versiones anteriores de PHP.
Esto puede ser particularmente útil para los desarrolladores que crean productos para sistemas de administración de contenido (CMS) heredados. WordPress, por ejemplo, todavía admite oficialmente PHP 5.6 (aunque recomienda PHP 7.4+). El porcentaje de sitios de WordPress que ejecutan las versiones de PHP 5.6 a 7.2, que son todos End-of-Life (EOL), lo que significa que ya no reciben actualizaciones de seguridad, se ubica en un considerable 34.8%, y aquellos que se ejecutan en cualquier versión de PHP que no sea 8.0 se sitúa en la friolera de 99,5%:

En consecuencia, es muy probable que los temas y complementos de WordPress dirigidos a una audiencia global se codifiquen con una versión anterior de PHP para aumentar su posible alcance. Gracias a la transpilación, estos podrían codificarse con PHP 8.0 y aún así publicarse para una versión anterior de PHP, por lo que podrían dirigirse a la mayor cantidad de usuarios posible.
De hecho, cualquier aplicación que necesite admitir cualquier versión de PHP que no sea la más reciente (incluso dentro del rango de las versiones de PHP actualmente admitidas) puede beneficiarse.
Este es el caso de Drupal, que requiere PHP 7.3. Gracias a la transpilación, los desarrolladores pueden crear módulos de Drupal disponibles públicamente usando PHP 8.0 y lanzarlos con PHP 7.3.
Otro ejemplo es cuando se crea código personalizado para clientes que no pueden ejecutar PHP 8.0 en sus entornos por una razón u otra. Sin embargo, gracias a la transpilación, los desarrolladores aún pueden codificar sus entregables usando PHP 8.0 y ejecutarlos en esos entornos heredados.
Cuándo transpilar PHP
El código PHP siempre se puede transpilar a menos que contenga alguna característica de PHP que no tenga equivalente en la versión anterior de PHP.
Ese es posiblemente el caso con los atributos, introducidos en PHP 8.0:
#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}
En el ejemplo anterior usando funciones de flecha, el código podría transpilarse porque las funciones de flecha son azúcar sintáctica. Los atributos, por el contrario, crean un comportamiento completamente nuevo. Este comportamiento también podría reproducirse con PHP 7.4 e inferior, pero solo codificándolo manualmente, es decir, no automáticamente en función de una herramienta o proceso (la IA podría proporcionar una solución, pero aún no hemos llegado allí).
Los atributos destinados al uso de desarrollo, como #[Deprecated]
, se pueden eliminar de la misma manera que se eliminan los tipos de unión. Pero los atributos que modifican el comportamiento de la aplicación en producción no se pueden eliminar, y tampoco se pueden transpilar directamente.
A partir de hoy, ningún transpilador puede tomar código con atributos PHP 8.0 y producir automáticamente su código PHP 7.4 equivalente. En consecuencia, si su código PHP necesita usar atributos, transpilarlo será difícil o inviable.
Características de PHP que se pueden transpilar
Estas son las características de PHP 7.1 y superiores que actualmente se pueden transpilar. Si su código solo usa estas funciones, puede disfrutar de la certeza de que su aplicación transpilada funcionará. De lo contrario, deberá evaluar si el código transpilado producirá fallas.
Versión PHP | Características |
---|---|
7.1 | Todo |
7.2 | – tipo de object – ampliación del tipo de parámetro – PREG_UNMATCHED_AS_NULL en preg_match |
7.3 | – Asignaciones de referencia en list() / desestructuración de matrices ( Excepto dentro de foreach — #4376)– Sintaxis flexible de Heredoc y Nowdoc – Comas finales en llamadas a funciones – set(raw)cookie acepta el argumento $option |
7.4 | – Propiedades tipeadas – Funciones de flecha – Operador de asignación coalescente nulo – Desempaquetado dentro de arreglos – Separador literal numérico – strip_tags() con una matriz de nombres de etiquetas– tipos de retorno covariantes y tipos de parámetros contravariantes |
8.0 | – Tipos de unión – pseudotipo mixed – tipo de retorno static – ::class en los objetos– Expresiones de match – catch excepciones solo por tipo– Operador de seguridad nula – Promoción de propiedad de constructor de clase – Comas finales en listas de parámetros y listas de use de cierre |
Transpiladores de PHP
Actualmente, existe una herramienta para transpilar código PHP: Rector.
Rector es una herramienta de reconstrucción de PHP, que convierte el código PHP en función de reglas programables. Ingresamos el código fuente y el conjunto de reglas para ejecutar, y Rector transformará el código.
Rector se opera a través de la línea de comandos, instalado en el proyecto a través de Composer. Cuando se ejecuta, Rector generará una "diferencia" (adiciones en verde, eliminaciones en rojo) del código antes y después de la conversión:

A qué versión de PHP transpilar
Para transpilar código entre versiones de PHP, se deben crear las reglas correspondientes.
Hoy en día, la biblioteca Rector incluye la mayoría de las reglas para transpilar código dentro del rango de PHP 8.0 a 7.1. Por lo tanto, podemos transpilar de manera confiable nuestro código PHP hasta la versión 7.1.
También hay reglas para transpilar de PHP 7.1 a 7.0 y de 7.0 a 5.6, pero no son exhaustivas. Se está trabajando para completarlos, por lo que eventualmente transpilaremos el código PHP a la versión 5.6.
Transpiling vs Backporting
La retroportación es similar a la transpilación, pero más sencilla. El código de backporting no depende necesariamente de las nuevas funciones de un lenguaje. En cambio, se puede proporcionar la misma funcionalidad a una versión anterior del lenguaje simplemente copiando/pegando/adaptando el código correspondiente de la nueva versión del lenguaje.
Por ejemplo, la función str_contains
se introdujo en PHP 8.0. La misma función para PHP 7.4 y versiones anteriores se puede implementar fácilmente de esta manera:
if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) { if (!function_exists('str_contains')) { /** * Checks if a string contains another * * @param string $haystack The string to search in * @param string $needle The string to search * @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise. */ function str_contains(string $haystack, string $needle): bool { return strpos($haystack, $needle) !== false; } } }
Debido a que el backporting es más simple que la transpilación, deberíamos optar por esta solución siempre que el backporting funcione.
Con respecto al rango entre PHP 8.0 a 7.1, podemos usar las bibliotecas polyfill de Symfony:
- Polyfill PHP 7.1
- Polyfill PHP 7.2
- Polyfill PHP 7.3
- Polyfill PHP 7.4
- Polyfill PHP 8.0
Estas bibliotecas respaldan las siguientes funciones, clases, constantes e interfaces:
Versión PHP | Características |
---|---|
7.2 | Funciones:
Constantes:
|
7.3 | Funciones:
Excepciones:
|
7.4 | Funciones:
|
8.0 | Interfaces:
Clases:
Constantes:
Funciones:
|
Ejemplos de PHP Transpilado
Inspeccionemos algunos ejemplos de código PHP transpilado y algunos paquetes que se están transpilando por completo.
Código PHP
La expresión de match
se introdujo en PHP 8.0. Este código fuente:
function getFieldValue(string $fieldName): ?string { return match($fieldName) { 'foo' => 'foofoo', 'bar' => 'barbar', 'baz' => 'bazbaz', default => null, }; }
…se transpilará a su versión PHP 7.4 equivalente, utilizando el operador de switch
:
function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }
El operador nullsafe también se introdujo en PHP 8.0:
public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }
El código transpilado necesita asignar primero el valor de la operación a una nueva variable, para evitar ejecutar la operación dos veces:
public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }
La función de promoción de propiedades del constructor, también introducida en PHP 8.0, permite a los desarrolladores escribir menos código:
class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }
Al transpilarlo para PHP 7.4, se produce el código completo:
class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
El código transpilado arriba contiene propiedades escritas, que se introdujeron en PHP 7.4. Transpilar ese código a PHP 7.3 los reemplaza con docblocks:
class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
Paquetes PHP
Las siguientes bibliotecas se están transpilando para producción:
biblioteca/descripción | Código/notas |
---|---|
Rector Herramienta de reconstrucción de PHP que hace posible la transpilación | - Código fuente – Código transpilado – Notas |
Estándares de codificación fáciles Herramienta para que el código PHP se adhiera a un conjunto de reglas | - Código fuente – Código transpilado – Notas |
API de GraphQL para WordPress Complemento que proporciona un servidor GraphQL para WordPress | - Código fuente – Código transpilado – Notas |
Pros y contras de transpilar PHP
Ya se ha descrito el beneficio de transpilar PHP: permite que el código fuente use PHP 8.0 (es decir, la última versión de PHP), que se transformará a una versión inferior de PHP para que la producción se ejecute en una aplicación o entorno heredado.
Esto nos permite efectivamente convertirnos en mejores desarrolladores, produciendo código con mayor calidad. Esto se debe a que nuestro código fuente puede usar los tipos de unión de PHP 8.0, las propiedades tipadas de PHP 7.4 y los diferentes tipos y pseudotipos agregados a cada nueva versión de PHP ( mixed
de PHP 8.0, object
de PHP 7.2), entre otras características modernas de PHP.
Con estas características, podemos detectar mejor los errores durante el desarrollo y escribir código que sea más fácil de leer.
Ahora, echemos un vistazo a los inconvenientes.
Debe codificarse y mantenerse
Rector puede transpilar el código automáticamente, pero es probable que el proceso requiera alguna entrada manual para que funcione con nuestra configuración específica.
Las bibliotecas de terceros también deben transpilarse
Esto se convierte en un problema cada vez que transpilarlos produce errores, ya que luego debemos profundizar en su código fuente para averiguar la posible razón. Si el problema se puede solucionar y el proyecto es de código abierto, necesitaremos enviar una solicitud de incorporación de cambios. Si la biblioteca no es de código abierto, podemos encontrarnos con un obstáculo.
Rector no nos informa cuando no se puede transpilar el código
Si el código fuente contiene atributos de PHP 8.0 o cualquier otra característica que no se pueda transpilar, no podemos continuar. Sin embargo, Rector no verificará esta condición, por lo que debemos hacerlo manualmente. Puede que esto no sea un gran problema con respecto a nuestro propio código fuente, ya que ya lo conocemos, pero podría convertirse en un obstáculo con respecto a las dependencias de terceros.
La información de depuración usa el código transpilado, no el código fuente
Cuando la aplicación genera un mensaje de error con un seguimiento de pila en producción, el número de línea apuntará al código transpilado. Necesitamos volver a convertir el código transpilado al original para encontrar el número de línea correspondiente en el código fuente.
El código transpilado también debe tener un prefijo
Nuestro proyecto transpilado y alguna otra biblioteca también instalada en el entorno de producción podrían usar la misma dependencia de terceros. Esta dependencia de terceros se transpilará para nuestro proyecto y mantendrá su código fuente original para la otra biblioteca. Por lo tanto, la versión transpilada debe tener un prefijo a través de PHP-Scoper, Strauss o alguna otra herramienta para evitar posibles conflictos.
La transpilación debe tener lugar durante la integración continua (CI)
Debido a que el código transpilado anulará naturalmente el código fuente, no debemos ejecutar el proceso de transpilación en nuestras computadoras de desarrollo, o nos arriesgaremos a crear efectos secundarios. Ejecutar el proceso durante una ejecución de CI es más adecuado (más sobre esto a continuación).
Cómo transpilar PHP
Primero, necesitamos instalar Rector en nuestro proyecto de desarrollo:

composer require rector/rector --dev
Luego creamos un archivo de configuración rector.php
en el directorio raíz del proyecto que contiene los conjuntos de reglas requeridos. Para degradar el código de PHP 8.0 a 7.1, usamos esta configuración:
use Rector\Set\ValueObject\DowngradeSetList; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->import(DowngradeSetList::PHP_80); $containerConfigurator->import(DowngradeSetList::PHP_74); $containerConfigurator->import(DowngradeSetList::PHP_73); $containerConfigurator->import(DowngradeSetList::PHP_72); };
Para asegurarnos de que el proceso se ejecute como se esperaba, podemos ejecutar el comando de process
de Rector en modo seco, pasando las ubicaciones para procesar (en este caso, todos los archivos en la carpeta src/
):
vendor/bin/rector process src --dry-run
Para realizar la transpilación, ejecutamos el comando de process
de Rector, que modificará los archivos dentro de su ubicación actual:
vendor/bin/rector process src
Tenga en cuenta: si ejecutamos el rector process
en nuestras computadoras de desarrollo, el código fuente se convertirá en su lugar, en src/
. Sin embargo, queremos producir el código convertido en una ubicación diferente para no anular el código fuente al degradar el código. Por esta razón, ejecutar el proceso es más adecuado durante la integración continua.
Optimización del proceso de transpilación
Para generar un entregable transpilado para producción, solo se debe convertir el código para producción; el código necesario solo para el desarrollo se puede omitir. Eso significa que podemos evitar transpilar todas las pruebas (tanto para nuestro proyecto como para sus dependencias) y todas las dependencias para el desarrollo.
En cuanto a las pruebas, ya sabremos dónde se encuentran las de nuestro proyecto, por ejemplo, en la carpeta tests/
. También debemos averiguar dónde están los de las dependencias, por ejemplo, en sus subcarpetas tests/
, test/
y Test/
(para diferentes bibliotecas). Luego, le decimos a Rector que se salte el procesamiento de estas carpetas:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };
Con respecto a las dependencias, Composer sabe cuáles son para desarrollo (aquellas bajo la entrada require-dev
en composer.json
) y cuáles son para producción (aquellas bajo la entrada require
).
Para recuperar de Composer las rutas de todas las dependencias para la producción, ejecutamos:
composer info --path --no-dev
Este comando producirá una lista de dependencias con su nombre y ruta, como esta:
brain/cortex /Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers composer/semver /Users/leo/GitHub/leoloso/PoP/vendor/composer/semver guzzlehttp/guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle league/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline
Podemos extraer todas las rutas e introducirlas en el comando Rector, que luego procesará la carpeta src/
de nuestro proyecto más las carpetas que contienen todas las dependencias para la producción:
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
Una mejora adicional puede evitar que Rector procese esas dependencias que ya utilizan la versión PHP de destino. Si una biblioteca ha sido codificada con PHP 7.1 (o cualquier versión anterior), entonces no hay necesidad de transpilarla a PHP 7.1.
Para lograr esto, podemos obtener la lista de bibliotecas que requieren PHP 7.2 y superior y procesar solo esas. Obtendremos los nombres de todas estas bibliotecas mediante el comando why-not
de Composer, así:
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
Debido a que este comando no funciona con el indicador --no-dev
, para incluir solo dependencias para producción, primero debemos eliminar las dependencias para desarrollo y regenerar el cargador automático, ejecutar el comando y luego agregarlas nuevamente:
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
El comando info --path
de Composer recupera la ruta de un paquete, con este formato:
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
Ejecutamos este comando para todos los elementos de nuestra lista para obtener todas las rutas para transpilar:
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
Finalmente, proporcionamos esta lista a Rector (más la carpeta src/
del proyecto):
¿Necesita una solución de hospedaje que le brinde una ventaja competitiva? Kinsta lo tiene cubierto con una velocidad increíble, seguridad de última generación y escalado automático. Consulta nuestros planes
vendor/bin/rector process src $paths
Trampas a evitar al transpilar código
La transpilación de código podría considerarse un arte y, a menudo, requiere ajustes específicos para el proyecto. Veamos algunos problemas en los que nos podemos encontrar.
Las reglas encadenadas no siempre se procesan
Una regla encadenada es cuando una regla necesita convertir el código producido por una regla anterior.
Por ejemplo, la biblioteca symfony/cache
contiene este código:
final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }
Al transpilar de PHP 7.4 a 7.3, la tag
de función debe sufrir dos modificaciones:
- El tipo de devolución
ItemInterface
debe convertirse primero enself
, debido a la reglaDowngradeCovariantReturnTypeRector
- El tipo de devolución
self
debe eliminarse debido a la reglaDowngradeSelfTypeDeclarationRector
El resultado final debería ser este:
final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }
Sin embargo, Rector solo genera la etapa intermedia:
final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }
El problema es que Rector no siempre puede controlar el orden en que se aplican las reglas.
La solución es identificar qué reglas encadenadas quedaron sin procesar y ejecutar una nueva ejecución de Rector para aplicarlas.
Para identificar las reglas encadenadas, ejecutamos Rector dos veces en el código fuente, así:
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
La primera vez, ejecutamos Rector como se esperaba, para ejecutar la transpilación. La segunda vez, usamos el --dry-run
para descubrir si todavía hay cambios por hacer. Si los hay, el comando saldrá con un código de error y la salida "diff" indicará qué regla(s) aún se pueden aplicar. Eso significaría que la primera ejecución no se completó, con alguna regla encadenada que no se procesó.

Una vez que hayamos identificado la regla (o reglas) encadenada no aplicada, podemos crear otro archivo de configuración de Rector; por ejemplo, rector-chained-rule.php
ejecutará la regla faltante. En lugar de procesar un conjunto completo de reglas para todos los archivos en src/
, esta vez, podemos ejecutar la regla faltante específica en el archivo específico donde debe aplicarse:
// rector-chained-rule.php use Rector\Core\Configuration\Option; use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeSelfTypeDeclarationRector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); $services->set(DowngradeSelfTypeDeclarationRector::class); $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PATHS, [ __DIR__ . '/vendor/symfony/cache/CacheItem.php', ]); };
Finalmente, le decimos a Rector en su segundo paso que use el nuevo archivo de configuración a través de input --config
:
# First pass with all modifications $ vendor/bin/rector process src # Second pass to fix a specific problem $ vendor/bin/rector process --config=rector-chained-rule.php
Las dependencias del compositor pueden ser inconsistentes
Las bibliotecas podrían declarar una dependencia para ser programada para el desarrollo (es decir, bajo require-dev
en composer.json
), y aún así, hacer referencia a algún código de ellas para producción (como en algunos archivos bajo src/
, no tests/
).
Por lo general, esto no es un problema porque es posible que ese código no se cargue en producción, por lo que nunca habrá un error en la aplicación. Sin embargo, cuando Rector procesa el código fuente y sus dependencias, valida que se pueda cargar todo el código referenciado. Rector arrojará un error si algún archivo hace referencia a algún fragmento de código de una biblioteca no instalada (porque se declaró necesario solo para desarrollo).
Por ejemplo, la clase EarlyExpirationHandler
del componente Cache de Symfony implementa la interfaz MessageHandlerInterface
del componente Messenger:
class EarlyExpirationHandler implements MessageHandlerInterface { //... }
Sin embargo, symfony/cache
declara que symfony/messenger
es una dependencia para el desarrollo. Luego, al ejecutar Rector en un proyecto que depende de symfony/cache
, arrojará un error:
[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to: "Analyze error: "Class Symfony\Component\Messenger\Handler\MessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config. See https://github.com/rectorphp/rector#configuration".
Hay tres soluciones a este problema:
- En la configuración de Rector, omita el procesamiento del archivo que hace referencia a ese fragmento de código:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
- Descargue la biblioteca que falta y agregue su ruta para que Rector la cargue automáticamente:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
- Haga que su proyecto dependa de la biblioteca que falta para la producción:
composer require symfony/messenger
Transpiling e Integración Continua
Como se mencionó anteriormente, en nuestras computadoras de desarrollo debemos usar el --dry-run
al ejecutar Rector, o de lo contrario, el código fuente se anulará con el código transpilado. Por esta razón, es más adecuado ejecutar el proceso de transpilación real durante la integración continua (CI), donde podemos activar ejecutores temporales para ejecutar el proceso.
Un momento ideal para ejecutar el proceso de transpilación es cuando se genera la versión para nuestro proyecto. Por ejemplo, el siguiente código es un flujo de trabajo para GitHub Actions, que crea el lanzamiento de un complemento de WordPress:
name: Generate Installable Plugin and Upload as Release Asset on: release: types: [published] jobs: build: name: Build, Downgrade and Upload Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Downgrade code for production (to PHP 7.1) run: | composer install vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php - name: Build project for production run: | composer install --no-dev --optimize-autoloader mkdir build - name: Create artifact uses: montudor/[email protected] with: args: zip -X -r build/graphql-api.zip . -x *.git* node_modules/\* .* "*/\.*" CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build** - name: Upload artifact uses: actions/[email protected] with: name: graphql-api path: build/graphql-api.zip - name: Upload to release uses: JasonEtco/[email protected] with: args: build/graphql-api.zip application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Este flujo de trabajo contiene un procedimiento estándar para lanzar un complemento de WordPress a través de GitHub Actions. La nueva adición, transpilar el código del complemento de PHP 7.4 a 7.1, ocurre en este paso:
- name: Downgrade code for production (to PHP 7.1) run: | vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
En conjunto, este flujo de trabajo ahora realiza los siguientes pasos:
- Comprueba el código fuente de un complemento de WordPress desde su repositorio, escrito con PHP 7.4
- Instala sus dependencias Composer
- Transpila su código de PHP 7.4 a 7.1
- Modifica la entrada "Requiere PHP" en el encabezado del archivo principal del complemento de
"7.4"
a"7.1"
- Elimina las dependencias necesarias para el desarrollo.
- Crea el archivo .zip del complemento, excluyendo todos los archivos innecesarios
- Carga el archivo .zip como recurso de publicación (y, además, como artefacto para la acción de GitHub)
Prueba del código transpilado
Una vez transpilado el código a PHP 7.1, ¿cómo sabemos que funciona bien? O, en otras palabras, ¿cómo sabemos que se ha convertido completamente y que no quedaron restos de versiones superiores del código PHP?
Similar a la transpilación del código, podemos implementar la solución dentro de un proceso de CI. La idea es configurar el entorno del corredor con PHP 7.1 y ejecutar un linter en el código transpilado. Si algún fragmento de código no es compatible con PHP 7.1 (como una propiedad escrita de PHP 7.4 que no se convirtió), el linter generará un error.
Un linter para PHP que funciona bien es PHP Parallel Lint. Podemos instalar esta biblioteca como una dependencia para el desarrollo en nuestro proyecto, o hacer que el proceso de CI la instale como un proyecto independiente de Composer:
composer create-project php-parallel-lint/php-parallel-lint
Siempre que el código contenga PHP 7.2 y superior, PHP Parallel Lint arrojará un error como este:
Run php-parallel-lint/parallel-lint layers/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php PHP 7.1.33 | 10 parallel jobs ............................................................ 60/2870 (2 %) ............................................................ 120/2870 (4 %) ... ............................................................ 660/2870 (22 %) .............X.............................................. 720/2870 (25 %) ............................................................ 780/2870 (27 %) ... ............................................................ 2820/2870 (98 %) .................................................. 2870/2870 (100 %) Checked 2870 files in 15.4 seconds Syntax error found in 1 file ------------------------------------------------------------ Parse error: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55 53| '0.8.0', 54| \__('GraphQL API for WordPress', 'graphql-api'), > 55| ))) { 56| $plugin->setup(); 57| } Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55 Error: Process completed with exit code 1.
Agreguemos el linter al flujo de trabajo de nuestro CI. Los pasos a ejecutar para transpilar código de PHP 8.0 a 7.1 y probarlo son:
- Mira el código fuente
- Haga que el entorno ejecute PHP 8.0, para que Rector pueda interpretar el código fuente
- Transpilar el código a PHP 7.1
- Instale la herramienta PHP linter
- Cambie la versión de PHP del entorno a 7.1
- Ejecute el linter en el código transpilado
Este flujo de trabajo de GitHub Action hace el trabajo:
name: Downgrade PHP tests jobs: main: name: Downgrade code to PHP 7.1 via Rector, and execute tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Set-up PHP uses: shivammathur/[email protected] with: php-version: 8.0 coverage: none - name: Local packages - Downgrade PHP code via Rector run: | composer install vendor/bin/rector process # Prepare for testing on PHP 7.1 - name: Install PHP Parallel Lint run: composer create-project php-parallel-lint/php-parallel-lint --ansi - name: Switch to PHP 7.1 uses: shivammathur/[email protected] with: php-version: 7.1 coverage: none # Lint the transpiled code - name: Run PHP Parallel Lint on PHP 7.1 run: php-parallel-lint/parallel-lint src/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
Ten en cuenta que varios archivos bootstrap80.php
de las bibliotecas polyfill de Symfony (que no necesitan transpilarse) deben excluirse del linter. Estos archivos contienen PHP 8.0, por lo que el linter generaría errores al procesarlos. Sin embargo, excluir estos archivos es seguro ya que se cargarán en producción solo cuando se ejecute PHP 8.0 o superior:
if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }
Resumen
Este artículo nos enseñó cómo transpilar nuestro código PHP, permitiéndonos usar PHP 8.0 en el código fuente y crear una versión que funcione en PHP 7.1. La transpilación se realiza a través de Rector, una herramienta de reconstrucción de PHP.
La transpilación de nuestro código nos convierte en mejores desarrolladores, ya que podemos detectar mejor los errores en el desarrollo y producir un código que, naturalmente, es más fácil de leer y comprender.
La transpilación también nos permite desvincular nuestro código de los requisitos específicos de PHP del CMS. Ahora podemos hacerlo si deseamos usar la última versión de PHP para crear un complemento de WordPress disponible públicamente o un módulo de Drupal sin restringir severamente nuestra base de usuarios.
¿Tiene alguna pregunta sobre la transpilación de PHP? ¡Infórmenos en la sección para comentarios!