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


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.