So initialisieren Sie statische Variablen

207

Ich habe diesen Code:

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

Was mir folgenden Fehler gibt:

Analysefehler: Syntaxfehler, unerwartetes '(', Erwarten ')' in /home/user/Sites/site/registration/inc/registration.class.inc in Zeile 19

Also, ich denke ich mache etwas falsch ... aber wie kann ich das machen, wenn nicht so? Wenn ich das Mktime-Zeug mit regulären Strings ändere, funktioniert es. Also ich weiß , dass ich es tun kann irgendwie ähnlich ..

Hat jemand ein paar Hinweise?

Svish
quelle
2
Die erste Antwort ist überbewertet. Siehe stackoverflow.com/a/4470002/632951
Pacerier
1
@ Pacerier Ich glaube nicht. Antwort Nr. 2 hat viel Aufwand im Vergleich zu Antwort Nr. 1
Kontrollfreak
2
@ Pacerier 10 Leute fragen, keiner von ihnen würde das vorziehen.
Buffalo

Antworten:

345

PHP kann nicht triviale Ausdrücke in Initialisierern nicht analysieren.

Ich ziehe es vor, dies zu umgehen, indem ich Code direkt nach der Definition der Klasse hinzufüge:

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

oder

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

PHP 5.6 kann jetzt einige Ausdrücke verarbeiten.

/* 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
quelle
135
Ich liebe PHP, aber manchmal ist es wirklich seltsam.
Marco Demaio
6
Ich weiß, dass dies alt ist, aber ich benutze auch diese Methode. Ich habe jedoch festgestellt, dass manchmal Foo :: init () nicht aufgerufen wird. Ich konnte nie herausfinden warum, sondern wollte nur alle darauf aufmerksam machen.
lucifurious
1
@porneL, die erste Methode würde nicht funktionieren, da Sie keinen Zugriff auf private Variablen haben. Die zweite Methode funktioniert, aber sie zwingt uns, initöffentlich zu machen, was hässlich ist. Was ist eine bessere Lösung?
Pacerier
2
@Pacerier Die erste Methode verwendet aus einem bestimmten Grund öffentliches Eigentum. AFAIK gibt es derzeit keine bessere Lösung in PHP (Tjeerd Vissers Antwort ist jedoch nicht schlecht). Das Ausblenden des Hacks im Klassenladeprogramm lässt ihn nicht verschwinden, erzwingt eine falsche Vererbung und es ist ein bisschen Klugheit, die unerwartet brechen kann (z. B. wenn Datei explizit erforderlich ist () d).
Kornel
1
@porneL Einfaches Array funktioniert für mich in PHP 5.6.x, obwohl es in RFC nicht erwähnt wird. Beispiel:class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);
Pang
32

Wenn Sie die Kontrolle über das Laden von Klassen haben, können Sie von dort aus eine statische Initialisierung durchführen.

Beispiel:

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

Gehen Sie in Ihrem Klassenlader wie folgt vor:

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

Eine schwerere Lösung wäre die Verwendung einer Schnittstelle mit ReflectionClass:

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

Gehen Sie in Ihrem Klassenlader wie folgt vor:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Emanuel Landeholm
quelle
Dies ist mehr als etwas ähnlich wie statische Konstruktoren in c #. Ich verwende seit Ewigkeiten etwas ziemlich Ähnliches und es funktioniert großartig.
Kris
@EmanuelLandeholm, ist also Methode eins schneller oder Methode zwei schneller?
Pacerier
@Kris Das ist kein Zufall. Ich war zum Zeitpunkt der Beantwortung von c # inspiriert.
Emanuel Landeholm
1
@Pacerier Ich habe keinen Beweis, aber ich vermute, dass ReflectionClass () mehr Overhead verursachen kann. OTOH, die erste Methode, geht etwas gefährlich davon aus, dass jede Methode mit dem Namen "static_init" in einer vom Klassenladeprogramm geladenen Klasse ein statischer Initialisierer ist. Dies könnte zu schwer auffindbaren Fehlern führen, z. mit Klassen von Drittanbietern.
Emanuel Landeholm
23

Anstatt einen Weg zu finden, um statische Variablen zum Laufen zu bringen, ziehe ich es vor, einfach eine Getter-Funktion zu erstellen. Auch hilfreich, wenn Sie Arrays benötigen, die zu einer bestimmten Klasse gehören und viel einfacher zu implementieren sind.

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

Wo immer Sie die Liste benötigen, rufen Sie einfach die Getter-Methode auf. Beispielsweise:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
Diggie
quelle
11
Obwohl dies eine elegante Lösung ist, würde ich nicht sagen, dass sie aus Leistungsgründen ideal ist, vor allem aufgrund der Häufigkeit, mit der das Array möglicherweise initialisiert werden könnte - dh aufgrund vieler Heap-Zuweisungen. Da PHP in C geschrieben ist, würde ich mir vorstellen, dass die Übersetzung in eine Funktion aufgelöst wird, die pro Aufruf einen Zeiger auf ein Array zurückgibt ... Nur meine zwei Cent.
Zeboidlund
Darüber hinaus sind Funktionsaufrufe in PHP teuer, daher ist es am besten, sie zu vermeiden, wenn sie nicht erforderlich sind.
Mark Rose
14
"am besten vermeiden, wenn es nicht nötig ist" - nicht wirklich. Vermeiden Sie sie, wenn sie (möglicherweise) zu Engpässen werden. Ansonsten ist es vorzeitige Optimierung.
Psycho Brm
2
@blissfreak - Man kann eine Neuzuweisung vermeiden, wenn wir eine statische Eigenschaft in der Klasse erstellen und getTypeList () einchecken, wenn sie bereits initialisiert wurde, und diese zurückgeben. Wenn noch nicht initialisiert, initialisieren Sie es und geben Sie diesen Wert zurück.
Grantwparks
12
Ich versuche ernsthaft nicht, Funktionsaufrufe zu vermeiden. Funktionen sind die Basis für strukturierte Programmierung.
Grantwparks
11

Ich benutze eine Kombination aus der Antwort von Tjeerd Visser und 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() ]]
    }
}

Eine noch bessere Lösung besteht darin, die statischen Methoden abzuschaffen und das Singleton-Muster zu verwenden. Dann führen Sie einfach die komplizierte Initialisierung im Konstruktor durch. Oder machen Sie es zu einem "Service" und verwenden Sie DI, um es in eine Klasse zu injizieren, die es benötigt.

Mambazo
quelle
10

Das ist zu komplex, um es in der Definition festzulegen. Sie können die Definition jedoch auf null setzen und sie dann im Konstruktor überprüfen. Wenn sie nicht geändert wurde, setzen Sie sie:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Alister Bulman
quelle
10
Aber wird der Konstruktor in einer abstrakten Klasse hilfreich sein, die niemals instanziiert wird?
Svish
Eine abstrakte Klasse kann nur dann sinnvoll verwendet werden, wenn sie abgeschlossen und instanziiert wurde. Das obige Setup muss nicht speziell in einem Konstruktor durchgeführt werden, solange es irgendwo aufgerufen wird, bevor die Variable verwendet wird.
Alister Bulman
Es ist keine gute Idee anzunehmen, dass der Konstruktor aufgerufen wird, bevor eine statische Variable benötigt wird. Oft benötigt man einen statischen Wert, bevor man eine Instanz erstellt.
ToolmakerSteve
4

In diesem Teil des Codes können keine Funktionsaufrufe durchgeführt werden. Wenn Sie eine Methode vom Typ init () erstellen, die vor jedem anderen Code ausgeführt wird, können Sie die Variable dann füllen.

alxp
quelle
Methode vom Typ init ()? Könnten Sie ein Beispiel geben? ist das wie ein statischer Konstruktor in C #?
Svish
@Svish: Nein. Es sollte als reguläre statische Methode direkt unterhalb der Klassendefinition aufgerufen werden.
Vladislav Rastrusny
4

In PHP 7.0.1 konnte ich Folgendes definieren:

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

Und dann benutze es so:

MyClass::$kIdsByActions[$this->mAction];
Büffel
quelle
FWIW: Was Sie zeigen, erfordert kein PHP 7; Es funktionierte gut, als die Frage gestellt wurde: "Wenn ich das Mktime-Zeug mit regulären Zeichenfolgen ändere, funktioniert es." Was dieser Thread sucht, sind Techniken zum Initialisieren einer Statik, wenn die Initialisierung eine oder mehrere Funktionen aufrufen muss .
ToolmakerSteve
3

Der beste Weg ist, einen Accessor wie folgt zu erstellen:

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

dann kannst du static :: db () machen; oder self :: db (); von überall.

espaciomore
quelle
-1

Hier ist ein hoffentlich hilfreicher Zeiger in einem Codebeispiel. Beachten Sie, dass die Initialisierungsfunktion nur einmal aufgerufen wird.

Auch, wenn Sie invertieren die Anrufe an StaticClass::initializeStStateArr()und $st = new StaticClass()Sie erhalten das gleiche Ergebnis.

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

Welche Ausbeuten:

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
David Luhman
quelle
2
Beachten Sie jedoch, dass Sie eine Instanz der Klasse (Objekt) erstellt haben, da der Konstruktor eine öffentliche NONSTATIC-Funktion ist. Die Frage ist: Unterstützt PHP nur statische Konstruktoren (keine Instanzerstellung. Zum Beispiel wie in Javastatic { /* some code accessing static members*/ }
Mitja Gustin