Як ініціалізувати статичні змінні


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 матеріал звичайними рядками, він працює. Так що я знаю , що я можу зробити це начебто як то ..

Хтось має вказівки?



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 (відповідь Теєрда Віссера непогана). Приховування зламу в завантажувачі класу не зникає, примушує помилкове успадкування, і це може бути трохи розумності, яка може несподівано зламатися (наприклад, коли файл вимагає () явно 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

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

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...
}

11
Хоча це елегантне рішення, я б не сказав, що це ідеально з міркувань продуктивності, в першу чергу через кількість разів потенційно може бути ініціалізовано масив - тобто, багато розподілу купи. Оскільки php написаний на C, я думаю, що переклад вирішив би функцію, що повертає вказівник на масив за виклик ... Просто два мої центи.
zeboidlund

Крім того, функціональні дзвінки в PHP дорогі, тому краще уникати їх, якщо вони не потрібні.
Марк Роуз

14
"Найкраще уникати їх, коли не потрібно" - не дуже. Уникайте їх, якщо вони (можуть) стати вузькими місцями. Інакше це передчасна оптимізація.
psycho brm

2
@blissfreak - можна уникнути повторного виклику, ЯКЩО ми створимо статичну властивість у класі та перевіримо в getTypeList (), якщо вона вже ініціалізована та повернемо це. Якщо ще не ініціалізовано, ініціалізуйте його та поверніть це значення.
grantwparks

12
Я серйозно не намагаюся уникати функціональних викликів. Функції є основою структурованого програмування.
grantwparks

11

Я використовую комбінацію відповідей Теєрда Віссера та 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() ]]
    }
}

Але ще кращим рішенням є усунення статичних методів та використання шаблону Сінглтона. Тоді ви просто зробите складну ініціалізацію в конструкторі. Або зробіть це "послугою" і використовуйте DI, щоб ввести його в будь-який клас, який йому потрібен.


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 (), який виконується раніше, ніж будь-який інший код, тоді ви зможете заповнити змінну.


метод 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 (); з будь-якого місця.


-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
Але зауважте, що ви створили екземпляр класу (об’єкта), тому що конструктор є загальнодоступною функцією NONSTATIC. Питання: чи підтримує PHP лише статичні конструктори (немає створення екземплярів. Наприклад, як у Javastatic { /* some code accessing static members*/ }
Mitja Gustin
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.