Jak zainicjować zmienne statyczne

207

Mam ten kod:

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

Co daje mi następujący błąd:

Błąd analizy: błąd składni, nieoczekiwany „(”, oczekiwanie „)” w /home/user/Sites/site/registration/inc/registration.class.inc w linii 19

Więc chyba coś robię źle ... ale jak mogę to zrobić, jeśli nie tak? Jeśli zmienię mktime na zwykłe ciągi, to działa. Wiem, że mogę to zrobić w ten sposób ...

Czy ktoś ma jakieś wskazówki?

Svish
źródło
2
Pierwsza odpowiedź została przegłosowana. Zobacz stackoverflow.com/a/4470002/632951
Pacerier
1
@Pacerier Nie sądzę. Odpowiedź nr 2 ma dużo narzutu w porównaniu do odpowiedzi nr 1
Kontrollfreak,
2
@Pacerier pyta 10 osób, żadna z nich nie wolałaby tego.
Buffalo,

Odpowiedzi:

345

PHP nie może analizować nietrywialnych wyrażeń w inicjalizatorach.

Wolę obejść ten problem, dodając kod zaraz po definicji klasy:

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

lub

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

PHP 5.6 obsługuje teraz niektóre wyrażenia.

/* 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
źródło
135
Uwielbiam PHP, ale czasami jest to naprawdę dziwne.
Marco Demaio,
6
Wiem, że to stare, ale ja też używam tej metody. Odkryłem jednak, że czasami Foo :: init () nie jest wywoływany. Nigdy nie byłem w stanie wyśledzić, dlaczego, ale chciałem tylko uświadomić wszystkim.
lucifurious
1
@porneL, pierwsza metoda nie zadziałałaby, ponieważ nie masz dostępu do zmiennych prywatnych. Druga metoda działa, ale zmusza nas do initupublicznienia, co jest brzydkie. Jakie jest lepsze rozwiązanie?
Pacerier
2
@Pacerier pierwsza metoda korzysta z własności publicznej z określonego powodu. AFAIK: obecnie nie ma lepszego rozwiązania w PHP (odpowiedź Tjeerda Vissera nie jest zła). Ukrywanie włamania w module ładującym klasy nie powoduje jego odejścia, wymusza fałszywe dziedziczenie i jest odrobiną sprytu, który może nieoczekiwanie się zepsuć (np. Gdy plik jest jawnie wymagany () d).
Kornel,
1
@porneL Prosta tablica działa dla mnie w PHP 5.6.x, chociaż nie jest wymieniona w RFC. Przykład:class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);
Pang
32

Jeśli masz kontrolę nad ładowaniem klas, możesz stamtąd dokonać inicjalizacji statycznej.

Przykład:

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

w module ładującym klasy wykonaj następujące czynności:

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

Bardziej ciężkim rozwiązaniem byłoby użycie interfejsu z ReflectionClass:

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

w module ładującym klasy wykonaj następujące czynności:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Emanuel Landeholm
źródło
Jest to bardziej niż nieco podobne do konstruktorów statycznych w języku c #, od wieków używam czegoś podobnego i działa świetnie.
Kris,
@EmanuelLandeholm, więc czy metoda pierwsza jest szybsza, czy metoda druga szybsza?
Pacerier
@Kris To nie przypadek. Zainspirowało mnie c # w momencie odpowiadania.
Emanuel Landeholm
1
@Pacerier Nie mam dowodu, ale podejrzewam, że ReflectionClass () może ponieść dodatkowe koszty. OTOH, pierwsza metoda przyjmuje nieco niebezpieczne założenie, że każda metoda o nazwie „static_init” w dowolnej klasie ładowanej przez moduł ładujący klasy jest statycznym inicjatorem. Może to prowadzić do trudnych do wyśledzenia błędów, np. z zajęciami osób trzecich.
Emanuel Landeholm
23

Zamiast znaleźć sposób na działanie zmiennych statycznych, wolę po prostu utworzyć funkcję gettera. Przydaje się również, jeśli potrzebujesz tablic należących do określonej klasy i jest o wiele prostszy w implementacji.

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

Gdziekolwiek potrzebujesz listy, po prostu wywołaj metodę gettera. Na przykład:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
diggie
źródło
11
Chociaż jest to eleganckie rozwiązanie, nie powiedziałbym, że jest idealne ze względu na wydajność, głównie ze względu na liczbę przypadków, w których tablica może być potencjalnie zainicjowana - tj. Dużo alokacji sterty. Ponieważ php jest napisane w C, wyobrażam sobie, że tłumaczenie rozwiązałoby funkcję zwracającą wskaźnik do tablicy na wywołanie ... Tylko moje dwa centy.
zeboidlund
Ponadto wywołania funkcji są drogie w PHP, więc najlepiej ich unikać, jeśli nie są konieczne.
Mark Rose
14
„najlepiej ich unikać, gdy nie jest to konieczne” - nie bardzo. Unikaj ich, jeśli (mogą) stać się wąskim gardłem. W przeciwnym razie jest to przedwczesna optymalizacja.
psycho brm
2
@blissfreak - można uniknąć ponownego przypisania, JEŚLI tworzymy właściwość statyczną w klasie i sprawdzamy w getTypeList (), czy została już zainicjalizowana, i zwracamy to. Jeśli jeszcze nie został zainicjowany, zainicjuj go i zwróć tę wartość.
grantwparks
12
Poważnie nie próbuję unikać wywołań funkcji. Funkcje są podstawą programowania strukturalnego.
grantwparks
11

Używam kombinacji odpowiedzi Tjeerda Vissera i 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() ]]
    }
}

Ale jeszcze lepszym rozwiązaniem jest wyeliminowanie metod statycznych i użycie wzorca Singleton. Następnie wykonujesz skomplikowaną inicjalizację w konstruktorze. Lub uczyń go „usługą” i użyj DI, aby wstrzyknąć go do dowolnej klasy, która tego potrzebuje.

Mambazo
źródło
10

To zbyt skomplikowane, aby można je było zdefiniować w definicji. Możesz jednak ustawić definicję na null, a następnie w konstruktorze sprawdź ją, a jeśli nie została zmieniona - ustaw:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Alister Bulman
źródło
10
ale czy konstruktor będzie pomocny w klasie abstrakcyjnej, która nigdy nie jest tworzona?
Svish
Klasa abstrakcyjna nie może być użyta, dopóki nie zostanie ukończona i utworzona instancja. Powyższej konfiguracji nie trzeba wykonywać specjalnie w konstruktorze, o ile jest ona wywoływana gdzieś przed użyciem zmiennej.
Alister Bulman
Założenie, że konstruktor jest wywoływany przed potrzebą zmiennej statycznej, nie jest dobrym pomysłem. Często trzeba utworzyć wartość statyczną przed utworzeniem instancji.
ToolmakerSteve
4

W tej części kodu nie można wykonywać wywołań funkcji. Jeśli stworzysz metodę typu init (), która zostanie wykonana przed wykonaniem jakiegokolwiek innego kodu, wówczas będziesz mógł wypełnić zmienną.

alp
źródło
metoda typu init ()? czy możesz podać przykład? czy jest to rodzaj statycznego konstruktora w C #?
Svish
@Svish: Nie. Należy go nazwać tuż poniżej definicji klasy jako zwykłą metodę statyczną.
Vladislav Rastrusny
4

W PHP 7.0.1 mogłem to zdefiniować:

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

A następnie użyj tego w ten sposób:

MyClass::$kIdsByActions[$this->mAction];
Bawół
źródło
FWIW: To, co pokazujesz, nie wymaga PHP 7; działało dobrze w momencie, gdy zadano pytanie: „Jeśli zmienię mktime na zwykłe ciągi, to zadziała ”. To, czego szuka ten wątek, to techniki inicjowania statycznego, gdy inicjalizacja musi wywołać jedną lub więcej funkcji .
ToolmakerSteve
3

najlepszym sposobem jest utworzenie takiego akcesorium:

/**
* @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;
}

wtedy możesz zrobić static :: db (); lub self :: db (); skądkolwiek.

espaciomore
źródło
-1

Oto, mam nadzieję, pomocny wskaźnik, w przykładzie kodu. Zwróć uwagę, jak funkcja inicjalizacyjna jest wywoływana tylko raz.

Ponadto, jeśli odwrócisz połączenia StaticClass::initializeStStateArr()i $st = new StaticClass()uzyskasz ten sam wynik.

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

Co daje:

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
David Luhman
źródło
2
Pamiętaj jednak, że utworzyłeś instancję klasy (obiektu), ponieważ konstruktor jest publiczną funkcją NIESTATYCZNĄ. Pytanie brzmi: czy PHP obsługuje tylko statyczne konstruktory (bez tworzenia instancji. Na przykład jak w Javiestatic { /* some code accessing static members*/ }
Mitja Gustin