Cómo configurar y usar variables globales en PHP o por qué evitarlas

4 mar 2013, 06:23:04
Vistas: 275K
Votos: 48

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.

11
Comentarios

¿Qué enlace muestra esta declaración echo esc_url( $category_link_prop );? ¿Cuál es el enlace esperado?

Vinod Dalvi Vinod Dalvi
4 mar 2013 06:57:39

¿Por qué no simplemente usar 'get_cat_ID( * )' donde sea que hayas planeado usar la variable global? Dudo que haya alguna ventaja de velocidad de la manera en que lo estás haciendo. Desde un punto de vista de legibilidad, 'get_cat_ID( * )' gana por mucho.

Chris Strutton Chris Strutton
4 mar 2013 07:09:02

¿Puedes reformular? Leí tu pregunta y todavía no estoy seguro de lo que quieres hacer y por qué quieres hacerlo. Mi consejo general sería no usar variables globales y no contaminar el ámbito global

Tom J Nowell Tom J Nowell
4 mar 2013 12:59:08

@VinodDalvi Esperaba obtener el enlace a la categoría con el slug 'proposal'.

JPollock JPollock
5 mar 2013 00:30:40

@TomJNowell Lo editaré en un minuto para reflejar que estoy usando esto para navegaciones, de modo que se usarán en prácticamente cualquier página de todos modos.

JPollock JPollock
5 mar 2013 00:31:59

@ChrisStrutton get_cat_id() no me da todo lo que necesito (es decir, el enlace, el título, etc.). La legibilidad no es una gran preocupación para mí, seré el único que lea esto.

JPollock JPollock
5 mar 2013 01:08:26

esto suena un poco como un Problema X/Y. quizás deberías retroceder y explicar exactamente cuál es el resultado deseado. Estoy seguro de que hay una solución mucho más elegante que establecer un montón de variables globales para luego hacer referencias codificadas a ellas en una navegación en otro lugar

Milo Milo
5 mar 2013 01:41:40

@Milo Excelente punto. Mi problema real es que mi tema tiene 4 navegaciones diferentes (hasta ahora). 1 que es una barra superior estándar, y 3 que solo se muestran según ciertas condiciones. Todos muestran diferentes combinaciones de aproximadamente las mismas cosas y están en órdenes arbitrarios y van a cambiar a medida que el proyecto avance. Mi problema es que si los codifico manualmente, tendré que codificar lo mismo una y otra vez, y luego cambiar lo mismo 4 veces cada vez que algo cambie.

JPollock JPollock
5 mar 2013 01:49:36

crea una función que genere tu menú basado en el contexto que le pases, de esa manera puedes mantener toda la lógica del menú y las variables asociadas encapsuladas en un solo lugar.

Milo Milo
5 mar 2013 01:57:36

@Milo Eso es con lo que empecé, pero ahora tengo una función para la navegación de la barra superior; una función para una barra lateral de navegación que creció hasta tener 4 versiones diferentes basadas en condicionales; y una función para un sub-nav en una plantilla de página y habrá más de esos a medida que esto avance. No se me ocurre una manera sensata de combinar todo eso en una sola función.

JPollock JPollock
5 mar 2013 02:01:25

@Milo También quiero poder usarlos como enlaces a varias partes del sitio en el texto del sitio, ya sea en publicaciones (si eso funciona) o en plantillas de página.

JPollock JPollock
5 mar 2013 02:11:02
Mostrar los 6 comentarios restantes
Todas las respuestas a la pregunta 4
7
56

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 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.

4 mar 2013 13:05:02
Comentarios

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!

JPollock JPollock
5 mar 2013 00:34:04

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

Tom J Nowell Tom J Nowell
5 mar 2013 09:59:13

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í...

JPollock JPollock
6 mar 2013 10:12:12

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

Tom J Nowell Tom J Nowell
6 mar 2013 10:45:46

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).

cvr cvr
3 jun 2018 14:37:02

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

Tom J Nowell Tom J Nowell
3 jun 2018 16:48:58

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.

cvr cvr
3 jun 2018 17:20:09
Mostrar los 2 comentarios restantes
13
26

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);
4 mar 2013 07:09:37
Comentarios

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

brasofilo brasofilo
4 mar 2013 08:04:23

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.

Mark Kaplun Mark Kaplun
4 mar 2013 12:11:43

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?

brasofilo brasofilo
4 mar 2013 12:37:46

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

Mark Kaplun Mark Kaplun
4 mar 2013 12:40:41

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.

Mark Kaplun Mark Kaplun
4 mar 2013 12:43:47

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

Tom J Nowell Tom J Nowell
4 mar 2013 13:00:22

@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?

JPollock JPollock
5 mar 2013 00:53:16

@JPollock, edité la respuesta.

Mark Kaplun Mark Kaplun
5 mar 2013 07:28:05

@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.

Mark Kaplun Mark Kaplun
5 mar 2013 08:27:33

@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.

JPollock JPollock
6 mar 2013 07:44:16

"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.

R Porter R Porter
2 abr 2014 13:35:14

@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?

klewis klewis
21 oct 2016 15:25:53

Las variables globales pueden ser malas por muchas razones, pero también lo son las personas que dicen que nunca se deben usar variables globales.

Joel M Joel M
18 feb 2018 06:15:10
Mostrar los 8 comentarios restantes
0

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.

4 mar 2013 09:04:33
0

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
25 nov 2015 10:35:03