Wordpress

La guia definitiva per a la transició de codi PHP

En circumstàncies ideals, hauríem d'utilitzar PHP 8.0 (la darrera versió en el moment d'escriure això) per a tots els nostres llocs i actualitzar-lo tan bon punt es publiqui una nova versió. Tanmateix, els desenvolupadors sovint hauran de treballar amb versions anteriors de PHP, com ara quan creen un connector públic per a WordPress o treballen amb codi antic que impedeix actualitzar l'entorn del servidor web.

En aquestes situacions, podríem renunciar a l'esperança d'utilitzar l'últim codi PHP. Però hi ha una alternativa millor: encara podem escriure el nostre codi font amb PHP 8.0 i transferir-lo a una versió anterior de PHP, fins i tot a PHP 7.1.

En aquesta guia, us ensenyarem tot el que necessiteu saber sobre la transmissió de codi PHP.

Què és Transpiling?

Transpiling converteix el codi font d'un llenguatge de programació en un codi font equivalent del mateix llenguatge de programació o d'un diferent.

La transició no és un concepte nou dins del desenvolupament web: és molt probable que els desenvolupadors del costat del client estiguin familiaritzats amb Babel, un transpilador de codi JavaScript.

Babel converteix el codi JavaScript de la versió moderna d'ECMAScript 2015+ en una versió heretada compatible amb navegadors antics. Per exemple, donada una funció de fletxa ES2015:

[2, 4, 6].map((n) => n * 2);

…Babel el convertirà a la seva versió ES5:

[2, 4, 6].map(function(n) {
  return n * 2;
});

Què és Transpiling PHP?

El que és potencialment nou en el desenvolupament web és la possibilitat de transpilar codi del servidor, en particular PHP.

Transpilar PHP funciona de la mateixa manera que transpilar JavaScript: el codi font d'una versió moderna de PHP es converteix en un codi equivalent per a una versió de PHP anterior.

Seguint el mateix exemple que abans, una funció de fletxa de PHP 7.4:

$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);

... es pot transpilar a la seva versió equivalent PHP 7.3:

$nums = array_map(
  function ($n) {
    return $n * 2;
  },
  [2, 4, 6]
);

Les funcions de fletxa es poden transpilar perquè són sucre sintàctic, és a dir, una nova sintaxi per produir un comportament existent. Aquesta és la fruita baixa.

Tanmateix, també hi ha noves característiques que creen un comportament nou i, com a tal, no hi haurà codi equivalent per a les versions anteriors de PHP. Aquest és el cas dels tipus d'unió, introduïts a PHP 8.0:

function someFunction(float|int $param): string|float|int|null
{
  // ...
}

En aquestes situacions, la transpiració encara es pot fer sempre que la nova característica sigui necessària per al desenvolupament, però no per a la producció. Aleshores, simplement podem eliminar la funció del codi transpilat sense greus conseqüències.

Un d'aquests exemples són els tipus de sindicats. Aquesta característica s'utilitza per comprovar que no hi ha desajustaments entre el tipus d'entrada i el seu valor proporcionat, la qual cosa ajuda a prevenir errors. Si hi ha un conflicte amb els tipus, hi haurà un error ja en desenvolupament i l'hauríem de detectar i solucionar-lo abans que el codi arribi a producció.

Per tant, ens podem permetre el luxe d'eliminar la funció del codi per a la producció:

function someFunction($param)
{
  // ...
}

Si l'error encara es produeix en producció, el missatge d'error llançat serà menys precís que si tinguéssim tipus d'unió. Tanmateix, aquest desavantatge potencial es compensa per poder utilitzar els tipus de sindicat en primer lloc.

En un món perfecte, hauríem de poder utilitzar PHP 8.0 a tots els nostres llocs i actualitzar-lo tan bon punt es publiqui una nova versió 😌 Però no sempre és així. Obteniu tot el que necessiteu saber sobre la transmissió de codi PHP aquí 👇Feu clic aquí per Tweet

Avantatges de la transició de codi PHP

Transpiling permet codificar una aplicació utilitzant la darrera versió de PHP i produir una versió que també funciona en entorns amb versions anteriors de PHP.

Això pot ser especialment útil per als desenvolupadors que creen productes per a sistemes de gestió de contingut (CMS) heretats. WordPress, per exemple, encara admet oficialment PHP 5.6 (tot i que recomana PHP 7.4+). El percentatge de llocs de WordPress amb versions de PHP 5.6 a 7.2, que són totes al final de la vida útil (EOL), és a dir, que ja no reben actualitzacions de seguretat, és d'un 34.8% important, i els que s'executen amb qualsevol versió de PHP que no sigui. 8.0 representa un 99.5%:

Ús de WordPress per versió
Estadístiques d'ús de WordPress per versió. Font de la imatge: WordPress

En conseqüència, els temes i complements de WordPress dirigits a un públic global probablement es codifiquen amb una versió antiga de PHP per augmentar el seu possible abast. Gràcies a la transició, es podrien codificar amb PHP 8.0 i encara es publicaran per a una versió de PHP més antiga, dirigint-se així a tants usuaris com sigui possible.

De fet, qualsevol aplicació que necessiti suportar qualsevol versió de PHP que no sigui la més recent (fins i tot dins del rang de les versions de PHP compatibles actualment) es pot beneficiar.

Aquest és el cas de Drupal, que requereix PHP 7.3. Gràcies a la transició, els desenvolupadors poden crear mòduls Drupal disponibles públicament mitjançant PHP 8.0 i alliberar-los amb PHP 7.3.

Un altre exemple és quan es crea codi personalitzat per a clients que no poden executar PHP 8.0 als seus entorns per un motiu o un altre. No obstant això, gràcies a la transició, els desenvolupadors encara poden codificar els seus lliuraments mitjançant PHP 8.0 i executar-los en aquests entorns heretats.

Quan transpirar PHP

El codi PHP sempre es pot transpilar tret que contingui alguna característica PHP que no tingui equivalent a la versió anterior de PHP.

Aquest és possiblement el cas dels atributs, introduïts a PHP 8.0:

#[SomeAttr]
function someFunc() {}

#[AnotherAttr]
class SomeClass {}

En l'exemple anterior amb funcions de fletxa, el codi es podria transpilar perquè les funcions de fletxa són sucre sintàctic. Els atributs, en canvi, creen un comportament completament nou. Aquest comportament també es podria reproduir amb PHP 7.4 i anteriors, però només codificant-lo manualment, és a dir, no basant-se automàticament en una eina o procés (la IA podria proporcionar una solució, però encara no hi som).

Atributs destinats al desenvolupament, com ara #[Deprecated], es pot eliminar de la mateixa manera que s'eliminen els tipus d'unió. Però els atributs que modifiquen el comportament de l'aplicació en producció no es poden eliminar, ni tampoc es poden transmetre directament.

A dia d'avui, cap transpilador pot prendre codi amb atributs PHP 8.0 i produir automàticament el seu codi PHP 7.4 equivalent. En conseqüència, si el vostre codi PHP necessita utilitzar atributs, serà difícil o inviable transposar-lo.

Característiques de PHP que es poden transferir

Aquestes són les funcions de PHP 7.1 i posteriors que actualment es poden transpilar. Si el vostre codi només utilitza aquestes funcions, podeu gaudir de la seguretat que la vostra aplicació transpilada funcionarà. En cas contrari, haureu d'avaluar si el codi transpilat produirà errors.

Versió PHPCaracterístiques
7.1Tot
7.2- object type
– Ampliació del tipus de paràmetre
- PREG_UNMATCHED_AS_NULL bandera dins preg_match
7.3– Fes de referència en list() / desestructuració de matrius (Excepte dins foreach — #4376)
– Sintaxi flexible Heredoc i Nowdoc
– Comes al final de les trucades de funcions
- set(raw)cookie accepta l'argument $option
7.4– Propietats tipificades
– Funcions de fletxa
– Operador d'assignació de coalescència nul·la
– Desembalatge dins de matrius
– Separador literal numèric
- strip_tags() amb una sèrie de noms d'etiquetes
– tipus de retorn covariant i tipus de paràmetre contravariant
8.0– Tipus sindicals
- mixed pseudotipus
- static tipus de retorn
- ::class constant màgica dels objectes
- match frases
- catch excepcions només per tipus
– Operador de seguretat nul·la
– Promoció de la propietat del constructor de classe
– Comes posteriors a les llistes de paràmetres i tancament use llistes

PHP Transpilers

Actualment, hi ha una eina per transpilar codi PHP: Rector.

Rector és una eina de reconstrucció PHP, que converteix el codi PHP basat en regles programables. Introduïm el codi font i el conjunt de regles per executar, i Rector transformarà el codi.

Rector s'opera mitjançant línia d'ordres, instal·lat al projecte mitjançant Composer. Quan s'executa, Rector mostrarà una "diferència" (addicions en verd, eliminacions en vermell) del codi abans i després de la conversió:

sortida "diff" del Rector
Sortida "diff" del Rector

A quina versió de PHP es vol transmetre

Per transpilar el codi entre les versions de PHP, s'han de crear les regles corresponents.

Avui, la biblioteca Rector inclou la majoria de les regles per a la transmissió de codi dins del rang de PHP 8.0 a 7.1. Per tant, podem transpilar de manera fiable el nostre codi PHP fins a la versió 7.1.

També hi ha regles per transpilar de PHP 7.1 a 7.0 i de 7.0 a 5.6, però aquestes no són exhaustives. S'està treballant per completar-los, de manera que eventualment podrem transferir el codi PHP a la versió 5.6.

Transpilació vs Backporting

El backporting és similar al transpiling, però més senzill. El codi de backporting no depèn necessàriament de noves funcions d'un idioma. En canvi, es pot proporcionar la mateixa funcionalitat a una versió anterior de l'idioma simplement copiant/enganxant/adaptant el codi corresponent de la nova versió de l'idioma.

Per exemple, la funció str_contains es va introduir a PHP 8.0. La mateixa funció per a PHP 7.4 i posteriors es pot implementar fàcilment així:

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;
    }
  }
}

Com que el backporting és més senzill que el transpiling, hauríem d'optar per aquesta solució sempre que el backporting faci la feina.

Pel que fa al rang entre PHP 8.0 i 7.1, podem utilitzar les biblioteques polyfill de Symfony:

  • Polyfill PHP 7.1
  • Polyfill PHP 7.2
  • Polyfill PHP 7.3
  • Polyfill PHP 7.4
  • Polyfill PHP 8.0

Aquestes biblioteques retroporten les funcions, classes, constants i interfícies següents:

Versió PHPCaracterístiques
7.2Funcions:

  • spl_object_id
  • utf8_encode
  • utf8_decode

Constants:

  • PHP_FLOAT_*
  • PHP_OS_FAMILY
7.3Funcions:

  • array_key_first
  • array_key_last
  • hrtime
  • is_countable

Excepcions:

  • JsonException
7.4Funcions:

  • get_mangled_object_vars
  • mb_str_split
  • password_algos
8.0interfícies:

  • Stringable

Classes:

  • ValueError
  • UnhandledMatchError

Constants:

  • FILTER_VALIDATE_BOOL

Funcions:

  • fdiv
  • get_debug_type
  • preg_last_error_msg
  • str_contains
  • str_starts_with
  • str_ends_with
  • get_resource_id

Exemples de PHP Transpiled

Inspeccionem alguns exemples de codi PHP transpilat i alguns paquets que s'estan transpilant completament.

Codi PHP

L' match expressió es va introduir a PHP 8.0. Aquest codi font:

function getFieldValue(string $fieldName): ?string
{
  return match($fieldName) {
    'foo' => 'foofoo',
    'bar' => 'barbar',
    'baz' => 'bazbaz',
    default => null,
  };
}

… es transposarà a la seva versió equivalent PHP 7.4, utilitzant 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;
  }
}

L'operador nullsafe també es va introduir a PHP 8.0:

public function getValue(TypeResolverInterface $typeResolver): ?string
{
  return $this->getResolver($typeResolver)?->getValue();
}

El codi transpilat ha d'assignar primer el valor de l'operació a una nova variable, per evitar executar l'operació dues vegades:

public function getValue(TypeResolverInterface $typeResolver): ?string
{
  return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null;
}

La funció de promoció de propietats del constructor, també introduïda a PHP 8.0, permet als desenvolupadors escriure menys codi:

class QueryResolver
{
  function __construct(protected QueryFormatter $queryFormatter)
  {
  }
}

Quan es transpil·la per a PHP 7.4, es produeix el codi complet:

 class QueryResolver
 {
  protected QueryFormatter $queryFormatter;

  function __construct(QueryFormatter $queryFormatter)
  {
    $this->queryFormatter = $queryFormatter;
  }
}

El codi transpilat anteriorment conté propietats escrites, que es van introduir a PHP 7.4. Transpilar aquest codi a PHP 7.3 els substitueix per docblocks:

 class QueryResolver
 {
  /**
   * @var QueryFormatter
   */
  protected $queryFormatter;

  function __construct(QueryFormatter $queryFormatter)
  {
    $this->queryFormatter = $queryFormatter;
  }
}

Paquets PHP

Les biblioteques següents s'estan transpilant per a la producció:

Biblioteca/descripcióCodi/notes
rector
Eina de reconstrucció PHP que fa possible la transpiració
- Codi font
– Codi transpilat
– Notes
Estàndards de codificació fàcils
Eina per fer que el codi PHP s'adhereixi a un conjunt de regles
- Codi font
– Codi transpilat
– Notes
API GraphQL per a WordPress
Connector que proporciona un servidor GraphQL per a WordPress
- Codi font
– Codi transpilat
– Notes

Pros i contres de transpilar PHP

Ja s'ha descrit l'avantatge de transpilar PHP: permet que el codi font utilitzi PHP 8.0 (és a dir, l'última versió de PHP), que es transformarà a una versió inferior per a PHP perquè la producció s'executi en una aplicació o entorn heretat.

Això ens permet efectivament convertir-nos en millors desenvolupadors, produint codi amb més qualitat. Això es deu al fet que el nostre codi font pot utilitzar els tipus d'unió de PHP 8.0, les propietats escrites de PHP 7.4 i els diferents tipus i pseudotipus afegits a cada nova versió de PHP (mixed des de PHP 8.0, object de PHP 7.2), entre altres funcions modernes de PHP.

Amb aquestes funcions, podem detectar millor els errors durant el desenvolupament i escriure codi que sigui més fàcil de llegir.

Ara, fem una ullada als inconvenients.

S'ha de codificar i mantenir

Rector pot transpilar codi automàticament, però és probable que el procés requereixi una entrada manual perquè funcioni amb la nostra configuració específica.

Les biblioteques de tercers també s'han de transferir

Això esdevé un problema sempre que la transició d'ells produeix errors, ja que després hem d'aprofundir en el seu codi font per esbrinar el possible motiu. Si el problema es pot solucionar i el projecte és de codi obert, haurem d'enviar una sol·licitud d'extracció. Si la biblioteca no és de codi obert, podem arribar a un bloqueig.

El rector no ens informa quan el codi no es pot transmetre

Si el codi font conté atributs PHP 8.0 o qualsevol altra característica que no es pugui transpilar, no podem continuar. Tanmateix, Rector no comprovarà aquesta condició, per la qual cosa hem de fer-ho manualment. Potser no és un gran problema pel que fa al nostre propi codi font, ja que ja estem familiaritzats amb ell, però podria convertir-se en un obstacle pel que fa a dependències de tercers.

La informació de depuració utilitza el codi transpilat, no el codi font

Quan l'aplicació produeix un missatge d'error amb una traça de pila en producció, el número de línia apuntarà al codi transpilat. Hem de tornar a convertir el codi transpilat al codi original per trobar el número de línia corresponent al codi font.

El codi transpilat també ha d'anar prefixat

El nostre projecte transpilat i alguna altra biblioteca també instal·lada a l'entorn de producció podrien utilitzar la mateixa dependència de tercers. Aquesta dependència de tercers es transferirà per al nostre projecte i conservarà el seu codi font original per a l'altra biblioteca. Per tant, la versió transpilada ha d'anar prefixada mitjançant PHP-Scoper, Strauss o alguna altra eina per evitar possibles conflictes.

La transpiració ha de tenir lloc durant la integració contínua (CI)

Com que el codi transpilat anul·larà naturalment el codi font, no hauríem d'executar el procés de transició als nostres ordinadors de desenvolupament, o ens arrisquem a crear efectes secundaris. És més adequat executar el procés durant una execució de CI (més informació a continuació).

Com transferir PHP

Primer, hem d'instal·lar Rector al nostre projecte per al desenvolupament:

composer require rector/rector --dev

Aleshores creem un rector.php fitxer de configuració al directori arrel del projecte que conté els conjunts de regles necessaris. Per baixar el codi de PHP 8.0 a 7.1, utilitzem aquesta configuració:

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);
};

Per assegurar-nos que el procés s'executa com s'esperava, podem executar Rector's process comanda en mode sec, passant les ubicacions a processar (en aquest cas, tots els fitxers de la carpeta src/):

vendor/bin/rector process src --dry-run

Per realitzar el transpil·lació, posem en marxa Rector's process comanda, que modificarà els fitxers dins de la seva ubicació existent:

vendor/bin/rector process src

Si us plau, tingueu en compte: si correm rector process als nostres ordinadors de desenvolupament, el codi font es convertirà al seu lloc, sota src/. Tanmateix, volem produir el codi convertit en una ubicació diferent per no anul·lar el codi font quan baixeu el codi. Per aquest motiu, executar el procés és més adequat durant la integració contínua.

Optimització del procés de transpilació

Per generar un lliurable transpilat per a la producció, només s'ha de convertir el codi de producció; el codi necessari només per al desenvolupament es pot saltar. Això vol dir que podem evitar transpilar totes les proves (tant per al nostre projecte com les seves dependències) i totes les dependències per al desenvolupament.

Pel que fa a les proves, ja sabrem on es troben les del nostre projecte, per exemple, a la carpeta tests/. També hem d'esbrinar on es troben els de les dependències, per exemple, a les seves subcarpetes tests/, test/ Test/ (per a diferents biblioteques). Aleshores, diem a Rector que omet el processament d'aquestes carpetes:

return static function (ContainerConfigurator $containerConfigurator): void {
  // ...

  $parameters->set(Option::SKIP, [
    // Skip tests
    '*/tests/*',
    '*/test/*',
    '*/Test/*',
  ]);
};

Pel que fa a les dependències, Composer sap quines són per al desenvolupament (les que estan sota entrada require-dev in composer.json) i quins són per a la producció (els que estan sota entrada require).

Per recuperar des de Composer els camins de totes les dependències per a la producció, executem:

composer info --path --no-dev

Aquesta ordre produirà una llista de dependències amb el seu nom i camí, com aquesta:

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

Podem extreure tots els camins i introduir-los a l'ordre Rector, que després processarà els del nostre projecte src/ carpeta més les carpetes que contenen totes les dependències per a la producció:

$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr 'n' ' ')"
$ vendor/bin/rector process src $paths

Una millora addicional pot evitar que Rector processi aquestes dependències que ja utilitza la versió de PHP objectiu. Si una biblioteca s'ha codificat amb PHP 7.1 (o qualsevol versió següent), no cal que la transpileu a PHP 7.1.

Per aconseguir-ho, podem obtenir la llista de biblioteques que requereixen PHP 7.2 i superior i processar només aquestes. Els noms de totes aquestes biblioteques els obtindrem a través de Composer's why-not ordre, així:

composer why-not php "7.1.*" | grep -o "S*/S*"

Com que aquesta ordre no funciona amb el --no-dev bandera, per incloure només dependències per a la producció, primer hem d'eliminar les dependències per al desenvolupament i regenerar el carregador automàtic, executar l'ordre i, a continuació, afegir-les de nou:

$ composer install --no-dev
$ packages=$(composer why-not php "7.1.*" | grep -o "S*/S*")
$ composer install

La del compositor info --path L'ordre recupera la ruta d'un paquet, amb aquest format:

# Executing this command
$ composer info psr/cache --path   
# Produces this response:
psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache

Executem aquesta ordre per a tots els elements de la nostra llista per obtenir tots els camins per transpilar:

Necessites una solució d'allotjament que et doni un avantatge competitiu? Behmastert'ha cobert amb una velocitat increïble, seguretat d'última generació i escala automàtica. Consulta els nostres plans

for package in $packages
do
  path=$(composer info $package --path | cut -d' ' -f2-)
  paths="$paths $path"
done

Finalment, facilitem aquesta llista al rector (més la del projecte src/ carpeta):

vendor/bin/rector process src $paths

Els esculls que cal evitar quan es transmet el codi

La transició de codi es podria considerar un art, sovint requerint ajustaments específics del projecte. Vegem alguns problemes que podem trobar.

Les regles encadenades no sempre es processen

Una regla encadenada és quan una regla necessita convertir el codi produït per una regla anterior.

Per exemple, biblioteca symfony/cache conté aquest codi:

final class CacheItem implements ItemInterface
{
  public function tag($tags): ItemInterface
  {
    // ...
    return $this;
  }
}

Quan es transpira de PHP 7.4 a 7.3, la funció tag ha de patir dues modificacions:

  • El tipus de retorn ItemInterface primer s'ha de convertir en self, per regla DowngradeCovariantReturnTypeRector
  • El tipus de retorn self llavors s'ha de treure, a causa de la regla DowngradeSelfTypeDeclarationRector

El resultat final hauria de ser aquest:

final class CacheItem implements ItemInterface
{
  public function tag($tags)
  {
    // ...
    return $this;
  }
}

No obstant això, Rector només dóna sortida a l'etapa intermèdia:

final class CacheItem implements ItemInterface
{
  public function tag($tags): self
  {
    // ...
    return $this;
  }
}

La qüestió és que el rector no pot controlar sempre l'ordre en què s'apliquen les normes.

La solució és identificar quines regles encadenades es van deixar sense processar i executar una nova execució de Rector per aplicar-les.

Per identificar les regles encadenades, executem Rector dues vegades al codi font, així:

$ vendor/bin/rector process src
$ vendor/bin/rector process src --dry-run

La primera vegada, executem Rector com s'esperava, per executar el transpiling. La segona vegada, fem servir el --dry-run marca per descobrir si encara hi ha canvis per fer. Si n'hi ha, l'ordre sortirà amb un codi d'error i la sortida "diff" indicarà quines regles encara es poden aplicar. Això voldria dir que la primera execució no s'ha completat i que no s'ha processat alguna regla encadenada.

Rector en curs amb bandera --dry-run
Rector en execució amb senyera de carrera en sec

Un cop hem identificat la regla (o regles) encadenada no aplicada, podem crear un altre fitxer de configuració de Rector, per exemple, rector-chained-rule.php executarà la regla que falta. En lloc de processar un conjunt complet de regles per a tots els fitxers sota src/, aquesta vegada, podem executar la regla específica que falta al fitxer específic on cal aplicar-la:

// 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',
  ]);
};

Finalment, diem a Rector en la seva segona passada que utilitzi el nou fitxer de configuració mitjançant l'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

Les dependències del compositor poden ser inconsistents

Les biblioteques podrien declarar una dependència per al desenvolupament (és a dir, sota require-dev in composer.json), tot i així, feu referència a algun codi d'ells per a la producció (com ara en alguns fitxers sota src/, No tests/).

Normalment, això no és un problema perquè és possible que aquest codi no es carregui en producció, de manera que mai hi haurà un error a l'aplicació. Tanmateix, quan Rector processa el codi font i les seves dependències, valida que es pugui carregar tot el codi de referència. Rector llançarà un error si algun fitxer fa referència a algun fragment de codi d'una biblioteca no instal·lada (perquè es va declarar que només era necessari per al desenvolupament).

Per exemple, classe EarlyExpirationHandler del component Cache de Symfony implementa la interfície MessageHandlerInterface del component Messenger:

class EarlyExpirationHandler implements MessageHandlerInterface
{
    //...
}

No obstant això, symfony/cache declara symfony/messenger ser una dependència per al desenvolupament. Aleshores, quan executeu Rector en un projecte que depèn de symfony/cache, llançarà 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".   

Hi ha tres solucions a aquest problema:

  1. A la configuració de Rector, ometeu el processament del fitxer que fa referència a aquest fragment de codi:
return static function (ContainerConfigurator $containerConfigurator): void {
  // ...

  $parameters->set(Option::SKIP, [
    __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php',
  ]);
};
  1. Baixeu la biblioteca que falta i afegiu-ne el camí perquè el rector la carrega automàticament:
return static function (ContainerConfigurator $containerConfigurator): void {
  // ...

  $parameters->set(Option::AUTOLOAD_PATHS, [
    __DIR__ . '/vendor/symfony/messenger',
  ]);
};
  1. Feu que el vostre projecte depengui de la biblioteca que falta per a la producció:
composer require symfony/messenger

Transpil·lació i integració contínua

Com s'ha esmentat anteriorment, als nostres ordinadors de desenvolupament hem d'utilitzar --dry-run marca quan s'executa Rector, o en cas contrari, el codi font se substituirà amb el codi transpilat. Per aquest motiu, és més adequat executar el procés de transpil·lació real durant la integració contínua (CI), on podem activar corredors temporals per executar el procés.

Un moment ideal per executar el procés de transpilació és quan es genera el llançament del nostre projecte. Per exemple, el codi següent és un flux de treball per a GitHub Actions, que crea el llançament d'un complement 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/checkout@v2
      - 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/action-zip@v0.1.0
        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/upload-artifact@v2
        with:
            name: graphql-api
            path: build/graphql-api.zip
      - name: Upload to release
        uses: JasonEtco/upload-to-release@master
        with:
          args: build/graphql-api.zip application/zip
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Aquest flux de treball conté un procediment estàndard per llançar un connector de WordPress mitjançant GitHub Actions. La nova incorporació, per transpilar el codi del connector de PHP 7.4 a 7.1, es fa en aquest pas:

      - 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 conjunt, aquest flux de treball ara realitza els passos següents:

  1. Consulta el codi font d'un complement de WordPress des del seu repositori, escrit amb PHP 7.4
  2. Instal·la les seves dependències de Composer
  3. Transpila el seu codi de PHP 7.4 a 7.1
  4. Modifica l'entrada "Requereix PHP" a la capçalera del fitxer principal del connector des de "7.4" a "7.1"
  5. Elimina les dependències necessàries per al desenvolupament
  6. Crea el fitxer .zip del connector, excloent tots els fitxers innecessaris
  7. Carrega el fitxer .zip com a actiu de llançament (i, a més, com a artefacte de l'acció de GitHub)

Prova del codi transpil·lat

Un cop el codi s'ha transpilat a PHP 7.1, com sabem que funciona bé? O, dit d'una altra manera, com sabem que s'ha convertit a fons i que no s'ha quedat cap resta de versions superiors de codi PHP?

De manera similar a la transpiració del codi, podem implementar la solució dins d'un procés CI. La idea és configurar l'entorn del corredor amb PHP 7.1 i executar un linter sobre el codi transpilat. Si qualsevol fragment de codi no és compatible amb PHP 7.1 (com ara una propietat escrita de PHP 7.4 que no s'ha convertit), llavors el linter generarà un error.

Un linter per a PHP que funciona bé és PHP Parallel Lint. Podem instal·lar aquesta biblioteca com a dependència per al desenvolupament del nostre projecte, o fer que el procés CI l'instal·li com a projecte de Composer autònom:

composer create-project php-parallel-lint/php-parallel-lint

Sempre que el codi contingui PHP 7.2 i superior, PHP Parallel Lint generarà un error com aquest:

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.

Afegim el linter al flux de treball del nostre CI. Els passos a executar per transpilar codi de PHP 8.0 a 7.1 i provar-lo són:

  1. Consulteu el codi font
  2. Feu que l'entorn executi PHP 8.0, de manera que Rector pugui interpretar el codi font
  3. Transmetre el codi a PHP 7.1
  4. Instal·leu l'eina PHP linter
  5. Canvia la versió PHP de l'entorn a la 7.1
  6. Executeu el linter al codi transpilat

Aquest flux de treball d'acció de GitHub fa la feina:

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/checkout@v2

      - name: Set-up PHP
        uses: shivammathur/setup-php@v2
        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/setup-php@v2
        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

Si us plau, tingueu en compte que diversos bootstrap80.php els fitxers de les biblioteques de polyfill de Symfony (que no cal que es transpilin) ​​s'han d'excloure del linter. Aquests fitxers contenen PHP 8.0, de manera que el linter generaria errors en processar-los. Tanmateix, excloure aquests fitxers és segur, ja que només es carregaran en producció quan executeu PHP 8.0 o superior:

if (PHP_VERSION_ID >= 80000) {
  return require __DIR__.'/bootstrap80.php';
}

Tant si estàs creant un connector públic per a WordPress com si estàs actualitzant el codi heretat, hi ha moltes raons per les quals pot ser impossible utilitzar l'última versió de PHP 👩‍💻 Descobriu com la transició pot ajudar en aquesta guia 👇Feu clic aquí per Tweet

resum

Aquest article ens va ensenyar com transpilar el nostre codi PHP, cosa que ens va permetre utilitzar PHP 8.0 al codi font i crear una versió que funcioni amb PHP 7.1. La transpiració es fa mitjançant Rector, una eina de reconstrucció PHP.

Transpilar el nostre codi ens fa millors desenvolupadors, ja que podem detectar millor els errors en desenvolupament i produir codi que és naturalment més fàcil de llegir i entendre.

La transició també ens permet desacoblar el nostre codi amb requisits específics de PHP del CMS. Ara ho podem fer si volem utilitzar la darrera versió de PHP per crear un connector de WordPress o un mòdul Drupal disponible públicament sense restringir severament la nostra base d'usuaris.

Et queden preguntes sobre la transició de PHP? Fes-nos-ho saber a la secció de comentaris!

Articles Relacionats

답글 남기기

이메일 주소는 공개되지 않습니다.

Torna al botó superior