Quando `@deprecated` grida al lupo: rendere più facili i prossimi major upgrade di Shopware
Quando PHPStan segnala che la tua estensione chiama un metodo deprecato, il passo successivo atteso è piuttosto chiaro: trovare il sostituto e migrare il codice.
Ma cosa succede se non esiste un sostituto?
Prendiamo Context::scope(). In precedenza, la modifica pianificata per Shopware 6.8 era annunciata così:
/**
* @deprecated tag:v6.8.0 - reason:new-optional-parameter - parameter $states will be added
*/
public function scope(string $scope, \Closure $callback): mixed
L'analisi statica vede @deprecated e segnala ogni chiamata al metodo. Tuttavia, il metodo non sparirà. Verrà aggiunto un nuovo parametro opzionale, quindi le chiamate esistenti continueranno a funzionare senza alcuna modifica.
Non c'è alcuna API alternativa verso cui migrare e nessun warning da risolvere. In questa situazione, @deprecated sta di fatto gridando al lupo.
Con Shopware 6.7.14.0, stiamo cambiando il modo in cui queste modifiche pianificate alle API vengono comunicate. Le deprecazioni reali rimangono deprecazioni. Le altre modifiche di retrocompatibilità sono ora descritte con attributi PHP dedicati e strutturati.
Il risultato immediato è meno rumore per gli sviluppatori di estensioni.
Inoltre, i nuovi attributi ci forniscono una base per preparare le estensioni a Shopware 6.8 — e alle future major release — prima che queste arrivino.
TL;DR
Shopware ora utilizza due segnali diversi per due scopi diversi:
@deprecatedsignifica che un'API è obsoleta e verrà rimossa o sostituita. Gli sviluppatori di estensioni devono migrare altrove.- Gli attributi BC-change descrivono una modifica futura a un'API che resta disponibile, come un nuovo parametro, un tipo di ritorno più restrittivo o una classe che diventa final.
Gli attributi distinguono inoltre tra modifiche che interessano il codice che chiama un'API e modifiche che interessano le classi che la estendono.
Questo significa che i warning di deprecazione tornano a essere affidabili e actionable, mentre le modifiche pianificate ai contratti trasportano informazioni strutturate sufficienti perché PHPStan, Rector, gli IDE e altri strumenti possano ragionarci sopra.
Stavamo chiedendo a @deprecated di fare due lavori diversi
Il significato comunemente attribuito a @deprecated nell'ecosistema PHP è piuttosto ristretto: questa API non dovrebbe più essere usata. Gli IDE possono barrarla, e gli strumenti di analisi statica avvertono quando il codice applicativo continua a dipenderne.
È un comportamento utile. Se un metodo sparirà, gli sviluppatori di estensioni dovrebbero saperlo il prima possibile.
Shopware, tuttavia, usava @deprecated anche come meccanismo di pianificazione interno per la major version successiva. Un marcatore reason:* poteva annunciare che:
- Un tipo di ritorno diventerà più restrittivo.
- Un tipo di parametro cambierà.
- Verrà aggiunto un nuovo parametro opzionale.
- Una classe diventerà final o internal.
- La visibilità di un metodo cambierà.
Sono tutte modifiche di retrocompatibilità rilevanti, ma non sono tutte deprecazioni.
In molti casi, il metodo annotato rimane l'API corretta da usare. Se un'estensione debba cambiare qualcosa dipende da come usa quell'API. Chiamare un metodo e fare l'override in una sottoclasse sono due forme di dipendenza molto diverse.
Il segnale standard @deprecated non può esprimere questa distinzione. Non può nemmeno esprimere in forma leggibile dalla macchina un tipo futuro, il nome di un parametro, un valore di default o il pubblico interessato. Non ci si può aspettare che gli strumenti di analisi statica comprendano prosa specifica di Shopware all'interno di un'annotazione PHPDoc.
Avremmo potuto raccomandare a ogni estensione di aggiungere altri pattern di ignore. Ma un ignore generalizzato nasconderebbe anche deprecazioni autentiche (i warning in cui lo sviluppatore di estensioni deve davvero intervenire).
Il problema, quindi, non era che PHPStan fosse troppo severo. Gli stavamo dando il segnale sbagliato.
Un modello più preciso per le modifiche di retrocompatibilità
I nuovi attributi risiedono nel namespace Shopware\Core\Framework\Deprecation\BCChange. Attualmente esistono 15 attributi concreti che coprono modifiche a tipi, parametri, visibilità, ereditarietà e contratti delle eccezioni.
Ogni attributo appartiene a uno o a entrambi questi due destinatari:
CallSiteCompatibilityChangedescrive una modifica che può interessare il codice che chiama un metodo.ExtenderCompatibilityChangedescrive una modifica che può interessare una sottoclasse o un override di metodo.
Per esempio, restringere il tipo di un parametro interessa i chiamanti che attualmente passano un valore al di fuori del tipo futuro. Allargare quel tipo di parametro non interessa i chiamanti, ma può rendere incompatibile un metodo che lo sovrascrive.
I tipi di ritorno funzionano nella direzione opposta, perché in PHP i tipi di ritorno sono covarianti. Un tipo di ritorno più restrittivo non rompe i chiamanti, ma le sottoclassi che sovrascrivono il metodo potrebbero dover adeguare la loro dichiarazione.
È anche per questo che gli attributi hanno nomi direzionali come ReturnTypeNarrowing, ReturnTypeWidening, ParameterTypeNarrowing e ParameterTypeWidening. La direzione ci dice chi può essere interessato.
Alcune modifiche interessano entrambi i lati. Se un metodo diventa internal, per esempio, sia il codice che lo chiama sia quello che lo estende potrebbero dover smettere di farvi affidamento.
Se devi intervenire, quindi, non è più una domanda a cui si risponde con un generico "questo metodo è deprecato". La risposta sta nella modifica concreta e nel modo in cui la tua estensione usa l'API.
Il design è nato come RFC guidata dalla community ed è stato affinato attraverso discussioni e feedback sull'implementazione. È stato introdotto tramite la famiglia di attributi, l'applicazione in PHPStan e la migrazione delle annotazioni di pianificazione esistenti; ParameterDefaultValueChange è seguito come perfezionamento successivo.
Come gli sviluppatori di estensioni possono usare le nuove informazioni
Gli attributi stessi sono marcati @internal. Non sono pensati come una nuova API per estensioni che i plugin debbano istanziare o aggiungere al proprio codice.
Ciò su cui gli sviluppatori di estensioni possono contare è il significato dell'annuncio su un'API di Shopware Core: cosa cambia, quando cambia e se i chiamanti o gli estensori possono essere interessati.
Torniamo a Context::scope(). La sua modifica pianificata è ora espressa come metadati strutturati:
#[NewOptionalParameter(
version: 'v6.8.0',
parameterName: 'states',
parameterType: 'array',
defaultValue: [],
)]
public function scope(string $scope, \Closure $callback): mixed
Per i chiamanti non c'è nulla da fare. Il nuovo parametro è opzionale, quindi le chiamate esistenti restano valide.
Per una classe che sovrascrive il metodo, la situazione è diversa. L'override può aggiungere il parametro opzionale già oggi:
public function scope(
string $scope,
\Closure $callback,
array $states = [],
): mixed {
// ...
}
I parametri opzionali aggiuntivi sono compatibili con l'attuale dichiarazione del genitore. Una volta che Shopware 6.8 aggiungerà il parametro, l'override resterà compatibile. L'estensione può quindi prepararsi senza controlli di versione di Shopware né due implementazioni separate.
Il restringimento dei tipi di ritorno fornisce un altro esempio. Attualmente Field::setFlags() restituisce self, ma in Shopware 6.8 restituirà static:
#[ReturnTypeNarrowing(version: 'v6.8.0', newType: 'static')]
public function setFlags(Flag ...$flags): self
I chiamanti non sono interessati, perché ogni valore static è anche un valore self valido. Un metodo che esegue l'override, però, può già dichiarare static, dato che PHP consente tipi di ritorno covarianti:
public function setFlags(Flag ...$flags): static
{
// ...
}
Anche in questo caso, una sola dichiarazione funziona sia con la versione attuale sia con quella futura.
Le rinomine dei parametri interessano principalmente gli argomenti nominati. Per esempio:
#[ParameterNameChange(
version: 'v6.8.0',
parameterName: 'filename',
newName: 'fileName',
)]
public function __construct(string $content, string $filename)
Una chiamata che usa filename: si romperà dopo la rinomina. Passare ad argomenti posizionali funziona con entrambi i nomi dei parametri:
new DomainVerificationRequestStruct($content, $fileName);
La guida alla retrocompatibilità contiene l'inventario completo degli attributi e una guida per singola modifica che spiega quali adattamenti si possono già fare senza perdere la compatibilità con la versione attuale di Shopware.
I metadati statici non sostituiscono le deprecazioni a runtime
Non ogni problema di compatibilità va lasciato interamente all'analisi statica.
Se un uso legacy incompatibile può essere rilevato quando il metodo viene eseguito, Shopware emette comunque una deprecazione condizionale a runtime. Per esempio, quando un parametro verrà ristretto da ?string a string, Core può rilevare che un chiamante passa ancora null e attivare Feature::triggerDeprecationOrThrow() per quella chiamata.
Questo è più preciso che deprecare l'intero metodo. I chiamanti che passano già una stringa non ricevono alcun warning, mentre quelli che dipendono dal comportamento che si romperà in 6.8 ricevono un messaggio actionable.
Altre modifiche non possono essere rilevate in modo affidabile a runtime. Un metodo non può generalmente sapere che un'estensione lo sovrascrive con una dichiarazione incompatibile con il futuro. Una classe che diventa final non può avvisare solo le sottoclassi che contano senza generare rumore anche per i test double e altri usi legittimi attuali.
Per queste modifiche, l'informazione statica strutturata è lo strumento migliore.
Le regole PHPStan di Shopware validano questi annunci all'interno di Core. Verificano che i nomi e i tipi dei parametri si risolvano, che lo stato annunciato differisca da quello attuale e che esistano deprecazioni a runtime dove è possibile rilevare un uso incompatibile. Gli attributi inoltre falliscono la validazione dopo che la loro versione di destinazione è arrivata, così non possono diventare silenziosamente documentazione obsoleta.
Una regola separata impedisce che i vecchi marcatori di pianificazione @deprecated reason:* vengano reintrodotti.
Cosa diventa possibile adesso
Il primo beneficio per i progetti di estensioni non richiede strumenti aggiuntivi: dopo l'aggiornamento a Shopware 6.7.14.0, le note di pianificazione interne di Core non compaiono più come errori di deprecazione generici.
I metadati strutturati ci permettono anche di andare oltre.
Un proof of concept lungimirante in phpstan-shopware esplora un ruleset opzionale di compatibilità futura. Invece di analizzare solo le dichiarazioni disponibili oggi, può interpretare i contratti annunciati e verificare se il codice dell'estensione è già compatibile con la prossima major version.
Un ruleset del genere può, per esempio, individuare:
- Chiamate che passano valori al di fuori di un tipo di parametro futuro più restrittivo.
- Argomenti nominati che usano il vecchio nome di un parametro.
- Override a cui manca un futuro parametro opzionale.
- Classi che estendono qualcosa che diventerà final o internal.
- Codice che non gestisce un futuro tipo di ritorno più ampio.
Questo resta un lavoro lungimirante ed è deliberatamente separato dalle regole PHPStan predefinite. I progetti di estensioni dovrebbero poter aderire volontariamente a un'analisi più rigorosa della compatibilità futura, quando è in linea con la loro strategia di supporto.
Gli stessi metadati potrebbero essere usati come input per regole Rector personalizzate. Invece di analizzare testo PHPDoc libero, una regola può leggere uno specifico tipo di modifica e il suo payload, per poi applicare una migrazione mirata. Le ispezioni degli IDE, le diagnostiche LSP, i report di compatibilità e i generatori di note di upgrade sono altri possibili consumatori.
La parte importante è che ora disponiamo di un input affidabile su cui costruire questi strumenti.
Smettete di ignorare le deprecazioni
Quando @deprecated grida al lupo abbastanza spesso, gli sviluppatori smettono di ascoltare. Questo era il vero costo di usare lo stesso segnale sia per le rimozioni sia per la pianificazione interna della BC: ogni falso allarme rendeva più facile ignorare la successiva deprecazione autentica.
Ora che le due questioni sono separate, quella scusa non regge più. Gli sviluppatori di estensioni dovrebbero intraprendere tre azioni concrete.
Rimuovere le soppressioni generiche delle deprecazioni
Un'annotazione @deprecated su un'API pubblica di Shopware Core è actionable. L'API verrà rimossa o sostituita, e le estensioni che la usano devono seguire il percorso di migrazione documentato.
I pattern di ignore generici che nascondono le deprecazioni di Shopware dovrebbero quindi essere rimossi. Ora rischiano di nascondere lavoro che deve essere completato prima della prossima major release.
Esaminate i report di deprecazione rimanenti nella vostra estensione, individuate il sostituto documentato e trattate la migrazione come manutenzione obbligatoria anziché come pulizia opzionale.
Controllare gli attributi BC-change sulle API che usate
Rimuovere i falsi warning è solo metà del miglioramento. Gli sviluppatori di estensioni devono anche recepire i nuovi segnali BC-change.
Quando un'API usata dalla vostra estensione porta un attributo BC-change, verificate se si applica ai call site, agli estensori o a entrambi. Poi confrontate la modifica annunciata con il modo in cui la vostra estensione usa quell'API.
Gli attributi non rendono di per sé un'estensione compatibile con il futuro. Forniscono le informazioni necessarie per farlo. Se vengono ignorati fino alla major upgrade, le incompatibilità arriveranno comunque tutte insieme.
Adottare presto dichiarazioni compatibili con il futuro
Molte modifiche annunciate possono essere gestite prima della prossima major release senza abbandonare il supporto per la versione attuale di Shopware.
Gli esempi precedenti mostrano il beneficio concreto:
- Un override può aggiungere già oggi un parametro opzionale annunciato.
- Un override può adottare già oggi un tipo di ritorno più restrittivo annunciato.
- Un call site può smettere di dipendere da un nome di parametro che cambierà.
Non sono semplici preparativi messi per iscritto per dopo. Sono modifiche che gli sviluppatori di estensioni possono fare ora. Una sola implementazione può restare retrocompatibile con la versione attuale di Shopware pur essendo già compatibile con il contratto futuro annunciato.
Rendete questi controlli parte della manutenzione ordinaria delle estensioni e dell'analisi statica, invece di rimandarli alla major upgrade. Questo trasforma un'unica migrazione dirompente in una serie di modifiche più piccole e più sicure.
Shopware 6.8 è la prima major upgrade a beneficiare di questo modello, ma non sarà l'ultima. Le future modifiche major potranno usare gli stessi annunci strutturati, mentre PHPStan, Rector e altri strumenti potranno diventare sempre più efficaci nel trasformarli in lavoro di migrazione concreto.
L'obiettivo è impedire a @deprecated di gridare al lupo. Quando ora compare, trattatelo come un avviso che richiede un intervento. Quando compare un attributo BC-change, sfruttate il vantaggio che offre.
Altri articoli recenti
Laravel AI 1.1 e 1.2: Agent Skills, Cohere e structured output più solidi
Le release 1.1.0 e 1.2.0 di laravel/ai portano gli Agent Skills, l'approvazione obbligatoria per i tool built-in e nuovi provider come Cohere e Laya self-hosted. Sul fronte qualità arrivano i fix allo structured output per Bedrock e il supporto alle provider options. Aggiornati anche i modelli di default, con Claude 4.7+ che ignora i sampling params.
Laravel 13.35.0: scheduler più controllabile, code più flessibili e response Markdown
Laravel 13.35.0 porta Schedule::alwaysOnOneServer(), il supporto a schedule:interrupt per i comandi di lunga durata, --memory in percentuale per queue:work e il nuovo metodo markdown() sulla response factory. Tante piccole migliorie anche a test, view compilate in parallelo e dischi local.
BashBox: un interprete Bash in sandbox per agenti AI in PHP
BashBox è un interprete bash scritto in puro PHP che permette di eseguire gli script generati dagli agenti AI senza mai ricorrere a proc_open, exec o shell_exec. Ogni comando è una classe PHP che lavora su un filesystem virtuale, con limiti di esecuzione, quote disco e accesso di rete configurabili. Ideale per chi sviluppa agenti con il Laravel AI SDK o altre librerie simili.