Como inicializar variáveis ​​estáticas

207

Eu tenho este código:

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

O que me dá o seguinte erro:

Erro de análise: erro de sintaxe, inesperado '(', expecting ')' em /home/user/Sites/site/registration/inc/registration.class.inc na linha 19

Então, acho que estou fazendo algo errado ... mas como posso fazer isso se não for assim? Se eu mudar o material do mktime com seqüências regulares, ele funcionará. Então, eu sei que posso fazê-lo tipo de como isso ..

Alguém tem algumas dicas?

Svish
fonte
2
A primeira resposta foi super-votada. Veja stackoverflow.com/a/4470002/632951
Pacerier
1
@ Pacerier Acho que não. Resposta # 2 tem um monte de sobrecarga em comparação com resposta # 1
Kontrollfreak
2
A @Pacerier pergunta a 10 pessoas, nenhuma delas prefere isso.
Buffalo19 de

Respostas:

345

O PHP não pode analisar expressões não triviais nos inicializadores.

Prefiro contornar isso adicionando código logo após a definição da classe:

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

ou

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

O PHP 5.6 pode lidar com algumas expressões agora.

/* 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
fonte
135
Eu amo PHP, mas às vezes é realmente estranho.
Marco Demaio 10/11
6
Eu sei que isso é antigo, mas eu também uso esse método. No entanto, descobri que algumas vezes o Foo :: init () não é chamado. Eu nunca fui capaz de rastrear o porquê, mas só queria conscientizar todos.
lucifurious 15/07/12
1
@porneL, o primeiro método não funcionaria porque você não tem acesso a variáveis ​​privadas. O segundo método funciona, mas nos obriga a tornar initpúblico o que é feio. Qual a melhor solução?
Pacerier 7/08/13
2
@Pacerier, o primeiro método usa propriedade pública por um motivo. AFAIK não há solução melhor no PHP no momento (a resposta de Tjeerd Visser não é ruim). Ocultar o hack no carregador de classes não faz com que ele desapareça, força a herança falsa e é um pouco de esperteza que pode ser interrompida inesperadamente (por exemplo, quando o arquivo é requerido () d explicitamente).
Kornel # 08/13
1
@porneL O array simples funciona para mim no PHP 5.6.x, embora não seja mencionado no RFC. Exemplo:class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);
Pang
32

Se você tiver controle sobre o carregamento de classe, poderá fazer a inicialização estática a partir daí.

Exemplo:

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

no seu carregador de classes, faça o seguinte:

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

Uma solução mais pesada seria usar uma interface com o ReflectionClass:

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

no seu carregador de classes, faça o seguinte:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Emanuel Landeholm
fonte
Isso é mais ou menos parecido com os construtores estáticos em c #, eu tenho usado algo bastante semelhante há muito tempo e funciona muito bem.
Kris
@EmanuelLandeholm, então o método é um mais rápido ou o método dois é mais rápido?
Pacerier 7/08/13
@ Kris Isso não é coincidência. Eu fui inspirado pelo c # no momento da resposta.
Emanuel Landeholm
1
@Pacerier Não tenho provas, mas desconfio que ReflectionClass () possa gerar mais despesas. OTOH, o primeiro método assume a perigosa suposição de que qualquer método chamado "static_init" em qualquer classe carregada pelo carregador de classes é um inicializador estático. Isso pode levar a alguns erros difíceis de localizar, por exemplo. com aulas de terceiros.
Emanuel Landeholm
23

Em vez de encontrar uma maneira de fazer funcionar as variáveis ​​estáticas, prefiro simplesmente criar uma função getter. Também é útil se você precisar de matrizes pertencentes a uma classe específica e muito mais simples de implementar.

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

Onde quer que você precise da lista, basta chamar o método getter. Por exemplo:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
diggie
fonte
11
Embora essa seja uma solução elegante, eu não diria que é ideal por motivos de desempenho, principalmente por causa da quantidade de vezes que a matriz pode ser inicializada - ou seja, muita alocação de heap. Como o php é escrito em C, eu imaginaria que a tradução resolveria uma função retornando um ponteiro para uma matriz por chamada ... Apenas meus dois centavos.
Zeboidlund 22/08/2012
Além disso, as chamadas de função são caras no PHP, portanto, é melhor evitá-las se não forem necessárias.
Mark Rose
14
"melhor evitá-los quando não for necessário" - na verdade não. Evite-os se eles (puderem) se tornar gargalos. Caso contrário, é otimização prematura.
Psmcho brm
2
@blissfreak - pode-se evitar realocação, se criarmos uma propriedade estática na classe e verificar getTypeList () se já tiver sido inicializado e retornar isso. Se ainda não foi inicializado, inicialize-o e retorne esse valor.
grantwparks
12
Eu seriamente não tento evitar chamadas de função. Funções são a base da programação estruturada.
grantwparks
11

Uso uma combinação da resposta de Tjeerd Visser e 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() ]]
    }
}

Mas uma solução ainda melhor é acabar com os métodos estáticos e usar o padrão Singleton. Então você apenas faz a inicialização complicada no construtor. Ou torne-o um "serviço" e use o DI para injetá-lo em qualquer classe que precise dele.

Mambazo
fonte
10

Isso é muito complexo para definir na definição. Você pode definir a definição como nula e, no construtor, verifique-a e, se não tiver sido alterada - defina-a:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Alister Bulman
fonte
10
mas o construtor ajudará em uma classe abstrata que nunca é instanciada?
Svish
Uma classe abstrata não pode ser utilmente usada, a menos que tenha sido concluída e instanciada. A configuração acima não precisa ser feita especificamente em um construtor, desde que seja chamada em algum lugar antes que a variável seja usada.
Alister Bulman
Não é uma boa idéia supor que o construtor seja chamado antes que a variável estática seja necessária. Geralmente, é necessário um valor estático antes de criar uma instância.
ToolmakerSteve
4

Você não pode fazer chamadas de função nesta parte do código. Se você criar um método do tipo init () executado antes de qualquer outro código, poderá preencher a variável.

alxp
fonte
método do tipo init ()? Você poderia dar um exemplo? isso é como um construtor estático em c #?
Svish
@Svish: Não. Deve ser chamado logo abaixo da definição de classe como um método estático regular.
Vladislav Rastrusny
4

No PHP 7.0.1, eu consegui definir isso:

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

E então use-o assim:

MyClass::$kIdsByActions[$this->mAction];
Búfalo
fonte
FWIW: O que você mostra não requer PHP 7; funcionou bem no momento em que a pergunta foi feita: "Se eu mudar o material do mktime com seqüências regulares, ele funcionará". O que esse encadeamento está procurando, são técnicas para inicializar uma estática, quando a inicialização precisar chamar uma ou mais funções .
ToolmakerSteve 16/02
3

A melhor maneira é criar um acessador 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;
}

então você pode fazer static :: db (); ou self :: db (); de qualquer lugar.

espaciomore
fonte
-1

Aqui está um ponteiro esperançosamente útil, em um exemplo de código. Observe como a função inicializadora é chamada apenas uma vez.

Além disso, se você inverter as chamadas StaticClass::initializeStStateArr()e $st = new StaticClass()obter o mesmo 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);

Qual produz:

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
David Luhman
fonte
2
Mas observe que você criou uma instância da classe (objeto), porque o construtor é uma função pública NÃO ESTÁTICA. A pergunta é: O PHP construtores suporte estáticos apenas (sem criação de instância, por exemplo como em Java.static { /* some code accessing static members*/ }
Mitja Gustin