Cómo configurar y usar variables globales en PHP o por qué evitarlas
ACTUALIZACIÓN: Mi pregunta original fue resuelta, pero esto se convirtió en una discusión válida sobre por qué no usar variables globales, así que actualizo la pregunta para reflejarlo. La solución fue <?php global $category_link_prop; echo esc_url( $category_link_prop ); ?>
como sugirió @TomJNowell.
ACTUALIZACIÓN 2: Ahora funciona exactamente como quería. Pero todavía estoy usando ámbito global y estaría feliz de encontrar una mejor manera.
Estoy intentando configurar muchas variables globales para los enlaces permanentes a categorías que se usarán en varios lugares de mi tema. La razón principal es para usarlas tanto en la navegación principal como en una serie de subnavegaciones que se eligen según la categoría del post actual. Este no es un tema que lanzaré para otros, está construido para un propósito muy específico.
Así es como las estoy creando actualmente (solo he pegado algunas variables).
function set_global_nav_var()
{
//propuesta
global $prop;
// Obtener el ID de una categoría dada
$category_id_prop = get_cat_ID( 'proposal' );
// Obtener la URL de esta categoría
$category_link_prop = get_category_link( $category_id_prop );
$prop = '<a href="' .esc_url( $category_link_prop ). '" title="Propuesta">Propuesta</a>';
//Calvinball
global $cb;
// Obtener el ID de una categoría dada
$category_id_cb = get_cat_ID( 'calvinball' );
// Obtener la URL de esta categoría
$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' );
Ahora puedo hacer <?php global $prop; echo $prop; ?>
en los 4 lugares donde va y obtener todo el enlace para el código. Cuando eso cambie solo necesito modificarlo en un lugar. Estoy abierto a alternativas que no involucren el ámbito global.
Aunque recomiendo encarecidamente no hacer esto, y no acelerará las cosas, tu uso es incorrecto.
WordPress ya almacena en caché estas cosas en la caché de objetos/memoria, por lo que no tiene que recuperarlas múltiples veces en la misma solicitud, no necesitas almacenar el resultado y reutilizarlo, WordPress ya hace esto automáticamente.
¡Es muy probable que tu código se ejecute más lento como resultado de esta micro-optimización, ¡no más rápido!
Cómo Usar Variables Globales
Cuando intentas usar una variable global, debes especificar primero la palabra clave global
. La has especificado al definir su valor, pero fuera de ese ámbito necesita ser redeclarada como una variable de ámbito global.
Ejemplo en functions.php
:
function test() {
global $hello;
$hello = 'hola mundo';
}
add_action( 'after_setup_theme', 'test' );
En single.php
, esto no funcionará:
echo $hello;
Porque $hello
no está definida. Sin embargo, esto sí funcionará:
global $hello;
echo $hello;
Por supuesto, no deberías hacer ninguna de las dos. WordPress ya intenta almacenar en caché estas cosas en la caché de objetos.
Desventajas y Peligros de las Variables Globales
No obtendrás ningún aumento de velocidad al hacer esto (puedes experimentar una pequeña disminución), solo obtendrás complejidad adicional y la necesidad de escribir muchas declaraciones globales que no son necesarias.
También encontrarás otros problemas:
- Código imposible de probar
- Código que se comporta de manera diferente cada vez que se ejecuta
- Conflictos en nombres de variables debido a un espacio de nombres compartido
- Errores accidentales por olvidar declarar
global
- Falta total de estructura en el almacenamiento de datos de tu código
- Y muchos más
¿Qué Deberías Usar En Lugar de Esto?
Sería mejor usar datos estructurados, como objetos o inyección de dependencias, o en tu caso, un conjunto de funciones.
Aquí tienes 3 alternativas:
Variables Estáticas
Las variables estáticas no son buenas, pero piensa en ellas como el primo ligeramente menos malvado de las variables globales. Las variables estáticas son para las variables globales lo que el pan cubierto de barro es para el cianuro.
Por ejemplo, aquí hay una forma de hacer algo similar mediante variables estáticas:
function funcion_mala( $nuevo_hola='' ) {
static $hola;
if ( !empty( $nuevo_hola ) ) {
$hola = $nuevo_hola;
}
return $hola;
}
funcion_mala( 'teléfono' );
echo funcion_mala(); // imprime teléfono
funcion_mala( 'plátano');
echo funcion_mala(); // imprime plátano
Ten en cuenta que hay otras razones para usar variables estáticas que no están relacionadas con el almacenamiento en caché y el rendimiento, pero tienen sus propias desventajas. Es difícil, si no imposible, escribir pruebas adecuadas para una función que usa una variable estática, y hace que la depuración sea mucho más complicada, ya que necesitas rastrear el valor de esa variable.
En un buen código, una función pura siempre hace lo mismo cuando se le dan los mismos parámetros. Las funciones puras son predecibles y fáciles de probar. Una función con una variable estática nunca puede ser una función pura.
Singletons
Los singletons son objetos que se crean una vez y solo puede haber una única instancia de ese objeto. Son tan malos como las variables globales, solo que con diferente sintaxis, tienen todos los mismos problemas que las variables estáticas y dan la apariencia de programación orientada a objetos sin ninguno de sus beneficios. Evítalos.
Lectura adicional:
WP_Cache, Lo Que Intentaste Hacer Pero WordPress Ya Lo Hace
Si realmente quieres ahorrar tiempo almacenando datos en algún lugar para reutilizarlos, considera usar el sistema WP_Cache
con wp_cache_get
, etc. Por ejemplo:
$valor = wp_cache_get( 'hola' );
if ( false === $valor ) {
// no encontrado, establece el valor por defecto
wp_cache_set( 'hola', 'mundo' );
}
Ahora el valor se almacenará en caché durante la vida de la solicitud por WordPress, aparecerá en herramientas de depuración y, si tienes una caché de objetos, persistirá entre solicitudes.
¿Significa Esto Que Necesito Usar wp_cache_get
en Mi Código?
Probablemente no, WordPress ya hace esto automáticamente para publicaciones/usuarios/metadatos/términos/opciones/etc., por lo que nunca necesitas almacenar o recuperar datos de publicaciones de esta manera. Simplemente usa las funciones normales de la API y lo hará automáticamente detrás de escena.
Nota al margen 1: Observaría que algunas personas intentan persistir datos en variables globales entre solicitudes, sin saber que así no funciona PHP. A diferencia de una aplicación Node, cada solicitud carga una copia nueva de la aplicación, que luego muere cuando se completa la solicitud. Por esta razón, las variables globales establecidas en una solicitud no sobreviven a la siguiente.
Nota al margen 2: A juzgar por la pregunta actualizada, tus variables globales no te dan ninguna ganancia de rendimiento. En su lugar, deberías generar el HTML cuando lo necesites y se ejecutaría igual de rápido, quizás incluso un poco más rápido. Esto es micro-optimización.

Sé que es un poco loco usar el ámbito global, pero la mayoría, si no todas estas variables se usarán en cada página. Estoy abierto a mejores ideas. Voy a editar la pregunta para aclarar un poco mi intención. Por cierto, funciona perfectamente bien cuando hago <?php global $category_link_prop; echo esc_url( $category_link_prop ); ?>
como sugieres. ¡Gracias!

Ah, si mi solución funciona, ¿podrías marcarla como aceptada? Tus variables globales son igual de rápidas que hacer la llamada original, quizás quieras intentar usar funciones para no tener que escribir 2 líneas, mejor aún, un singleton, mejor aún, haz todo eso dinámico y en una parte de plantilla incluida via get_template_part

Marcado como aceptado ya que es lo que estoy haciendo ahora, aunque puede que vaya con una de las estrategias que @MarkKaplun sugiere abajo. Usar get_template_part() es una idea interesante, pero no estoy seguro de querer tener un directorio lleno de archivos cortos así...

oh no no, no querrías un archivo para cada categoría, querrías solo uno que tome el nombre de la categoría actual y lo use. No deberías tener que codificar nada manualmente, imagina el lío de codificarlo todo

Puse el código en mi child-functions.php que está activo. Pero no puedo acceder a la variable en un archivo php-include que llamo desde una publicación normal generada desde la base de datos. Por favor, aconséjame, ¿qué estoy haciendo mal? (Lo defino como global, por supuesto).

Tienes que declarar global
cada vez que lo uses. No es algo que funcione una vez y ya esté, tienes que usarlo cada, cada, maldita vez, sin excepciones de ningún tipo. Pero como digo en mi pregunta, las variables globales son mala práctica, problemáticas y no son la solución que buscas para tu problema. Incluso el mal que son los singletons sería una mejor solución

Gracias, agradezco tu comentario. Lo declaré y lo usé pero en algún lugar debí cometer un error. Simplemente no funcionó para mí. Probé diferentes enfoques. Finalmente este funcionó. Estoy de acuerdo con lo que dices sobre las globales, pero a veces se necesita una solución rápida para un sitio personal. Gracias.

No utilices variables globales, así de simple.
Por qué no usar globales
Porque el uso de globales hace que sea más difícil mantener el software a largo plazo.
- Una global puede ser declarada en cualquier parte del código, o en ninguna, por lo tanto no hay un lugar específico donde puedas buscar instintivamente algún comentario sobre para qué se usa la global
- Mientras lees código, usualmente asumes que las variables son locales a la función y no entiendes que cambiar su valor en una función podría tener un cambio a nivel de todo el sistema.
- Si no manejan entrada, las funciones deberían devolver el mismo valor/salida cuando son llamadas con los mismos parámetros. El uso de globales en una función introduce parámetros adicionales que no están documentados en la declaración de la función.
- Las globales no tienen ningún constructor de inicialización específico y por lo tanto nunca puedes estar seguro de cuándo puedes acceder al valor de la global, y no recibes ningún error al intentar acceder a la global antes de su inicialización.
- Alguien más (un plugin quizás) podría usar globales con el mismo nombre, arruinando tu código, o tú arruinando el suyo dependiendo del orden de inicialización.
El núcleo de WordPress usa muchísimas globales. Mientras intentas entender cómo funcionan funciones y hooks básicos como the_content
, de repente te das cuenta de que la variable $more
no es local sino global y necesitas buscar en todos los archivos del núcleo para entender cuándo se establece en verdadero.
Entonces, ¿qué se puede hacer cuando intentas dejar de copiar-pegar varias líneas de código en lugar de almacenar el resultado de la primera ejecución en una global? Hay varios enfoques, funcionales y POO.
La función edulcorante. Es simplemente un envoltorio/macro para ahorrar el copiar/pegar
// entrada: $id - el id de la categoría
// retorna: el valor foo2 de la categoría
function notaglobal($id) {
$a = foo1($id);
$b = foo2($a);
return $b;
}
Los beneficios son que ahora hay documentación sobre lo que hacía la antigua global, y tienes un punto obvio para depuración cuando el valor devuelto no es el que esperas.
Una vez que tienes un edulcorante, es fácil cachear el resultado si es necesario (hazlo solo si descubres que esta función tarda mucho en ejecutarse)
function notaglobal($id) {
static $cache;
if (!isset($cache)) {
$a = foo1($id);
$b = foo2($a);
$cache = $b;
}
return $cache;
}
Esto te da el mismo comportamiento de una global pero con la ventaja de tener una inicialización asegurada cada vez que accedes a ella.
Puedes tener patrones similares con POO. Encuentro que POO usualmente no añade ningún valor en plugins y temas, pero eso es una discusión diferente
class notaglobal {
var $latestfoo2;
__constructor($id) {
$a = foo1($id);
$this->latestfoo2 = foo2($a);
}
}
$v = new notaglobal($cat_id);
echo $v->latestfoo2;
Este es un código más torpe, pero si tienes varios valores que te gustaría precalcular porque siempre se usan, esta puede ser una manera de hacerlo. Básicamente es un objeto que contiene todas tus globales de manera organizada. Para evitar hacer una instancia de este objeto una global (quieres solo una instancia, de lo contrario recalculas los valores) podrías querer usar un patrón singleton (algunos argumentan que es una mala idea, depende de ti)
No me gusta acceder a un atributo de objeto directamente, así que en mi código lo envolvería un poco más
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);

Por favor, no grites. ¿Podrías explicar por qué y proporcionar algún tipo de cita?

Creo que malinterpretaste la respuesta. Si no estuviera intentando hacer una optimización prematura almacenando valores en variables globales, su código habría funcionado. Los gritos son porque seguir principios básicos establecidos de desarrollo de software es algo que no se puede enfatizar lo suficiente. Las personas que no entienden esos principios básicos (disponibles en tu búsqueda local de Google) no deberían distribuir código por la red.

Hola, Mark, disculpas, mi comentario fue tan breve como tu respuesta y debería haberme explicado mejor: 1) En mi opinión, negritas son suficientes para hacer un punto. 2) Aunque a veces no hay más que decir, sospecho de las respuestas de una línea: ¿Está bien publicar una respuesta de una línea, o sería mejor como comentario?

sí, solo después de publicar me di cuenta de que debería haber usado negrita. arreglaré ese aspecto

IMO esta es una respuesta, las personas que llegan aquí desde Google deberían ver que es una mala idea siquiera pensar en usar globales de inmediato.

No es suficiente decir no hagas X, tienes que explicar por qué o parecerá que lo dices por capricho

@MarkKaplun ¿Qué harías en su lugar para evitar tener que escribir el mismo conjunto de cosas una y otra vez, y luego tener que cambiar cada una manualmente si alguna parte de ello cambia?

@TomJNowell, me parece curioso que fui el único que votó en contra de la pregunta en sí, ya que obviamente estaba fuera del alcance de WASE. No vi el valor de expandir sobre un tema que no debería haber comenzado aquí en primer lugar.

@MarkKaplun Increíble. Tu primera solución suena como la mejor. Voy a experimentar con el caché, lo cual probablemente será necesario. No estoy seguro de por qué esto está fuera del alcance de este stackexchange. La pregunta es sobre PHP, pero resulta que tiene todo que ver con cómo WordPress maneja las globales. Además, el caso es específico para los menús de navegación de WordPress.

"El núcleo de WordPress usa way way way demasiadas variables globales." Diría que WordPress tener alguna variable global ya es demasiado, pero eso es solo mi opinión.

@MarkKaplun gracias por tu enfoque funcional. En caso de que lo adoptemos, ¿podrías hacer una actualización para mostrarnos cómo debería verse con un valor de respaldo de $ID, si por alguna razón no existiera, no estuviera configurado o no fuera un entero positivo?

Tu pregunta está relacionada con cómo funciona PHP.
Tomemos $wpdb como ejemplo.
$wpdb es una variable global muy conocida.
¿Sabes cuándo se declarará y se le asignarán valores?
Cada página cargada, sí, cada vez que visitas tu sitio WordPress.
De manera similar, debes asegurarte de que aquellas variables que quieras globalizar se declaren y se les asignen los valores correspondientes en cada carga de página.
Aunque no soy diseñador de temas, puedo decir que after_setup_theme es un hook de una sola vez. Solo se activará cuando el tema se active.
Si fuera tú, usaría init u otros hooks. No, si fuera tú, no usaría variables globales en absoluto...
Realmente no soy bueno explicando cosas. Por lo tanto, deberías tomar un libro si quieres profundizar en PHP.

Siempre puedes utilizar el patrón singleton a través de métodos estáticos de obtención.
<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' => 'Propuesta', 'text' => 'Propuesta' ),
'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; // end MyGlobals
