219

I have this 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
);

Which gives me the following error:

Parse error: syntax error, unexpected '(', expecting ')' in /home/user/Sites/site/registration/inc/registration.class.inc on line 19

So, I guess I am doing something wrong... but how can I do this if not like that? If I change the mktime stuff with regular strings, it works. So I know that I can do it sort of like that..

Anyone have some pointers?

Brandon
  • 16,382
  • 12
  • 55
  • 88
Svish
  • 152,914
  • 173
  • 462
  • 620

10 Answers10

357

PHP can't parse non-trivial expressions in initializers.

I prefer to work around this by adding code right after definition of the class:

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

or

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

PHP 5.6 can handle some expressions now.

/* 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();
}
Tunasing
  • 31
  • 4
Kornel
  • 97,764
  • 37
  • 219
  • 309
  • 148
    I love PHP, but it's really odd sometimes. – Marco Demaio Nov 10 '11 at 21:46
  • 6
    I know this is old, but I too use this method. However, I found that sometimes the Foo::init() is not called. I was never able to track down why, but just wanted to make all aware. – lucifurious Jul 15 '12 at 18:53
  • 1
    @porneL, the first method wouldn't work because you have no access to private variables. The second method works but it forces us to make `init` public which is ugly. What's a better solution? – Pacerier Aug 07 '13 at 09:32
  • 2
    @Pacerier the first method uses public property for a reason. AFAIK there is no better solution in PHP at this time (Tjeerd Visser's answer is not bad though). Hiding the hack in class loader doesn't make it go away, forces false inheritance, and it's a bit of cleverness that could unexpectedly break (e.g. when file is require()d explicitly). – Kornel Aug 08 '13 at 02:39
  • But let's say I have class with multiple static methods. I call them manytimes, they call each other. How can i have a varible on class (not instance) level, so that variable will be executed only once per request lifecyle. Let's say i want to use like this: `static myOneTimeInitVar = Singleton::Instance::getSomeShit()`. – Mitja Gustin Aug 20 '13 at 11:34
  • 1
    @porneL Simple array works for me in PHP 5.6.x, although not mentioned in RFC. Example: `class Foo {public static $bar = array(3 * 4, "b" => 7 + 8);} var_dump(Foo::$bar);` – Pang May 08 '15 at 02:29
  • I sometimes also use this method. It can mess with unit tests, though, if anything non-trivial is added to the init method. – Umbrella Apr 25 '16 at 15:57
  • Man, you'd think this is something that would be cooked into the language by default. Annoying to have to do this – Kellen Stuart Jul 14 '16 at 20:11
34

If you have control over class loading, you can do static initializing from there.

Example:

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

in your class loader, do the following:

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

A more heavy weight solution would be to use an interface with ReflectionClass:

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

in your class loader, do the following:

$rc = new ReflectionClass($klass);
if(in_array('StaticInit', $rc->getInterfaceNames())) { $klass::staticInit() }
Emanuel Landeholm
  • 1,396
  • 1
  • 15
  • 21
  • This is more than somewhat similar to static constructors in c#, I've been using something quite similar for ages and it works great. – Kris Apr 06 '13 at 23:20
  • @EmanuelLandeholm, so is method one faster or method two faster? – Pacerier Aug 07 '13 at 09:38
  • @Kris That is no coincidence. I was inspired by c# at the time of answering. – Emanuel Landeholm Aug 07 '13 at 17:15
  • 2
    @Pacerier I have no proof but I suspect that ReflectionClass() may incur more overhead. OTOH, the first method makes the somewhat dangerous assumption that *any* method called "static_init" in *any* class loaded by the class loader is a static initializer. This could lead to some hard to track down bugs, eg. with third party classes. – Emanuel Landeholm Aug 07 '13 at 17:16
23

Instead of finding a way to get static variables working, I prefer to simply create a getter function. Also helpful if you need arrays belonging to a specific class, and a lot simpler to implement.

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

Wherever you need the list, simply call the getter method. For example:

if (array_key_exists($type, MyClass::getTypeList()) {
     // do something important...
}
diggie
  • 455
  • 4
  • 5
  • 11
    While this is an elegant solution, I wouldn't say it's ideal for performance reasons, primarily because of the amount of times the array could potentially be initialized - i.e., lots of heap allocation. Since php is written in C, I'd imagine the translation would resolve to a function returning a pointer to an Array per call... Just my two cents. – zeboidlund Aug 22 '12 at 19:19
  • Furthermore function calls are expensive in PHP, so it's best to avoid them if they're not necessary. – Mark Rose Sep 20 '12 at 21:03
  • 14
    "best to avoid them when not necessary" - not really. Avoid them if they (might) become bottlenecks. Otherwise it's premature optimization. – psycho brm Mar 13 '13 at 15:49
  • 2
    @blissfreak - one can avoid reallcating, IF we create a static property in the class, and check in getTypeList() if it's been initialized already and return that. If not initialized yet, initialize it and return that value. – grantwparks Nov 06 '13 at 22:52
  • 13
    I seriously don't get trying to avoid function calls. Functions are the basis of structured programming. – grantwparks Nov 06 '13 at 22:53
12

I use a combination of Tjeerd Visser's and porneL's answer.

class Something
{
    private static $foo;

    private static getFoo()
    {
        if ($foo === null)
            $foo = [[ complicated initializer ]]
        return $foo;
    }

    public static bar()
    {
        [[ do something with self::getFoo() ]]
    }
}

But an even better solution is to do away with the static methods and use the Singleton pattern. Then you just do the complicated initialization in the constructor. Or make it a "service" and use DI to inject it into any class that needs it.

Mambazo
  • 170
  • 2
  • 9
10

That's too complex to set in the definition. You can set the definition to null though, and then in the constructor, check it, and if it has not been changed - set it:

private static $dates = null;
public function __construct()
{
    if (is_null(self::$dates)) {  // OR if (!is_array(self::$date))
         self::$dates = array( /* .... */);
    }
}
Alister Bulman
  • 34,482
  • 9
  • 71
  • 110
  • 11
    but will the constructor be of any help in an abstract class that never is instantiated? – Svish Mar 28 '09 at 23:22
  • An abstract class can't be usefully used unless it's been completed and instantiated. The setup above doesn't have to be done specifically in a constructor, as long as it's called somewhere before the variable is going to be used. – Alister Bulman Mar 28 '09 at 23:55
  • Its not a great idea to assume that constructor is called before static variable is needed. Often one needs a static value *before* creating an instance. – ToolmakerSteve Apr 22 '19 at 15:53
4

best way is to create an accessor like this:

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

then you can do static::db(); or self::db(); from anywhere.

espaciomore
  • 328
  • 2
  • 10
4

In PHP 7.0.1, I was able to define this:

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

And then use it like this:

MyClass::$kIdsByActions[$this->mAction];
Buffalo
  • 3,861
  • 8
  • 44
  • 69
  • FWIW: What you show doesn't require PHP 7; it worked fine at the time the question was asked: *"If I change the mktime stuff with regular strings, it works."* What this thread is looking for, is techniques for initializing a static, when the initialization needs to call one or more *functions*. – ToolmakerSteve Feb 16 '20 at 05:12
3

You can't make function calls in this part of the code. If you make an init() type method that gets executed before any other code does then you will be able to populate the variable then.

alxp
  • 6,153
  • 1
  • 22
  • 19
0

In my case, I'm using both static and nonstatic class properties, and I might even have main program code referencing the static part of the class before defining the class. Since static portions of classes don't have constructors, just add a manual constructor to initialize any variables requiring nontrivial calculation:

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
David Spector
  • 1,520
  • 15
  • 21
-1

Here is a hopefully helpful pointer, in a code example. Note how the initializer function is only called once.

Also, if you invert the calls to StaticClass::initializeStStateArr() and $st = new StaticClass() you'll get the same result.

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

Which yields :

$ php static.php
Starting...
In initializeStStateArr
Array
(
    [CA] => California
    [CO] => Colorado
)
Brant Olsen
  • 5,628
  • 5
  • 36
  • 53
David Luhman
  • 129
  • 2
  • 4
  • 2
    But please note, that you created a instance of class (object), because constructor is a public NONSTATIC function. Question is: does PHP support static constructors only (no instance creation. For example like in Java `static { /* some code accessing static members*/ }` – Mitja Gustin Aug 20 '13 at 11:42