Comment initialiser des variables statiques

207

J'ai ce 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
);

Ce qui me donne l'erreur suivante:

Erreur d'analyse: erreur de syntaxe, inattendu '(', expecting ')' dans /home/user/Sites/site/registration/inc/registration.class.inc sur la ligne 19

Donc, je suppose que je fais quelque chose de mal ... mais comment puis-je faire ceci si ce n'est pas comme ça? Si je change le truc mktime avec des cordes régulières, ça marche. Donc je sais que je peux le faire un peu comme ça ..

Quelqu'un a des conseils?

Svish
la source
2
La première réponse est sur votée. Voir stackoverflow.com/a/4470002/632951
Pacerier
1
@Pacerier Je ne pense pas. La réponse n ° 2 a beaucoup de frais généraux par rapport à la réponse n ° 1
Kontrollfreak
2
@Pacerier demande à 10 personnes, aucune d'entre elles ne préférerait cela.
Buffalo

Réponses:

345

PHP ne peut pas analyser les expressions non triviales dans les initialiseurs.

Je préfère contourner cela en ajoutant du code juste après la définition de la classe:

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

ou

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

PHP 5.6 peut désormais gérer certaines expressions.

/* 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
la source
135
J'adore PHP, mais c'est parfois vraiment étrange.
Marco Demaio
6
Je sais que c'est vieux, mais moi aussi j'utilise cette méthode. Cependant, j'ai trouvé que parfois Foo :: init () n'est pas appelé. Je n'ai jamais pu découvrir pourquoi, mais je voulais juste que tout le monde soit au courant.
lucifurious
1
@porneL, la première méthode ne fonctionnerait pas car vous n'avez pas accès aux variables privées. La seconde méthode fonctionne mais elle nous oblige à rendre initpublic ce qui est moche. Quelle meilleure solution?
Pacerier
2
@Pacerier la première méthode utilise la propriété publique pour une raison. AFAIK il n'y a pas de meilleure solution en PHP pour le moment (la réponse de Tjeerd Visser n'est pas mauvaise cependant). Cacher le hack dans le chargeur de classe ne le fait pas disparaître, force un faux héritage, et c'est un peu d'intelligence qui pourrait se casser de manière inattendue (par exemple lorsque le fichier est explicitement requis () d).
Kornel
1
@porneL Simple array fonctionne pour moi en PHP 5.6.x, bien qu'il ne soit pas mentionné dans RFC. Exemple:class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);
Pang
32

Si vous contrôlez le chargement des classes, vous pouvez effectuer une initialisation statique à partir de là.

Exemple:

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

dans votre chargeur de classe, procédez comme suit:

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

Une solution plus lourde serait d'utiliser une interface avec ReflectionClass:

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

dans votre chargeur de classe, procédez comme suit:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Emanuel Landeholm
la source
C'est plus que quelque peu similaire aux constructeurs statiques en c #, j'utilise quelque chose de assez similaire depuis des lustres et cela fonctionne très bien.
Kris
@EmanuelLandeholm, la méthode est-elle donc plus rapide ou la méthode deux plus rapide?
Pacerier
@Kris Ce n'est pas un hasard. J'ai été inspiré par c # au moment de répondre.
Emanuel Landeholm
1
@Pacerier Je n'ai aucune preuve mais je soupçonne que ReflectionClass () peut entraîner plus de frais généraux. OTOH, la première méthode fait l'hypothèse quelque peu dangereuse que toute méthode appelée "static_init" dans n'importe quelle classe chargée par le chargeur de classe est un initialiseur statique. Cela pourrait entraîner des bogues difficiles à détecter, par exemple. avec des cours tiers.
Emanuel Landeholm
23

Au lieu de trouver un moyen de faire fonctionner les variables statiques, je préfère simplement créer une fonction getter. Aussi utile si vous avez besoin de tableaux appartenant à une classe spécifique et beaucoup plus simples à implémenter.

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

Partout où vous avez besoin de la liste, appelez simplement la méthode getter. Par exemple:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
diggie
la source
11
Bien qu'il s'agisse d'une solution élégante, je ne dirais pas qu'elle est idéale pour des raisons de performances, principalement en raison du nombre de fois où le tableau pourrait être initialisé - c'est-à-dire, beaucoup d'allocation de tas. Puisque php est écrit en C, j'imagine que la traduction se résoudrait en une fonction renvoyant un pointeur vers un tableau par appel ... Juste mes deux cents.
zeboidlund
De plus, les appels de fonction sont chers en PHP, il est donc préférable de les éviter s'ils ne sont pas nécessaires.
Mark Rose
14
"mieux les éviter quand ce n'est pas nécessaire" - pas vraiment. Évitez-les s'ils deviennent (pourraient) devenir des goulots d'étranglement. Sinon, c'est une optimisation prématurée.
psycho brm
2
@blissfreak - on peut éviter de réallouer, SI nous créons une propriété statique dans la classe, et vérifions getTypeList () si elle a déjà été initialisée et la retournons. S'il n'est pas encore initialisé, initialisez-le et renvoyez cette valeur.
grantwparks
12
Sérieusement, je n'essaie pas d'éviter les appels de fonction. Les fonctions sont à la base d'une programmation structurée.
grantwparks
11

J'utilise une combinaison de la réponse de Tjeerd Visser et de 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() ]]
    }
}

Mais une solution encore meilleure consiste à supprimer les méthodes statiques et à utiliser le modèle Singleton. Ensuite, vous venez de faire l'initialisation compliquée dans le constructeur. Ou faites-en un "service" et utilisez DI pour l'injecter dans n'importe quelle classe qui en a besoin.

Mambazo
la source
10

C'est trop complexe pour être défini dans la définition. Vous pouvez cependant définir la définition sur null, puis dans le constructeur, vérifiez-la, et si elle n'a pas été modifiée - définissez-la:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Alister Bulman
la source
10
mais le constructeur sera-t-il utile dans une classe abstraite qui n'est jamais instanciée?
Svish
Une classe abstraite ne peut être utilement utilisée que si elle est terminée et instanciée. La configuration ci-dessus ne doit pas être effectuée spécifiquement dans un constructeur, tant qu'elle est appelée quelque part avant que la variable ne soit utilisée.
Alister Bulman
Ce n'est pas une bonne idée de supposer que le constructeur est appelé avant qu'une variable statique soit nécessaire. Souvent, il faut une valeur statique avant de créer une instance.
ToolmakerSteve
4

Vous ne pouvez pas effectuer d'appels de fonction dans cette partie du code. Si vous créez une méthode de type init () qui est exécutée avant tout autre code, vous pourrez alors remplir la variable.

alxp
la source
méthode de type init ()? Pouvez-vous donner un exemple? est-ce un peu comme un constructeur statique en C #?
Svish
@Svish: Non. Elle devrait être appelée juste en dessous de la définition de classe en tant que méthode statique régulière.
Vladislav Rastrusny
4

En PHP 7.0.1, j'ai pu définir ceci:

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

Et puis utilisez-le comme ceci:

MyClass::$kIdsByActions[$this->mAction];
Buffle
la source
FWIW: Ce que vous montrez ne nécessite pas PHP 7; cela a bien fonctionné au moment où la question a été posée: "Si je change le truc mktime avec des cordes régulières, ça marche." Ce que ce thread recherche, ce sont des techniques pour initialiser un statique, lorsque l'initialisation doit appeler une ou plusieurs fonctions .
ToolmakerSteve
3

la meilleure façon est de créer un accesseur comme celui-ci:

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

alors vous pouvez faire static :: db (); ou self :: db (); de partout.

espaciomore
la source
-1

Voici un pointeur, espérons-le, utile, dans un exemple de code. Notez que la fonction d'initialisation n'est appelée qu'une seule fois.

De plus, si vous inversez les appels vers StaticClass::initializeStStateArr()et $st = new StaticClass()vous obtiendrez le même résultat.

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

Ce qui donne:

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
David Luhman
la source
2
Mais veuillez noter que vous avez créé une instance de classe (objet), car le constructeur est une fonction NONSTATIQUE publique. La question est: PHP supporte-t-il uniquement les constructeurs statiques (pas de création d'instance. Par exemple comme dans Javastatic { /* some code accessing static members*/ }
Mitja Gustin