La guida definitiva per la traslazione del codice PHP
Pubblicato: 2021-09-22In circostanze ideali, dovremmo utilizzare PHP 8.0 (l'ultima versione al momento della stesura di questo) per tutti i nostri siti e aggiornarlo non appena viene rilasciata una nuova versione. Tuttavia, gli sviluppatori dovranno spesso lavorare con versioni precedenti di PHP, ad esempio quando creano un plug-in pubblico per WordPress o lavorano con codice legacy che impedisce l'aggiornamento dell'ambiente del server web.
In queste situazioni, potremmo rinunciare alla speranza di utilizzare il codice PHP più recente. Ma c'è un'alternativa migliore: possiamo ancora scrivere il nostro codice sorgente con PHP 8.0 e trasferirlo a una versione PHP precedente, anche a PHP 7.1.
In questa guida, ti insegneremo tutto ciò che devi sapere sulla transpilazione del codice PHP.
Che cos'è il trapianto?
Transpiling converte il codice sorgente da un linguaggio di programmazione in un codice sorgente equivalente dello stesso o di un linguaggio di programmazione diverso.
Il transpiling non è un concetto nuovo all'interno dello sviluppo web: gli sviluppatori lato client avranno molto probabilmente familiarità con Babel, un transpiler per codice JavaScript.
Babel converte il codice JavaScript dalla moderna versione ECMAScript 2015+ in una versione legacy compatibile con i browser meno recenti. Ad esempio, data una funzione freccia ES2015:
[2, 4, 6].map((n) => n * 2);
…Babel lo convertirà nella sua versione ES5:
[2, 4, 6].map(function(n) { return n * 2; });
Che cos'è la traslazione di PHP?
Ciò che è potenzialmente nuovo nello sviluppo web è la possibilità di transpilare il codice lato server, in particolare PHP.
La transpilazione di PHP funziona allo stesso modo della transpilazione di JavaScript: il codice sorgente di una versione moderna di PHP viene convertito in un codice equivalente per una versione precedente di PHP.
Seguendo lo stesso esempio di prima, una funzione freccia da PHP 7.4:
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
...può essere trasferito nella sua versione equivalente di PHP 7.3:
$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );
Le funzioni delle frecce possono essere trapilate perché sono zucchero sintattico, cioè una nuova sintassi per produrre un comportamento esistente. Questo è il frutto basso.
Tuttavia, ci sono anche nuove funzionalità che creano un nuovo comportamento e, come tale, non ci sarà codice equivalente per le versioni precedenti di PHP. Questo è il caso dei tipi di unione, introdotti in PHP 8.0:
function someFunction(float|int $param): string|float|int|null { // ... }
In queste situazioni, è ancora possibile eseguire il transpiling fintanto che la nuova funzionalità è richiesta per lo sviluppo ma non per la produzione. Quindi, possiamo semplicemente rimuovere del tutto la funzione dal codice transpilato senza gravi conseguenze.
Uno di questi esempi sono i tipi di unione. Questa funzione viene utilizzata per verificare che non vi siano discrepanze tra il tipo di input e il valore fornito, il che aiuta a prevenire i bug. Se c'è un conflitto con i tipi, ci sarà un errore già in fase di sviluppo e dovremmo catturarlo e risolverlo prima che il codice raggiunga la produzione.
Quindi, possiamo permetterci di rimuovere la funzionalità dal codice per la produzione:
function someFunction($param) { // ... }
Se l'errore si verifica ancora in produzione, il messaggio di errore generato sarà meno preciso rispetto ai tipi di unione. Tuttavia, questo potenziale svantaggio è compensato dalla possibilità di utilizzare in primo luogo i tipi di sindacato.
twittare
Vantaggi della trasposizione del codice PHP
Transpiling consente di codificare un'applicazione utilizzando l'ultima versione di PHP e produrre una versione che funzioni anche in ambienti che eseguono versioni precedenti di PHP.
Ciò può essere particolarmente utile per gli sviluppatori che creano prodotti per sistemi di gestione dei contenuti (CMS) legacy. WordPress, ad esempio, supporta ancora ufficialmente PHP 5.6 (anche se raccomanda PHP 7.4+). La percentuale di siti WordPress che eseguono le versioni PHP da 5.6 a 7.2 — che sono tutti End-of-Life (EOL), il che significa che non ricevono più aggiornamenti di sicurezza — si attesta a un considerevole 34,8% e quelli in esecuzione su qualsiasi versione di PHP diversa da 8.0 si attesta a un enorme 99,5%:

Di conseguenza, i temi e i plugin di WordPress destinati a un pubblico globale saranno molto probabilmente codificati con una vecchia versione di PHP per aumentare la loro possibile portata. Grazie al transpiling, questi potrebbero essere codificati utilizzando PHP 8.0 e comunque essere rilasciati per una versione PHP precedente, rivolgendosi così al maggior numero possibile di utenti.
In effetti, qualsiasi applicazione che necessita di supportare qualsiasi versione di PHP diversa dalla più recente (anche all'interno della gamma delle versioni PHP attualmente supportate) può trarne vantaggio.
Questo è il caso di Drupal, che richiede PHP 7.3. Grazie al transpiling, gli sviluppatori possono creare moduli Drupal pubblicamente disponibili utilizzando PHP 8.0 e rilasciarli con PHP 7.3.
Un altro esempio è la creazione di codice personalizzato per i client che non possono eseguire PHP 8.0 nei loro ambienti per un motivo o per l'altro. Tuttavia, grazie al transpiling, gli sviluppatori possono ancora codificare i loro deliverable utilizzando PHP 8.0 ed eseguirli su quegli ambienti legacy.
Quando transpilare PHP
Il codice PHP può sempre essere transpilato a meno che non contenga alcune funzionalità PHP che non hanno equivalenti nella versione precedente di PHP.
Questo è forse il caso degli attributi, introdotti in PHP 8.0:
#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}
Nell'esempio precedente utilizzando le funzioni freccia, il codice potrebbe essere trascritto perché le funzioni freccia sono zucchero sintattico. Gli attributi, al contrario, creano un comportamento completamente nuovo. Questo comportamento potrebbe essere riprodotto anche con PHP 7.4 e precedenti, ma solo codificandolo manualmente, cioè non automaticamente in base a uno strumento o processo (l'IA potrebbe fornire una soluzione, ma non ci siamo ancora).
Gli attributi destinati all'uso in fase di sviluppo, come #[Deprecated]
, possono essere rimossi nello stesso modo in cui vengono rimossi i tipi di unione. Ma gli attributi che modificano il comportamento dell'applicazione in produzione non possono essere rimossi e non possono nemmeno essere trasferiti direttamente.
Ad oggi, nessun transpiler può accettare codice con attributi PHP 8.0 e produrre automaticamente il suo equivalente codice PHP 7.4. Di conseguenza, se il tuo codice PHP ha bisogno di utilizzare attributi, la sua transpilazione sarà difficile o impossibile.
Funzionalità PHP che possono essere trasferite
Queste sono le funzionalità di PHP 7.1 e versioni successive che possono essere attualmente trasferite. Se il tuo codice utilizza solo queste funzionalità, puoi avere la certezza che l'applicazione trasferita funzionerà. In caso contrario, dovrai valutare se il codice trasferito produrrà errori.
Versione PHP | Caratteristiche |
---|---|
7.1 | Qualunque cosa |
7.2 | – tipo di object – ampliamento del tipo di parametro – Flag PREG_UNMATCHED_AS_NULL in preg_match |
7.3 | – Assegnazioni di riferimento in list() / destrutturazione dell'array ( tranne all'interno di foreach — #4376)– Sintassi flessibile di Heredoc e Nowdoc – Virgole finali nelle chiamate di funzioni – set(raw)cookie accetta l'argomento $opzione |
7.4 | – Proprietà tipizzate – Funzioni delle frecce – Operatore di assegnazione di coalescenza nullo – Disimballaggio all'interno degli array – Separatore letterale numerico – strip_tags() con array di nomi di tag– tipi di ritorno covarianti e tipi di parametri controvarianti |
8.0 | – Tipi di unione – pseudotipo mixed – tipo di ritorno static – ::class costante sugli oggetti– match le espressioni– catch eccezioni solo per tipo– Operatore null-safe – Promozione della proprietà del costruttore di classe – Virgole finali negli elenchi di parametri e negli elenchi di use della chiusura |
Transpiler PHP
Attualmente, esiste uno strumento per il transpiling del codice PHP: Rector.
Rector è uno strumento di ricostruzione PHP, che converte il codice PHP in base a regole programmabili. Inseriamo il codice sorgente e l'insieme di regole da eseguire e Rector trasformerà il codice.
Rector è gestito tramite riga di comando, installato nel progetto tramite Composer. Una volta eseguito, Rector emetterà un "diff" (aggiunte in verde, rimozioni in rosso) del codice prima e dopo la conversione:

In quale versione di PHP trasferire
Per trasferire il codice tra versioni PHP, è necessario creare le regole corrispondenti.
Oggi, la libreria Rector include la maggior parte delle regole per la transpilazione del codice nell'intervallo da PHP 8.0 a 7.1. Quindi, possiamo trasferire in modo affidabile il nostro codice PHP fino alla versione 7.1.
Ci sono anche regole per il transpiling da PHP 7.1 a 7.0 e da 7.0 a 5.6, ma queste non sono esaustive. Il lavoro è in corso per completarli, quindi potremmo eventualmente trasferire il codice PHP fino alla versione 5.6.
Transpiling vs Backporting
Il backporting è simile al transpiling, ma più semplice. Il codice di backporting non si basa necessariamente sulle nuove funzionalità di una lingua. La stessa funzionalità può essere invece fornita ad una versione precedente della lingua semplicemente copiando/incollando/adattando il codice corrispondente dalla nuova versione della lingua.
Ad esempio, la funzione str_contains
è stata introdotta in PHP 8.0. La stessa funzione per PHP 7.4 e precedenti può essere facilmente implementata in questo modo:
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; } } }
Poiché il backporting è più semplice del transpiling, dovremmo optare per questa soluzione ogni volta che il backporting fa il suo lavoro.
Per quanto riguarda l'intervallo tra PHP 8.0 e 7.1, possiamo utilizzare le librerie polyfill di Symfony:
- Polyfill PHP 7.1
- Polyfill PHP 7.2
- Polyfill PHP 7.3
- Polyfill PHP 7.4
- Polyfill PHP 8.0
Queste librerie eseguono il backport delle seguenti funzioni, classi, costanti e interfacce:
Versione PHP | Caratteristiche |
---|---|
7.2 | Funzioni:
Costanti:
|
7.3 | Funzioni:
Eccezioni:
|
7.4 | Funzioni:
|
8.0 | Interfacce:
Classi:
Costanti:
Funzioni:
|
Esempi di PHP transpilato
Esaminiamo alcuni esempi di codice PHP transpilato e alcuni pacchetti che vengono completamente transpilati.
Codice PHP
L'espressione di match
è stata introdotta in PHP 8.0. Questo codice sorgente:
function getFieldValue(string $fieldName): ?string { return match($fieldName) { 'foo' => 'foofoo', 'bar' => 'barbar', 'baz' => 'bazbaz', default => null, }; }
…verrà trasferito alla sua versione equivalente di PHP 7.4, utilizzando l'operatore switch
:
function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }
L'operatore nullsafe è stato introdotto anche in PHP 8.0:
public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }
Il codice transpilato deve prima assegnare il valore dell'operazione a una nuova variabile, per evitare di eseguire l'operazione due volte:
public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }
La funzione di promozione della proprietà del costruttore, introdotta anche in PHP 8.0, consente agli sviluppatori di scrivere meno codice:
class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }
Durante la traspilazione per PHP 7.4, viene prodotto l'intero pezzo di codice:
class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
Il codice transpilato sopra contiene proprietà tipizzate, che sono state introdotte in PHP 7.4. Traspilare quel codice fino a PHP 7.3 li sostituisce con docblocks:
class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
Pacchetti PHP
Le seguenti librerie sono in fase di trasferimento per la produzione:
Libreria/descrizione | Codice/note |
---|---|
Rettore Strumento di ricostruzione PHP che rende possibile il transpiling | - Codice sorgente – Codice trascritto - Appunti |
Standard di codifica facili Strumento per far aderire il codice PHP a una serie di regole | - Codice sorgente – Codice trascritto - Appunti |
API GraphQL per WordPress Plugin che fornisce un server GraphQL per WordPress | - Codice sorgente – Codice trascritto - Appunti |
Pro e contro della trasposizione di PHP
Il vantaggio della transpilazione di PHP è già stato descritto: consente al codice sorgente di utilizzare PHP 8.0 (ovvero l'ultima versione di PHP), che verrà trasformato in una versione inferiore per PHP affinché la produzione possa essere eseguita in un'applicazione o in un ambiente legacy.
Questo ci consente effettivamente di diventare sviluppatori migliori, producendo codice con una qualità superiore. Questo perché il nostro codice sorgente può utilizzare i tipi di unione di PHP 8.0, le proprietà tipizzate di PHP 7.4 e i diversi tipi e pseudo-tipi aggiunti a ogni nuova versione di PHP ( mixed
da PHP 8.0, object
da PHP 7.2), tra altre caratteristiche moderne di PHP.
Utilizzando queste funzionalità, possiamo rilevare meglio i bug durante lo sviluppo e scrivere codice più facile da leggere.
Ora, diamo un'occhiata agli svantaggi.
Deve essere codificato e mantenuto
Rector può trascrivere il codice automaticamente, ma il processo richiederà probabilmente un input manuale per farlo funzionare con la nostra configurazione specifica.
Anche le biblioteche di terze parti devono essere trasferite
Questo diventa un problema ogni volta che la loro trascrizione produce errori poiché dobbiamo quindi approfondire il loro codice sorgente per scoprire il possibile motivo. Se il problema può essere risolto e il progetto è open source, dovremo inviare una richiesta pull. Se la libreria non è open source, potremmo incontrare un blocco stradale.
Il rettore non ci informa quando il codice non può essere trascritto
Se il codice sorgente contiene attributi PHP 8.0 o qualsiasi altra caratteristica che non può essere trasferita, non possiamo procedere. Tuttavia, Rector non verificherà questa condizione, quindi dobbiamo farlo manualmente. Questo potrebbe non essere un grosso problema per quanto riguarda il nostro codice sorgente poiché lo conosciamo già, ma potrebbe diventare un ostacolo per quanto riguarda le dipendenze di terze parti.
Le informazioni di debug utilizzano il codice trasferito, non il codice sorgente
Quando l'applicazione genera un messaggio di errore con un'analisi dello stack in produzione, il numero di riga punterà al codice trascritto. Abbiamo bisogno di riconvertire dal codice transpilato al codice originale per trovare il numero di riga corrispondente nel codice sorgente.
Anche il codice trascritto deve essere preceduto
Il nostro progetto trasferito e alcune altre librerie installate anche nell'ambiente di produzione potrebbero utilizzare la stessa dipendenza di terze parti. Questa dipendenza di terze parti verrà trasferita per il nostro progetto e manterrà il suo codice sorgente originale per l'altra libreria. Quindi, la versione trasferita deve essere preceduta da PHP-Scoper, Strauss o qualche altro strumento per evitare potenziali conflitti.
Il trasferimento deve avvenire durante l'integrazione continua (CI)
Poiché il codice transpilato sovrascriverà naturalmente il codice sorgente, non dovremmo eseguire il processo di transpilazione sui nostri computer di sviluppo, altrimenti rischieremo di creare effetti collaterali. L'esecuzione del processo durante un'esecuzione della CI è più adatta (ulteriori informazioni di seguito).

Come Transpilare PHP
Innanzitutto, dobbiamo installare Rector nel nostro progetto di sviluppo:
composer require rector/rector --dev
Creiamo quindi un file di configurazione rector.php
nella directory principale del progetto contenente i set di regole richiesti. Per eseguire il downgrade del codice da PHP 8.0 a 7.1, utilizziamo questa configurazione:
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); };
Per assicurarci che il processo venga eseguito come previsto, possiamo eseguire il comando di process
di Rector in modalità asciutta, passando le posizioni da elaborare (in questo caso, tutti i file nella cartella src/
):
vendor/bin/rector process src --dry-run
Per eseguire il transpiling, eseguiamo il comando di process
di Rector, che modificherà i file all'interno della loro posizione esistente:
vendor/bin/rector process src
Nota: se eseguiamo il rector process
nei nostri computer di sviluppo, il codice sorgente verrà convertito sul posto, sotto src/
. Tuttavia, vogliamo produrre il codice convertito in una posizione diversa per non sovrascrivere il codice sorgente durante il downgrade del codice. Per questo motivo, l'esecuzione del processo è più adatta durante l'integrazione continua.
Ottimizzazione del processo di transpiling
Per generare un deliverable trasferito per la produzione, è necessario convertire solo il codice per la produzione; il codice necessario solo per lo sviluppo può essere saltato. Ciò significa che possiamo evitare di trasferire tutti i test (sia per il nostro progetto che per le sue dipendenze) e tutte le dipendenze per lo sviluppo.
Per quanto riguarda i test, sapremo già dove si trovano quelli per il nostro progetto, ad esempio nella cartella tests/
. Dobbiamo anche scoprire dove si trovano quelle per le dipendenze, ad esempio nelle loro sottocartelle tests/
, test/
e Test/
(per librerie diverse). Quindi, diciamo al Rettore di saltare l'elaborazione di queste cartelle:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };
Per quanto riguarda le dipendenze, Composer sa quali sono per lo sviluppo (quelle in entry require-dev
in composer.json
) e quali per la produzione (quelle in entry require
).
Per recuperare da Composer i percorsi di tutte le dipendenze per la produzione, eseguiamo:
composer info --path --no-dev
Questo comando produrrà un elenco di dipendenze con il loro nome e percorso, in questo modo:
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
Possiamo estrarre tutti i percorsi e inserirli nel comando Rector, che elaborerà quindi la cartella src/
del nostro progetto più quelle cartelle contenenti tutte le dipendenze per la produzione:
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
Un ulteriore miglioramento può impedire a Rector di elaborare quelle dipendenze già utilizzando la versione PHP di destinazione. Se una libreria è stata codificata con PHP 7.1 (o qualsiasi versione successiva), non è necessario trasferirla in PHP 7.1.
Per ottenere ciò, possiamo ottenere l'elenco delle librerie che richiedono PHP 7.2 e versioni successive ed elaborare solo quelle. Otterremo i nomi di tutte queste librerie tramite il comando why-not
di Composer, in questo modo:
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
Poiché questo comando non funziona con il flag --no-dev
, per includere solo le dipendenze per la produzione, dobbiamo prima rimuovere le dipendenze per lo sviluppo e rigenerare il caricatore automatico, eseguire il comando e quindi aggiungerle di nuovo:
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
Il comando info --path
di Composer recupera il percorso di un pacchetto, con questo formato:
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
Eseguiamo questo comando per tutti gli elementi nel nostro elenco per ottenere tutti i percorsi da transpilare:
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
Infine, forniamo questo elenco a Rector (più la cartella src/
del progetto):
Hai bisogno di una soluzione di hosting che ti dia un vantaggio competitivo? Kinsta ti copre con velocità incredibile, sicurezza all'avanguardia e scalabilità automatica. Dai un'occhiata ai nostri piani
vendor/bin/rector process src $paths
Insidie da evitare durante la trascrizione del codice
Il trasferimento del codice potrebbe essere considerato un'arte, che spesso richiede modifiche specifiche del progetto. Vediamo alcuni problemi che potremmo incontrare.
Le regole concatenate non vengono sempre elaborate
Una regola concatenata è quando una regola deve convertire il codice prodotto da una regola precedente.
Ad esempio, la libreria symfony/cache
contiene questo codice:
final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }
Durante la transpilazione da PHP 7.4 a 7.3, tag
funzione deve subire due modifiche:
- Il tipo restituito
ItemInterface
deve essere prima convertito inself
, a causa della regolaDowngradeCovariantReturnTypeRector
- Il tipo restituito
self
deve quindi essere rimosso, a causa della regolaDowngradeSelfTypeDeclarationRector
Il risultato finale dovrebbe essere questo:
final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }
Tuttavia, Rector emette solo la fase intermedia:
final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }
Il problema è che il Rettore non può sempre controllare l'ordine in cui le regole vengono applicate.
La soluzione è identificare quali regole concatenate non sono state elaborate ed eseguire una nuova corsa Rector per applicarle.
Per identificare le regole concatenate, eseguiamo Rector due volte sul codice sorgente, in questo modo:
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
La prima volta, eseguiamo Rector come previsto, per eseguire il traspiling. La seconda volta, utilizziamo il flag --dry-run
per scoprire se ci sono ancora modifiche da apportare. In tal caso, il comando uscirà con un codice di errore e l'output "diff" indicherà quali regole possono ancora essere applicate. Ciò significherebbe che la prima esecuzione non è stata completata, con alcune regole concatenate non elaborate.

Una volta identificata la regola (o le regole concatenate) non applicata, possiamo quindi creare un altro file di configurazione di Rector, ad esempio, rector-chained-rule.php
eseguirà la regola mancante. Invece di elaborare un set completo di regole per tutti i file in src/
, questa volta possiamo eseguire la regola mancante specifica sul file specifico in cui deve essere applicata:
// 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', ]); };
Infine, diciamo a Rector al suo secondo passaggio di utilizzare il nuovo file di configurazione tramite 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
Le dipendenze del compositore possono essere incoerenti
Le librerie potrebbero dichiarare una dipendenza da programmare per lo sviluppo (cioè sotto require-dev
in composer.json
), tuttavia, fare comunque riferimento a del codice da esse per la produzione (come su alcuni file sotto src/
, non tests/
).
Di solito, questo non è un problema perché quel codice potrebbe non essere caricato in produzione, quindi non ci sarà mai un errore nell'applicazione. Tuttavia, quando Rector elabora il codice sorgente e le sue dipendenze, verifica che tutto il codice di riferimento possa essere caricato. Rector genererà un errore se un file fa riferimento a un pezzo di codice da una libreria non installata (perché è stato dichiarato necessario solo per lo sviluppo).
Ad esempio, la classe EarlyExpirationHandler
del componente Cache di Symfony implementa l'interfaccia MessageHandlerInterface
dal componente Messenger:
class EarlyExpirationHandler implements MessageHandlerInterface { //... }
Tuttavia, symfony/cache
dichiara che symfony/messenger
è una dipendenza per lo sviluppo. Quindi, quando si esegue Rector su un progetto che dipende da symfony/cache
, genererà un errore:
[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".
Ci sono tre soluzioni a questo problema:
- Nella configurazione di Rector, salta l'elaborazione del file che fa riferimento a quella parte di codice:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
- Scarica la libreria mancante e aggiungi il suo percorso che verrà caricato automaticamente dal Rettore:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
- Fai in modo che il tuo progetto dipenda dalla libreria mancante per la produzione:
composer require symfony/messenger
Transpiling e Integrazione Continua
Come accennato in precedenza, nei nostri computer di sviluppo dobbiamo usare il flag --dry-run
durante l'esecuzione di Rector, altrimenti il codice sorgente verrà sovrascritto con il codice transpilato. Per questo motivo, è più adatto eseguire l'effettivo processo di traspiling durante l'integrazione continua (CI), dove possiamo creare dei corridori temporanei per eseguire il processo.
Un momento ideale per eseguire il processo di traspiling è quando si genera il rilascio per il nostro progetto. Ad esempio, il codice seguente è un flusso di lavoro per GitHub Actions, che crea il rilascio di un plug-in 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 }}
Questo flusso di lavoro contiene una procedura standard per rilasciare un plug-in WordPress tramite GitHub Actions. La nuova aggiunta, per transpilare il codice del plugin da PHP 7.4 a 7.1, avviene in questo passaggio:
- 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
Nel complesso, questo flusso di lavoro ora esegue i seguenti passaggi:
- Controlla il codice sorgente di un plugin per WordPress dal suo repository, scritto con PHP 7.4
- Installa le sue dipendenze Composer
- Traspila il suo codice da PHP 7.4 a 7.1
- Modifica la voce "Richiede PHP" nell'intestazione del file principale del plug-in da
"7.4"
a"7.1"
- Rimuove le dipendenze necessarie per lo sviluppo
- Crea il file .zip del plugin, escludendo tutti i file non necessari
- Carica il file .zip come risorsa di rilascio (e, inoltre, come artefatto nell'azione GitHub)
Testare il codice transpilato
Una volta che il codice è stato trasferito in PHP 7.1, come facciamo a sapere che funziona bene? O, in altre parole, come facciamo a sapere che è stato completamente convertito e che non sono stati lasciati resti di versioni superiori del codice PHP?
Simile alla transpilazione del codice, possiamo implementare la soluzione all'interno di un processo CI. L'idea è quella di configurare l'ambiente del corridore con PHP 7.1 ed eseguire un linter sul codice transpilato. Se un qualsiasi pezzo di codice non è compatibile con PHP 7.1 (come una proprietà digitata da PHP 7.4 che non è stata convertita), il linter genererà un errore.
Una linter per PHP che funziona bene è PHP Parallel Lint. Possiamo installare questa libreria come dipendenza per lo sviluppo nel nostro progetto, o farla installare dal processo CI come progetto Composer autonomo:
composer create-project php-parallel-lint/php-parallel-lint
Ogni volta che il codice contiene PHP 7.2 e versioni successive, PHP Parallel Lint genererà un errore come questo:
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.
Aggiungiamo il linter nel flusso di lavoro del nostro CI. I passaggi da eseguire per transpilare il codice da PHP 8.0 a 7.1 e testarlo sono:
- Controlla il codice sorgente
- Fai in modo che l'ambiente esegua PHP 8.0, in modo che Rector possa interpretare il codice sorgente
- Transpilare il codice in PHP 7.1
- Installa lo strumento linter PHP
- Passare la versione PHP dell'ambiente a 7.1
- Eseguire la linter sul codice trascritto
Questo flusso di lavoro di GitHub Action fa il lavoro:
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
Si noti che diversi file bootstrap80.php
delle librerie polyfill di Symfony (che non devono essere trapiantati) devono essere esclusi dal linter. Questi file contengono PHP 8.0, quindi il linter genererebbe errori durante l'elaborazione. Tuttavia, escludere questi file è sicuro poiché verranno caricati in produzione solo quando si esegue PHP 8.0 o versioni successive:
if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }
twittare
Sommario
Questo articolo ci ha insegnato come transpilare il nostro codice PHP, consentendoci di utilizzare PHP 8.0 nel codice sorgente e creare una versione che funzioni su PHP 7.1. Il transpiling viene eseguito tramite Rector, uno strumento di ricostruzione PHP.
Traspilare il nostro codice ci rende sviluppatori migliori poiché possiamo rilevare meglio i bug durante lo sviluppo e produrre codice che è naturalmente più facile da leggere e capire.
Il transpiling ci consente anche di disaccoppiare il nostro codice con requisiti PHP specifici dal CMS. Ora possiamo farlo se desideriamo utilizzare l'ultima versione di PHP per creare un plug-in WordPress pubblicamente disponibile o un modulo Drupal senza limitare gravemente la nostra base di utenti.
Hai ancora domande sulla transpilazione di PHP? Facci sapere nella sezione commenti!