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

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

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 с помощью обычных строк, это сработает. Так что я знаю, что могу сделать это вроде этого..

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

10 ответов

Решение

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

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

Пример:

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() }

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

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

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

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}

Я использую комбинацию ответа 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, чтобы внедрить его в любой класс, который в этом нуждается.

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

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}

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

/**
* @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(); откуда угодно.

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

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

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

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

MyClass::$kIdsByActions[$this->mAction];

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

      class A
   {
   static $a; // Initialized by Init()
   static function Init()
      {
      A::$a=nontrivial();
      {
   }
...
A::Init();    // Initialize static part of class
...
$obj=new A(); // Using initialized class as an object

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

Кроме того, если вы инвертируете звонки 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
)
Другие вопросы по тегам