Come impostare e utilizzare variabili globali? O perché non usarle affatto
AGGIORNAMENTO: La mia domanda originale è stata risolta, ma questa discussione sta diventando un valido confronto sul perché non usare le variabili globali, quindi sto aggiornando la domanda per riflettere ciò. La soluzione era <?php global $category_link_prop; echo esc_url( $category_link_prop ); ?>
come suggerito da @TomJNowell.
AGGIORNAMENTO 2: Ora ho ottenuto esattamente ciò che volevo. Ma sto ancora usando lo scope globale e sarei felice di trovare un modo migliore.
Sto cercando di impostare un'intera serie di variabili globali per i permalink alle categorie da utilizzare in vari punti del mio tema. La ragione principale è per l'uso sia nella navigazione principale, sia in una serie di sottomenu che vengono scelti in base alla categoria a cui appartiene il post corrente. Questo non è un tema che rilascerò per l'uso da parte di altri, ma è costruito per uno scopo molto specifico.
Ecco come le sto attualmente creando (ho incollato solo alcune delle variabili).
function set_global_nav_var()
{
//proposta
global $prop;
// Ottieni l'ID di una determinata categoria
$category_id_prop = get_cat_ID( 'proposal' );
// Ottieni l'URL di questa categoria
$category_link_prop = get_category_link( $category_id_prop );
$prop = '<a href="' .esc_url( $category_link_prop ). '" title="Proposal">Proposal</a>';
//Calvinball
global $cb;
// Ottieni l'ID di una determinata categoria
$category_id_cb = get_cat_ID( 'calvinball' );
// Ottieni l'URL di questa categoria
$category_link_cb = get_category_link( $category_id_cb );
$cb = '<a href="' .esc_url( $category_link_cb). '" title="Calvinball">Calvinball</a>';
}
add_action( 'init', 'set_global_nav_var' );
Ora posso fare <?php global $prop; echo $prop; ?>
nei 4 punti dove serve e ottenere l'intero link per il codice. Quando questo cambia, ho bisogno di modificarlo solo in un punto. Sono aperto ad alternative che non coinvolgano lo scope globale.
Sebbene ti sconsigli vivamente di farlo, e non velocizzerà affatto le cose, il tuo utilizzo è scorretto.
WordPress memorizza già queste cose nella cache degli oggetti/in memoria, quindi non deve recuperarle più volte nella stessa richiesta. Non è necessario memorizzare il risultato e riutilizzarlo, WordPress lo fa già di default.
È molto probabile che il tuo codice sia più lento a causa di questa micro-ottimizzazione, non più veloce!
Come Usare le Variabili Globali
Quando provi a usare una variabile globale, devi prima specificare la keyword global
. L'hai specificata qui quando definisci il suo valore, ma al di fuori di quello scope, deve essere ridefinita come variabile globale.
Ad esempio, in functions.php
:
function test() {
global $hello;
$hello = 'ciao mondo';
}
add_action( 'after_setup_theme', 'test' );
In single.php
, questo non funzionerà:
echo $hello;
Perché $hello
non è definita. Questo invece funzionerà:
global $hello;
echo $hello;
Ovviamente non dovresti fare nessuna delle due cose. WordPress cerca già di memorizzare queste cose nella cache degli oggetti.
Svantaggi e Pericoli delle Variabili Globali
Non vedrai alcun aumento di velocità facendo questo (potresti addirittura vedere una piccola diminuzione), tutto ciò che otterrai sarà maggiore complessità e la necessità di digitare molte dichiarazioni globali non necessarie.
Inoltre incontrerai altri problemi:
- codice per il quale è impossibile scrivere test
- codice che si comporta in modo diverso ogni volta che viene eseguito
- conflitti nei nomi delle variabili a causa di uno spazio dei nomi condiviso
- bug accidentali per aver dimenticato di dichiarare
global
- una completa mancanza di struttura nell'archiviazione dei dati del tuo codice
- e molti altri
Cosa Dovresti Usare Invece?
Saresti meglio servito usando dati strutturati, come oggetti o dependency injection, o nel tuo caso, un insieme di funzioni.
Ecco 3 alternative:
Variabili Statiche
Le variabili statiche non sono buone, ma pensale come il cugino leggermente meno malvagio delle variabili globali. Le variabili statiche stanno alle variabili globali, come il pane coperto di fango sta al cianuro.
Ad esempio, ecco un modo per fare qualcosa di simile tramite variabili statiche:
funzione pessima_funzione( $nuovo_ciao='' ) {
static $ciao;
if ( !empty( $nuovo_ciao ) ) {
$ciao = $nuovo_ciao;
}
return $ciao;
}
pessima_funzione( 'telefono' );
echo pessima_funzione(); // stampa telefono
pessima_funzione( 'banana');
echo pessima_funzione(); // stampa banana
Nota che ci sono altri motivi per usare variabili statiche che non sono legati a caching e prestazioni, ma hanno i loro svantaggi. È difficile se non impossibile scrivere test corretti per una funzione che usa una variabile statica, e rende il debug molto più difficile poiché devi tenere traccia del valore di quella variabile.
In un codice ben scritto, una funzione pura fa sempre la stessa cosa quando riceve gli stessi parametri. Le funzioni pure sono prevedibili e facili da testare. Una funzione con una variabile statica non potrà mai essere una funzione pura.
Singleton
I Singleton sono oggetti che vengono creati una volta e può esserci solo una singola istanza di quell'oggetto. Sono tanto cattivi quanto le variabili globali, solo con una sintassi diversa, hanno tutti gli stessi problemi delle variabili statiche e danno l'illusione della programmazione orientata agli oggetti senza nessuno dei benefici. Evitali.
Letture consigliate:
WP_Cache, La Cosa che Hai Provato a Fare ma WordPress lo Fa Già
Se vuoi davvero risparmiare tempo memorizzando dati da riutilizzare, considera di usare il sistema WP_Cache
con wp_cache_get
ecc. Ad esempio:
$valore = wp_cache_get( 'ciao' );
if ( false === $valore ) {
// non trovato, imposta il valore di default
wp_cache_set( 'ciao', 'mondo' );
}
Ora il valore verrà memorizzato nella cache per la durata della richiesta da WordPress, apparirà negli strumenti di debug e, se hai una cache degli oggetti, persisterà tra le richieste.
Questo Significa che Devo Usare wp_cache_get
nel Mio Codice?
Probabilmente no, WordPress lo fa già automaticamente per post/utenti/meta/termini/opzioni/ecc., quindi non avrai mai bisogno di memorizzare o recuperare dati dei post in questo modo. Basta usare le normali funzioni API e lo farà automaticamente dietro le quinte.
Nota a margine 1: Vorrei far notare che alcune persone cercano di conservare dati in variabili globali tra le richieste, ignare del fatto che PHP non funziona così. A differenza di un'applicazione Node, ogni richiesta carica una copia fresca dell'applicazione, che poi muore quando la richiesta è completata. Per questo motivo, le variabili globali impostate in una richiesta non sopravvivono alla richiesta successiva.
Nota a margine 2: A giudicare dalla domanda aggiornata, le tue variabili globali non ti danno alcun guadagno in termini di prestazioni. Dovresti invece generare l'HTML quando ti serve e sarebbe altrettanto veloce, forse anche leggermente più veloce. Questa è micro-ottimizzazione.

So che è un po' folle usare lo scope globale, ma la maggior parte, se non tutte queste variabili verranno utilizzate in ogni pagina. Sono aperto a idee migliori. Modificherò la domanda per rendere le mie intenzioni un po' più chiare. BTW funziona perfettamente quando faccio <?php global $category_link_prop; echo esc_url( $category_link_prop ); ?>
come per il tuo suggerimento. Grazie!

Ah se la mia soluzione funziona, potresti marcarla come accettata? Le tue variabili globali sono veloci tanto quanto fare la chiamata originale, potresti provare invece a usare funzioni così non devi scrivere 2 righe, meglio ancora, un singleton, meglio ancora, rendi tutto dinamico e in un template part incluso via get_template_part

Segnato come accettato poiché è quello che sto facendo ora anche se potrei optare per una delle strategie suggerite da @MarkKaplun qui sotto. Usare get_template_part() è un'idea interessante, ma non sono sicuro di volere una cartella piena di file corti così...

oh no no non vorresti un file per ogni categoria, vorresti solo quello che prende il nome della categoria corrente e lo utilizza. Non dovresti dover hardcodare nulla, immagina il fastidio di hardcodare tutto

Ho inserito il codice nel mio child-functions.php che è attivo. Ma non riesco ad accedere alla variabile in un file php-include che chiamo da un post "normale" generato dal database. Per favore consigliami, cosa sto sbagliando? (La definisco come global, ovviamente.)

Devi dichiarare global
ogni volta che la usi. Non è una cosa che usi una volta e funziona ovunque, devi usarla ogni, singola volta, senza eccezioni di alcun tipo. Ma come dico nella mia domanda, le variabili globali sono una cattiva pratica, problematiche e non la soluzione che stai cercando al tuo problema. Persino il male che sono i singleton sarebbe una soluzione migliore

Grazie, apprezzo il tuo commento. L'ho dichiarato e usato ma da qualche parte devo aver commesso un errore. Semplicemente non ha funzionato per me. Ho provato approcci diversi. Alla fine questo ha funzionato. Sono d'accordo con quello che dici sui globali, ma a volte è necessaria una soluzione rapida per un sito personale. Grazie.

Non utilizzare variabili globali, è semplice.
Perché non usare le globali
Perché l'uso di variabili globali rende più difficile la manutenzione del software nel lungo periodo.
- Una globale può essere dichiarata ovunque nel codice, o da nessuna parte, quindi non c'è un posto in cui puoi istintivamente cercare per trovare qualche commento su a cosa serve la globale
- Durante la lettura del codice di solito si assume che le variabili siano locali alla funzione e non si capisce che cambiarne il valore in una funzione potrebbe avere un effetto a livello di sistema.
- Se non gestiscono input, le funzioni dovrebbero restituire lo stesso valore/output quando vengono chiamate con gli stessi parametri. L'uso di globali in una funzione introduce parametri aggiuntivi che non sono documentati nella dichiarazione della funzione.
- Le globali non hanno alcun costrutto di inizializzazione specifico e quindi non puoi mai essere sicuro di quando puoi accedere al valore della globale, e non ottieni alcun errore quando tenti di accedere alla globale prima dell'inizializzazione.
- Qualcun altro (un plugin magari) potrebbe usare globali con lo stesso nome, rovinando il tuo codice, o tu rovinando il suo a seconda dell'ordine di inizializzazione.
Il core di WordPress utilizza moltissime globali, decisamente troppe. Mentre cerchi di capire come funzionano funzioni e hook di base come the_content
, improvvisamente ti rendi conto che la variabile $more
non è locale ma globale e devi cercare in tutti i file del core per capire quando viene impostata a true.
Quindi cosa si può fare quando si cerca di evitare di copiare-incollare diverse righe di codice invece di memorizzare il risultato del primo run in una globale? Ci sono diversi approcci, funzionali e OOP.
La funzione "sweetener". È semplicemente un wrapper/macro per evitare il copia/incolla
// input: $id - l'ID della categoria
// restituisce: il valore foo2 della categoria
function notaglobal($id) {
$a = foo1($id);
$b = foo2($a);
return $b;
}
I vantaggi sono che ora c'è una documentazione su cosa faccia la precedente globale, e hai un punto ovvio per il debug quando il valore restituito non è quello che ti aspetti.
Una volta che hai una funzione "sweetener" è facile memorizzare il risultato nella cache se necessario (fallo solo se scopri che questa funzione impiega molto tempo per essere eseguita)
function notaglobal($id) {
static $cache;
if (!isset($cache)) {
$a = foo1($id);
$b = foo2($a);
$cache = $b;
}
return $cache;
}
Questo ti dà lo stesso comportamento di una globale ma con il vantaggio di avere un'inizializzazione assicurata ogni volta che vi accedi.
Puoi avere pattern simili con l'OOP. Trovo che l'OOP di solito non aggiunga alcun valore in plugin e temi, ma questa è una discussione diversa
class notaglobal {
var latestfoo2;
__constructor($id) {
$a = foo1($id);
$this->latestfoo2 = foo2($a)
}
}
$v = new notaglobal($cat_id);
echo $v->latestfoo2;
Questo è un codice più goffo, ma se hai diversi valori che vorresti precalcolare perché vengono sempre utilizzati, questa può essere una strada da percorrere. Fondamentalmente è un oggetto che contiene tutte le tue globali in modo organizzato. Per evitare di rendere un'istanza di questo oggetto una globale (vuoi solo un'istanza altrimenti ricalcoli i valori) potresti voler usare un pattern singleton (alcuni sostengono che sia una cattiva idea, dipende da te)
Non mi piace accedere direttamente a un attributo di un oggetto, quindi nel mio codice lo wrapperò un po' di più
class notaglobal {
var latestfoo2;
__constructor() {}
foo2($id) {
if (!isset($this->latestfoo2)) {
$a = foo1($id);
$b = foo2($a);
$this->latestfoo2= $b;
}
return $this->latestfoo2;
}
}
$v = new notaglobal();
echo $v->foo2($cat_id);

Per favore, non urlare. Potresti spiegare il perché e fornire qualche tipo di citazione?

Penso che tu abbia frainteso la risposta. Se non avesse cercato di fare un'ottimizzazione prematura memorizzando valori in variabili globali, il suo codice avrebbe funzionato. L'urlare è perché seguire i principi base stabiliti dello sviluppo software è qualcosa che non può essere sottolineato abbastanza. Le persone che non comprendono questi principi base (disponibili con una semplice ricerca su Google) non dovrebbero diffondere codice in rete.

Ciao, Mark, scusa, il mio commento era breve quanto la tua risposta e avrei dovuto spiegarmi meglio: 1) IMO, il grassetto è sufficiente per fare un punto. 2) Sebbene a volte non ci sia altro da dire, sospetto delle risposte di una riga: Va bene pubblicare una risposta di una riga o sarebbe meglio come commento?

sì, solo dopo aver postato mi sono reso conto che avrei dovuto usare il grassetto. sistemerò quell'aspetto

IMO questa è una risposta, le persone che arrivano qui da Google dovrebbero vedere che è una cattiva idea anche solo pensare di usare le globali subito.

Non basta dire non fare X, devi spiegare perché altrimenti sembra che lo dici a caso

@MarkKaplun Cosa faresti invece per evitare di dover scrivere la stessa cosa più e più volte, e poi dover cambiare ciascuna manualmente se una qualsiasi parte dovesse cambiare?

@TomJNowell, Trovo divertente che fossi l'unico a votare negativamente la domanda stessa, dato che era ovviamente al di fuori dello scopo di WASE. Non vedevo il valore di approfondire un argomento che non avrebbe dovuto nemmeno iniziare qui.

@MarkKaplun Fantastico. La tua prima soluzione sembra la migliore. Sperimenterò con la cache, che probabilmente sarà necessaria. Non sono sicuro del perché questo sia al di fuori dello scopo di questo stackexchange? La domanda riguarda PHP, ma risulta che abbia tutto a che fare con il modo in cui WordPress gestisce le variabili globali. Inoltre il caso è specifico per i menu di navigazione di WordPress.

"Il core di WordPress fa un uso eccessivo, davvero eccessivo, delle variabili globali." Direi che WordPress abbia qualsiasi variabile globale sia già troppo, ma questa è solo la mia opinione.

@MarkKaplun grazie per il tuo approccio funzionale. Nel caso in cui lo adottassimo, potresti fare un aggiornamento per mostrarci come dovrebbe apparire con un valore di fallback $ID, se per qualche motivo non esistesse, non fosse impostato o non fosse un intero positivo?

La tua domanda riguarda il funzionamento di PHP.
Prendiamo $wpdb come esempio
$wpdb è una variabile globale ben conosciuta.
Sai quando viene dichiarata e assegnata con i valori?
Ogni pagina caricata, sì, ogni volta che visiti il tuo sito WordPress.
Allo stesso modo, devi assicurarti che quelle variabili che vuoi rendere globali vengano dichiarate e assegnate con i valori corrispondenti ad ogni caricamento di pagina.
Anche se non sono un designer di temi, posso dirti che after_setup_theme è un hook che viene eseguito una sola volta. Verrà attivato solo quando il tema viene attivato.
Se fossi in te, userei init o altri hook. No, se fossi in te, non userei affatto variabili globali...
Non sono davvero bravo a spiegare le cose. Quindi, dovresti prendere un libro se vuoi approfondire PHP.

Puoi sempre utilizzare il pattern singleton tramite getter statici.
<ul>
<li><?php echo MyGlobals::get_nav_prop( 'proposal' )[ 'html' ]; ?></li>
<li><?php echo MyGlobals::get_nav_prop( 'calvinball', 'html' ); ?></li>
</ul>
<?php
if ( ! class_exists('MyGlobals') ):
class MyGlobals {
public $props;
public function __construct(){
$this->props = array (
'proposal' => array( 'title' => 'Proposta', 'text' => 'Proposta' ),
'calvinball' => array( 'title' => 'Calvinball', 'text' => 'Calvinball' ),
);
}
public function get_nav_prop ( $term, $prop = false )
{
$o = self::instance();
if ( ! isset( $o->props[$term] ) ) { return false; }
if ( ! isset( $o->props[$term][ 'html' ] ) ) {
$id = get_cat_ID( $term );
$link = esc_url ( get_category_link( $id ) );
$title = $o->props[$term]['title'];
$text = $o->props[$term]['text'];
$o->props[$term]['html'] = '<a href="'.$link.'" title="'.$title.'">'.$text.'</a>';
$o->props[$term]['link'] = $link;
$o->props[$term]['id'] = $id;
}
if($prop){ return isset($o->props[$term][$prop]) ? $o->props[$term][$prop] : null; }
return $o->props[$term];
}
// -------------------------------------
private static $_instance;
public static function instance(){
if(!isset(self::$_instance)) {
self::$_instance = new MyGlobals();
}
return self::$_instance;
}
}
endif; // fine MyGlobals
