Как инициализировать статические переменные

207

У меня есть этот код:

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

Что дает мне следующую ошибку:

Ошибка синтаксического анализа: синтаксическая ошибка, неожиданный '(', ожидающий ')' в /home/user/Sites/site/registration/inc/registration.class.inc строке 19

Итак, я думаю, что я делаю что-то не так ... но как я могу это сделать, если не так? Если я изменю mktime с помощью обычных строк, это сработает. Так что я знаю, что могу сделать что- то вроде этого ..

У кого-нибудь есть указатели?

Svish
источник
2
Первый ответ окончен. См stackoverflow.com/a/4470002/632951
Pacerier
1
@Pacerier Я так не думаю. Ответ № 2 имеет много накладных расходов по сравнению с Ответом № 1
Kontrollfreak
2
@Pacerier спросить 10 человек, никто из них не предпочел бы это.
Буффало

Ответы:

345

PHP не может анализировать нетривиальные выражения в инициализаторах.

Я предпочитаю обходить это путем добавления кода сразу после определения класса:

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

или

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

PHP 5.6 теперь может обрабатывать некоторые выражения.

/* 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();
}
Корнель
источник
135
Я люблю PHP, но иногда это действительно странно.
Марко Демайо
6
Я знаю, что это старый, но я тоже использую этот метод. Однако я обнаружил, что иногда Foo :: init () не вызывается. Я никогда не мог отследить почему, но просто хотел, чтобы все знали.
выгодно
1
@porneL, первый метод не будет работать, потому что у вас нет доступа к закрытым переменным. Второй метод работает, но он заставляет нас initобнародовать, что уродливо. Какое решение лучше?
Pacerier
2
@Pacerier первый метод использует публичное свойство по причине. AFAIK, в настоящее время нет лучшего решения в PHP (хотя ответ Tjeerd Visser неплохой). Сокрытие взлома в загрузчике классов не заставляет его уходить, вызывает ложное наследование, и это немного хитрости, которая может неожиданно сломаться (например, когда file явно требует require () d).
Корнель
1
@porneL Простой массив работает для меня в PHP 5.6.x, хотя и не упоминается в RFC. Пример:class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);
Панг
32

Если у вас есть контроль над загрузкой классов, вы можете выполнить статическую инициализацию оттуда.

Пример:

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

в вашем загрузчике классов сделайте следующее:

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

Более тяжелым решением будет использование интерфейса с ReflectionClass:

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

в вашем загрузчике классов сделайте следующее:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Эмануэль Ландехольм
источник
Это более чем похоже на статические конструкторы в c #, я использовал что-то очень похожее целую вечность, и это прекрасно работает.
Крис
@EmanuelLandeholm, так что метод один быстрее или метод два быстрее?
Pacerier
@ Kris Это не совпадение. Я был вдохновлен c # во время ответа.
Эмануэль Ландехольм,
1
@Pacerier У меня нет доказательств, но я подозреваю, что ReflectionClass () может повлечь за собой дополнительные затраты. OTOH, первый метод делает несколько опасное предположение, что любой метод, называемый static_init в любом классе, загруженном загрузчиком классов, является статическим инициализатором. Это может привести к некоторым трудно выявляемым ошибкам, например. со сторонними классами.
Эмануэль Ландехольм,
23

Вместо того, чтобы найти способ заставить работать статические переменные, я предпочитаю просто создать функцию получения. Также полезно, если вам нужны массивы, принадлежащие определенному классу, и намного проще в реализации.

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

Везде, где вам нужен список, просто вызовите метод получения. Например:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
Diggie
источник
11
Хотя это элегантное решение, я бы не сказал, что оно идеально по соображениям производительности, в первую очередь из-за того, сколько раз массив мог быть потенциально инициализирован, то есть из-за большого количества выделенной памяти. Поскольку php написан на C, я думаю, что перевод разрешится функцией, возвращающей указатель на Array за вызов ... Только мои два цента.
Zeboidlund
Кроме того, вызовы функций в PHP дороги, поэтому лучше их избегать, если они не нужны.
Марк Роуз
14
«Лучше всего избегать их, когда это не нужно» - не совсем. Избегайте их, если они (могут) стать узкими местами. В противном случае это преждевременная оптимизация.
psycho brm
2
@blissfreak - можно избежать повторного выражения, ЕСЛИ мы создаем статическое свойство в классе, и проверяем в getTypeList (), если оно уже было инициализировано, и возвращаем его. Если еще не инициализирован, инициализируйте его и верните это значение.
grantwparks
12
Я серьезно не пытаюсь избежать вызовов функций. Функции являются основой структурного программирования.
grantwparks
11

Я использую комбинацию ответа Tjeerd Visser и 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() ]]
    }
}

Но еще лучшее решение - избавиться от статических методов и использовать шаблон Singleton. Затем вы просто делаете сложную инициализацию в конструкторе. Или сделайте его «сервисом» и используйте DI, чтобы внедрить его в любой класс, который в этом нуждается.

Mambazo
источник
10

Это слишком сложно, чтобы установить в определении. Вы можете установить определение в null, а затем в конструкторе проверить его, и если оно не было изменено - установите его:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Алистер Булман
источник
10
но поможет ли конструктор в абстрактном классе, который никогда не создается?
Свиш
Абстрактный класс не может быть с пользой использован, если он не был завершен и создан. Вышеуказанная настройка не должна выполняться специально в конструкторе, если она вызывается где-то перед использованием переменной.
Алистер Булман
Не стоит предполагать, что конструктор вызывается до того, как потребуется статическая переменная. Часто нужно статическое значение, прежде чем созданием экземпляра.
ToolmakerSteve
4

Вы не можете выполнять вызовы функций в этой части кода. Если вы создадите метод типа init (), который будет выполнен раньше, чем любой другой код, тогда вы сможете заполнить переменную.

alxp
источник
метод типа init ()? не могли бы вы привести пример? это что-то вроде статического конструктора в C #?
Свиш
@Svish: Нет. Он должен вызываться чуть ниже определения класса как обычный статический метод.
Владислав Раструсный
4

В PHP 7.0.1 я смог определить это:

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

И затем используйте это так:

MyClass::$kIdsByActions[$this->mAction];
буйвол
источник
FWIW: То, что вы показываете, не требует PHP 7; в тот момент, когда был задан вопрос, все работало нормально: «Если я изменю материал mktime обычными строками, это сработает». То, что ищет этот поток, это методы для инициализации статического, когда инициализация должна вызвать одну или несколько функций .
ToolmakerSteve
3

Лучший способ - создать такой метод доступа:

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

тогда вы можете сделать static :: db (); или self :: db (); откуда угодно.

espaciomore
источник
-1

Вот, надеюсь, полезный указатель в примере кода. Обратите внимание, как функция инициализатора вызывается только один раз.

Кроме того, если вы перевернете вызовы, StaticClass::initializeStStateArr()и $st = new StaticClass()вы получите тот же результат.

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

Что дает :

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
Дэвид Лухман
источник
2
Но учтите, что вы создали экземпляр класса (объекта), потому что конструктор является публичной НЕСТАТИЧЕСКОЙ функцией. Вопрос в том, поддерживает ли PHP только статические конструкторы (создание экземпляров не требуется. Например, как в Javastatic { /* some code accessing static members*/ }
Митя Густин,