La guía definitiva para transpilar código PHP
En 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 se crea un complemento público para WordPress o cuando se trabaja con código heredado que impide la actualización del entorno del servidor web.
En estas situaciones, podríamos perder la esperanza de utilizar el último código PHP. Pero hay una alternativa mejor: 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 la transpiración?
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.
Transpilar no es un concepto nuevo dentro del desarrollo web: los desarrolladores del lado del cliente probablemente estarán familiarizados con Babel, un transpilador para 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 Transpiling PHP?
Lo que es potencialmente nuevo dentro del desarrollo web es la posibilidad de transpilar código del lado del servidor, en particular PHP.
Transpilar PHP funciona de la misma manera que transpilar 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 equivalente de PHP 7.3:
$nums = array_map(
function ($n) {
return $n * 2;
},
[2, 4, 6]
);
Las funciones de flecha se pueden transpilar porque son azúcar sintáctico, es decir, una nueva sintaxis para producir un comportamiento existente. Esta es la fruta madura.
Sin embargo, también hay nuevas características que crean un nuevo comportamiento y, como tal, no habrá código equivalente para 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, la transpilación aún se puede realizar siempre que la nueva característica sea necesaria para el desarrollo, pero no para la producción. Entonces, podemos simplemente eliminar la función por completo del código transpilado sin consecuencias graves.
Un ejemplo son los tipos de sindicatos. Esta función se utiliza 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 deberíamos detectarlo y solucionarlo antes de que el código llegue a producción.
Por lo tanto, podemos permitirnos 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 lanzado será menos preciso que si tuviéramos tipos de unión. Sin embargo, esta desventaja potencial se compensa al poder utilizar tipos de sindicatos en primer lugar.
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 resultar especialmente útil para los desarrolladores que crean productos para sistemas de gestió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 al final de su vida útil (EOL), lo que significa que ya no reciben actualizaciones de seguridad, se sitúa en un considerable 34,8%, y los que se ejecutan en cualquier versión de PHP que no sea 8.0 se sitúa en un enorme 99.5%:
Estadísticas de uso de WordPress por versión. Fuente de la imagen: WordPress
En consecuencia, es muy probable que los temas y complementos de WordPress dirigidos a una audiencia global estén codificados con una versión anterior de PHP para aumentar su posible alcance. Gracias a la transpilación, estos podrían codificarse usando PHP 8.0 y aún así ser lanzados para una versión anterior de PHP, apuntando así a tantos usuarios como sea 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 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 de 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áctico. Los atributos, por el contrario, crean un comportamiento completamente nuevo. Este comportamiento también podría reproducirse con PHP 7.4 y versiones anteriores, pero solo codificándolo manualmente, es decir, no basándose automáticamente en una herramienta o proceso (la IA podría proporcionar una solución, pero aún no lo hemos logrado).
Atributos destinados al uso en desarrollo, como #[Deprecated], se puede eliminar de la misma forma 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 día 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.
Funciones de PHP que se pueden transferir
Estas son las características de PHP 7.1 y posteriores que actualmente se pueden transpilar. Si su código solo usa estas funciones, puede tener la certeza de que su aplicación transpilada funcionará. De lo contrario, deberá evaluar si el código transpilado producirá fallas.
Transpiladores PHP
Actualmente, existe una herramienta para transpilar código PHP: Rector.
Rector es una herramienta de reconstrucción de PHP, que convierte código PHP basado en reglas programables. Ingresamos el código fuente y el conjunto de reglas a 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á un «diff» (adiciones en verde, eliminaciones en rojo) del código antes y después de la conversión:
Salida «diff» de Rector
¿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 podemos transpilar el código PHP a la versión 5.6.
Transpiling vs Backporting
El backporting es similar al transpiling, pero más simple. El código de backporting no depende necesariamente de las nuevas funciones de un idioma. En cambio, se puede proporcionar la misma funcionalidad a una versión anterior del idioma simplemente copiando / pegando / adaptando el código correspondiente de la nueva versión del idioma.
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 el transpilar, deberíamos optar por esta solución siempre que el backporting haga el trabajo.
Con respecto al rango entre PHP 8.0 y 7.1, podemos usar las bibliotecas polyfill de Symfony:
Estas bibliotecas soportan las siguientes funciones, clases, constantes e interfaces:
Ejemplos de PHP transpilado
Examinemos algunos ejemplos de código PHP transpilado y algunos paquetes que se están transpilando por completo.
Código PHP
los match La expresión 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 equivalente de PHP 7.4, utilizando el switch operador:
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 debe asignar el valor de la operación a una nueva variable primero, 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 genera el código completo:
class QueryResolver
{
protected QueryFormatter $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
los El código transpilado anterior 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 están siendo transpiladas para producción:
Pros y contras de transpilar PHP
El beneficio de transpilar PHP ya se ha descrito: permite que el código fuente utilice PHP 8.0 (es decir, la última versión de PHP), que se transformará en una versión inferior de PHP para que la producción se ejecute en una aplicación o entorno heredado.
Esto efectivamente nos permite 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 escritas de PHP 7.4 y los diferentes tipos y pseudo-tipos agregados a cada nueva versión de PHP (mixed desde PHP 8.0, object desde PHP 7.2), entre otras características modernas de PHP.
Con estas funciones, 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 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, tendremos que enviar una solicitud de extracción. Si la biblioteca no es de código abierto, es posible que nos encontremos con un obstáculo.
Rector no nos informa cuando el código no se puede transpilar
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 estamos familiarizados con él, pero podría convertirse en un obstáculo con respecto a las dependencias de terceros.
La información de depuración utiliza 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 del 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 el prefijo PHP-Scoper, Strauss o alguna otra herramienta para evitar posibles conflictos.
El traspaso 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 correremos el riesgo de 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 para el desarrollo:
composer require rector/rector --dev
Luego creamos un rector.php archivo de configuración 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 RectorSetValueObjectDowngradeSetList;
use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator;
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 Rector’s process comando en modo seco, pasando la (s) ubicación (es) para procesar (en este caso, todos los archivos de la carpeta src/):
vendor/bin/rector process src --dry-run
Para realizar el transpiling, ejecutamos Rector’s process comando, que modificará los archivos dentro de su ubicación existente:
vendor/bin/rector process src
Tenga en cuenta: si corremos rector process en nuestras computadoras de desarrollo, el código fuente se convertirá en su lugar, bajo 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 las de las dependencias, por ejemplo, en sus subcarpetas. tests/, test/ y Test/ (para diferentes bibliotecas). Luego, le decimos a Rector que omita el procesamiento de estas carpetas:
return static function (ContainerConfigurator $containerConfigurator): void {
// ...
$parameters->set(Option::SKIP, [
// Skip tests
'*/tests/*',
'*/test/*',
'*/Test/*',
]);
};
En cuanto a las dependencias, Composer sabe cuáles están para el desarrollo (las que se encuentran bajo la entrada require-dev en composer.json) y cuáles son para producción (los que están bajo 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á los datos de nuestro proyecto. src/ carpeta más aquellas 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 de PHP de destino. Si una biblioteca ha sido codificada con PHP 7.1 (o cualquier versión a continuación), entonces no es necesario 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 a través de Composer’s why-not comando, así:
composer why-not php "7.1.*" | grep -o "S*/S*"
Debido a que este comando no funciona con el --no-dev flag, para incluir solo las dependencias para la producción, primero debemos eliminar las dependencias para el desarrollo y regenerar el autocargador, ejecutar el comando y luego agregarlas nuevamente:
$ composer install --no-dev
$ packages=$(composer why-not php "7.1.*" | grep -o "S*/S*")
$ composer install
Del compositor info --path comando 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 al Rector (más el src/ carpeta):
vendor/bin/rector process src $paths
Escollos que se deben evitar al transpilar código
La transpilación de código podría considerarse un arte, que a menudo requiere ajustes específicos del proyecto. Veamos algunos problemas en los que podemos entrar.
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, 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, función tag debe someterse a dos modificaciones:
El resultado final debería ser este:
final class CacheItem implements ItemInterface
{
public function tag($tags)
{
// ...
return $this;
}
}
Sin embargo, Rector solo da salida a 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 el transpiling. La segunda vez, usamos el --dry-run marca para descubrir si aún quedan 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ó y que no se procesó alguna regla encadenada.
Rector en ejecución con la bandera de ejecución en seco
Una vez que hayamos identificado la regla encadenada no aplicada (o reglas), podemos crear otro archivo de configuración de Rector, por ejemplo, rector-chained-rule.php ejecutará la regla que falta. En lugar de procesar un conjunto completo de reglas para todos los archivos bajo src/, esta vez, podemos ejecutar la regla específica que falta en el archivo específico donde debe aplicarse:
// rector-chained-rule.php
use RectorCoreConfigurationOption;
use RectorDowngradePhp74RectorClassMethodDowngradeSelfTypeDeclarationRector;
use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator;
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 segunda pasada que use el nuevo archivo de configuración a través de la entrada --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), aún así, haga referencia a algún código de ellos para la producción (como en algunos archivos bajo src/, no tests/).
Por lo general, esto no es un problema porque es posible que el 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 el desarrollo).
Por ejemplo, clase EarlyExpirationHandler del componente Cache de Symfony implementa la interfaz MessageHandlerInterface desde el componente Messenger:
class EarlyExpirationHandler implements MessageHandlerInterface
{
//...
}
Sin embargo, symfony/cache declara symfony/messenger ser 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 SymfonyComponentMessengerHandlerMessageHandlerInterface 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 para 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 faltante para la producción:
composer require symfony/messenger
Transpiración 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 corredores temporales para ejecutar el proceso.
Un momento ideal para ejecutar el proceso de transpiling es cuando se genera el release para nuestro proyecto. Por ejemplo, el siguiente código es un flujo de trabajo para acciones de GitHub, 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 Acciones de GitHub. La nueva adición, para 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:
- Verifica el código fuente de un complemento de WordPress de su repositorio, escrito con PHP 7.4
- Instala sus dependencias de 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"para"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 un activo de lanzamiento (y, además, como un artefacto para la acción de GitHub)
Prueba del código transpilado
Una vez que el código se ha transpilado 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 se han dejado restos de versiones superiores de código PHP?
De forma 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ó), entonces el linter arrojará 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
- Transpile 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 acción de GitHub 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
Tenga en cuenta que varios bootstrap80.php Los archivos de las bibliotecas polyfill de Symfony (que no necesitan ser transpilados) deben excluirse del linter. Estos archivos contienen PHP 8.0, por lo que el linter arrojarí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 desacoplar nuestro código con requisitos PHP específicos del CMS. Ahora podemos hacerlo si deseamos usar la última versión de PHP para crear un complemento de WordPress o un módulo de Drupal disponible públicamente sin restringir severamente nuestra base de usuarios.
¿Tiene alguna pregunta sobre la transpilación de PHP? ¡Infórmenos en la sección para comentarios!
Ahorre tiempo, costos y maximice el rendimiento del sitio con:
- Ayuda instantánea de expertos en alojamiento de WordPress, 24 horas al día, 7 días a la semana.
- Integración de Cloudflare Enterprise.
- Alcance de audiencia global con 28 centros de datos en todo el mundo.
- Optimización con nuestro monitoreo de rendimiento de aplicaciones integrado.
Todo eso y mucho más, en un plan sin contratos a largo plazo, migraciones asistidas y una garantía de devolución de dinero de 30 días. Consulte nuestros planes o hable con ventas para encontrar el plan adecuado para usted.
