Cómo inicializar variables estáticas

207

Tengo este codigo:

private static $dates = array(
  'start' => mktime( 0,  0,  0,  7, 30, 2009),  // Start date
  'end'   => mktime( 0,  0,  0,  8,  2, 2009),  // End date
  'close' => mktime(23, 59, 59,  7, 20, 2009),  // Date when registration closes
  'early' => mktime( 0,  0,  0,  3, 19, 2009),  // Date when early bird discount ends
);

Lo que me da el siguiente error:

Error de análisis: error de sintaxis, inesperado '(', esperando ')' en /home/user/Sites/site/registration/inc/registration.class.inc en la línea 19

Entonces, supongo que estoy haciendo algo mal ... pero ¿cómo puedo hacer esto si no es así? Si cambio las cosas de mktime con cadenas regulares, funciona. Entonces sé que puedo hacerlo de esa manera ...

Alguien tiene algunos consejos?

Svish
fuente
2
La primera respuesta ha sido votada en exceso. Ver stackoverflow.com/a/4470002/632951
Pacerier
1
@Pacerier, no lo creo. La respuesta n. ° 2 tiene muchos gastos generales en comparación con la respuesta n. ° 1
Kontrollfreak el
2
@Pacerier pregunta a 10 personas, ninguna de ellas preferiría eso.
Buffalo

Respuestas:

345

PHP no puede analizar expresiones no triviales en inicializadores.

Prefiero evitar esto agregando código justo después de la definición de la clase:

class Foo {
  static $bar;
}
Foo::$bar = array(…);

o

class Foo {
  private static $bar;
  static function init()
  {
    self::$bar = array(…);
  }
}
Foo::init();

PHP 5.6 puede manejar algunas expresiones ahora.

/* For Abstract classes */
abstract class Foo{
    private static function bar(){
        static $bar = null;
        if ($bar == null)
            bar = array(...);
        return $bar;
    }
    /* use where necessary */
    self::bar();
}
Kornel
fuente
135
Me encanta PHP, pero a veces es realmente extraño.
Marco Demaio
66
Sé que esto es viejo, pero yo también uso este método. Sin embargo, descubrí que a veces no se llama a Foo :: init (). Nunca pude rastrear por qué, pero solo quería darme cuenta.
lucifurious
1
@porneL, el primer método no funcionaría porque no tiene acceso a variables privadas. El segundo método funciona pero nos obliga a hacer initpúblico lo que es feo. ¿Qué es una mejor solución?
Pacerier
2
@Pacerier, el primer método utiliza propiedad pública por una razón. AFAIK no hay una solución mejor en PHP en este momento (la respuesta de Tjeerd Visser no es mala). Ocultar el truco en el cargador de clases no lo hace desaparecer, fuerza una herencia falsa y es un poco de inteligencia que podría romperse inesperadamente (por ejemplo, cuando se requiere el archivo () d explícitamente).
Kornel
1
@porneL La matriz simple me funciona en PHP 5.6.x, aunque no se menciona en RFC. Ejemplo:class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);
Pang
32

Si tiene control sobre la carga de clases, puede hacer una inicialización estática desde allí.

Ejemplo:

class MyClass { public static function static_init() { } }

en su cargador de clases, haga lo siguiente:

include($path . $klass . PHP_EXT);
if(method_exists($klass, 'static_init')) { $klass::staticInit() }

Una solución más pesada sería utilizar una interfaz con ReflectionClass:

interface StaticInit { public static function staticInit() { } }
class MyClass implements StaticInit { public static function staticInit() { } }

en su cargador de clases, haga lo siguiente:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Emanuel Landeholm
fuente
Esto es más que algo similar a los constructores estáticos en C #, he estado usando algo bastante similar durante años y funciona muy bien.
Kris
@EmanuelLandeholm, entonces, ¿es el método uno más rápido o el método dos más rápido?
Pacerier
@ Kris Eso no es casualidad. Me inspiré en c # a la hora de responder.
Emanuel Landeholm
1
@Pacerier No tengo pruebas, pero sospecho que ReflectionClass () puede incurrir en más gastos generales. OTOH, el primer método hace la suposición algo peligrosa de que cualquier método llamado "static_init" en cualquier clase cargada por el cargador de clases es un inicializador estático. Esto podría conducir a algunos errores difíciles de localizar, por ejemplo. con clases de terceros.
Emanuel Landeholm
23

En lugar de encontrar una manera de hacer que funcionen las variables estáticas, prefiero simplemente crear una función getter. También es útil si necesita matrices que pertenecen a una clase específica y mucho más simple de implementar.

class MyClass
{
   public static function getTypeList()
   {
       return array(
           "type_a"=>"Type A",
           "type_b"=>"Type B",
           //... etc.
       );
   }
}

Donde sea que necesite la lista, simplemente llame al método getter. Por ejemplo:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
diggie
fuente
11
Si bien esta es una solución elegante, no diría que es ideal por razones de rendimiento, principalmente debido a la cantidad de veces que la matriz podría potencialmente inicializarse, es decir, mucha asignación de montón. Como php está escrito en C, me imagino que la traducción se resolvería en una función que devuelva un puntero a una matriz por llamada ... Solo mis dos centavos.
zeboidlund
Además, las llamadas a funciones son caras en PHP, por lo que es mejor evitarlas si no son necesarias.
Mark Rose
14
"Es mejor evitarlos cuando no es necesario", en realidad no. Evítelos si (podrían) convertirse en cuellos de botella. De lo contrario, es una optimización prematura.
psycho brm
2
@blissfreak: se puede evitar la reubicación, SI creamos una propiedad estática en la clase, y verificamos getTypeList () si ya se ha inicializado y devuelve eso. Si aún no se ha inicializado, inicialícelo y devuelva ese valor.
grantwparks
12
En serio, no trato de evitar las llamadas a funciones. Las funciones son la base de la programación estructurada.
grantwparks
11

Utilizo una combinación de la respuesta de Tjeerd Visser y de porneL.

class Something
{
    private static $foo;

    private static getFoo()
    {
        if ($foo === null)
            $foo = [[ complicated initializer ]]
        return $foo;
    }

    public static bar()
    {
        [[ do something with self::getFoo() ]]
    }
}

Pero una solución aún mejor es eliminar los métodos estáticos y usar el patrón Singleton. Entonces solo haces la complicada inicialización en el constructor. O conviértalo en un "servicio" y use DI para inyectarlo en cualquier clase que lo necesite.

Mambazo
fuente
10

Eso es demasiado complejo para establecerlo en la definición. Sin embargo, puede establecer la definición como nula, y luego en el constructor, verificarla y, si no se ha cambiado, configúrela:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Alister Bulman
fuente
10
pero, ¿será útil el constructor en una clase abstracta que nunca se instancia?
Svish
Una clase abstracta no se puede usar de manera útil a menos que se haya completado e instanciado. La configuración anterior no tiene que hacerse específicamente en un constructor, siempre que se llame a algún lugar antes de que se vaya a utilizar la variable.
Alister Bulman
No es una buena idea suponer que se llama al constructor antes de que se necesite una variable estática. A menudo se necesita un valor estático antes de crear una instancia.
ToolmakerSteve
4

No puede realizar llamadas a funciones en esta parte del código. Si crea un método de tipo init () que se ejecuta antes que cualquier otro código, entonces podrá completar la variable.

alxp
fuente
Método de tipo init ()? ¿podrías dar un ejemplo? ¿Es algo así como un constructor estático en C #?
Svish
@Svish: No. Debería llamarse justo debajo de la definición de clase como un método estático regular.
Vladislav Rastrusny
4

En PHP 7.0.1, pude definir esto:

public static $kIdsByActions = array(
  MyClass1::kAction => 0,
  MyClass2::kAction => 1
);

Y luego úsalo así:

MyClass::$kIdsByActions[$this->mAction];
Búfalo
fuente
FWIW: Lo que muestra no requiere PHP 7; funcionó bien en el momento en que se hizo la pregunta: "Si cambio las cosas de mktime con cadenas regulares, funciona". Lo que este hilo busca son técnicas para inicializar una estática, cuando la inicialización necesita llamar a una o más funciones .
ToolmakerSteve
3

La mejor manera es crear un descriptor de acceso como este:

/**
* @var object $db : map to database connection.
*/
public static $db= null; 

/**
* db Function for initializing variable.   
* @return object
*/
public static function db(){
 if( !isset(static::$db) ){
  static::$db= new \Helpers\MySQL( array(
    "hostname"=> "localhost",
    "username"=> "root",
    "password"=> "password",
    "database"=> "db_name"
    )
  );
 }
 return static::$db;
}

entonces puedes hacer static :: db (); o self :: db (); de donde sea.

espaciomore
fuente
-1

Aquí hay un puntero con suerte útil, en un ejemplo de código. Observe cómo la función de inicializador solo se llama una vez.

Además, si invierte las llamadas StaticClass::initializeStStateArr()y $st = new StaticClass()obtendrá el mismo resultado.

$ cat static.php
<?php

class StaticClass {

  public static  $stStateArr = NULL;

  public function __construct() {
    if (!isset(self::$stStateArr)) {
      self::initializeStStateArr();
    }
  }

  public static function initializeStStateArr() {
    if (!isset(self::$stStateArr)) {
      self::$stStateArr = array('CA' => 'California', 'CO' => 'Colorado',);
      echo "In " . __FUNCTION__. "\n";
    }
  }

}

print "Starting...\n";
StaticClass::initializeStStateArr();
$st = new StaticClass();

print_r (StaticClass::$stStateArr);

Cuyos rendimientos :

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
David Luhman
fuente
2
Pero tenga en cuenta que creó una instancia de clase (objeto), porque el constructor es una función pública NO ESTÁTICA. La pregunta es: ¿PHP solo admite constructores estáticos (sin creación de instancias. Por ejemplo, como en Java?static { /* some code accessing static members*/ }
Mitja Gustin