113

I have an helper class with some static functions. All the functions in the class require a ‘heavy’ initialization function to run once (as if it were a constructor).

Is there a good practice for achieving this?

The only thing I thought of was calling an init function, and breaking its flow if it has already run once (using a static $initialized var). The problem is that I need to call it on every one of the class’s functions.

TRiG
  • 10,148
  • 7
  • 57
  • 107
user258626
  • 1,185
  • 2
  • 8
  • 9
  • 5
    Under discussion is the [Static Class Constructor](https://wiki.php.net/rfc/static_class_constructor) RFC, which would offer an alternative approach. – bishop Feb 26 '16 at 19:41
  • 3
    Future readers: [Here are code details and a discussion of the approach user258626 said he was thinking of doing](https://stackoverflow.com/a/55658771/199364). Please compare it to the accepted answer. Decide which you'd rather have. Or do one of the *other* answers; I am suggesting you not blindly adopt the accepted answer. Key point: **As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.** – ToolmakerSteve Apr 12 '19 at 20:12
  • I wish we could refactor SO, put the accepted answer into a new question “What does the Singleton pattern look like in PHP?” (for which it’s an excellent answer) and make user258626’s answer (or something like it) the accepted answer to this question. – Adam Chalcraft Jul 22 '21 at 07:29

9 Answers9

134

Sounds like you'd be better served by a singleton rather than a bunch of static methods

class Singleton
{
  /**
   * 
   * @var Singleton
   */
  private static $instance;

  private function __construct()
  {
    // Your "heavy" initialization stuff here
  }

  public static function getInstance()
  {
    if ( is_null( self::$instance ) )
    {
      self::$instance = new self();
    }
    return self::$instance;
  }

  public function someMethod1()
  {
    // whatever
  }

  public function someMethod2()
  {
    // whatever
  }
}

And then, in usage

// As opposed to this
Singleton::someMethod1();

// You'd do this
Singleton::getInstance()->someMethod1();
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • Thank you very much. I probably be doing this way – user258626 Jul 22 '10 at 20:17
  • 5
    I want to -1 (but I won't) for private constructor and `getInstance()`... You're going to make it VERY hard to test effectively... At least make it protected so that you have options... – ircmaxell Jul 22 '10 at 20:31
  • 19
    @ircmaxell - you're just talking about issues with the singleton pattern itself, really. And code posted by anybody on SO shouldn't be considered authoritative - especially simple examples that are only meant to be illustrative. Everyone's scenarios and situations are different – Peter Bailey Jul 22 '10 at 20:47
  • 1
    Quite true. I won't argue that (and that's why I didn't downvote, the content is good). I just hate seeing private methods/members (it's a pet peeve)... – ircmaxell Jul 22 '10 at 20:47
  • what is the difference between this Singleton and a solution which implements the construct in every method? i.e. Singleton::getInstance()->method() vs. Singleton::method() //function method(){ $s=new self(); ....} – ulkas Sep 18 '12 at 10:02
  • ou, i see, this way the class is instantiated once and keeps itself in the $instance param – ulkas Sep 18 '12 at 10:04
  • 1
    This solution adds 20ish lines of glue code. It is appropriate to compiled technologies such as Java and C#. But PHP is an interpreted language, and it's much simpler to simply call the initialization function in the same file but outside the class definition. – Liz Av Nov 15 '12 at 17:50
  • 19
    20 whole lines??!?!? Jeez, doesn't the author of this answer know that lines of code are a precious resource?!? They don't grow on trees ya know! – Peter Bailey Nov 15 '12 at 17:53
  • 12
    @PeterBailey Lines of code that don't accomplish anything but glue are a distraction and makes code less maintainable. – Liz Av Nov 19 '12 at 03:07
  • 17
    @ekevoo I'm not the author of the Singleton Pattern, you know. Don't kill the messenger. – Peter Bailey Nov 19 '12 at 17:01
  • 1
    @PeterBailey Oh, I love the Singleton Pattern. It's a great pattern for compiled languages. My problem is that PHP isn't one. – Liz Av Nov 22 '12 at 17:26
  • 1
    I just slip the call to getInstance inside each static method for convenience. – grantwparks Nov 24 '12 at 20:44
  • @PeterBailey, shouldn't that be `static::$instance` instead of `self::$instance`? – Pacerier Jul 27 '13 at 12:47
  • 2
    Beware that this has the classic Singleton race condition. You would need a mutex on the getInstance function for this to be safe in context where multiple threads are utilizing this class. – rshepherd Jul 20 '15 at 20:32
  • Warning In PHP 7, calling non-static methods statically is deprecated, and will generate an E_DEPRECATED warning. Support for calling non-static methods statically may be removed in the future. [link](http://php.net/manual/en/language.oop5.static.php) – Malus Jan Oct 27 '17 at 14:30
  • 2
    Consider `new static()` instead of `new self()` (that way you can override the class) – Jonathan Oct 25 '18 at 01:58
  • @grantwparks re *"I just slip the call to getInstance inside each static method for convenience"*. By doing so, you've eliminated the only possible benefit this approach had, compared to [the weakest] alternatives (including what OP said he was considering doing). If you're going to add a line to every method, then this implementation is extra coding for no benefit - just do plain old static methods, that include an init call. Or see Victor Nicollet's answer. – ToolmakerSteve Apr 12 '19 at 20:33
  • @MalusJan - Nowhere in this answer's code, is there a call of a non-static method from a static method. Look more carefully at usage. The static `getInstance` *returns an instance*. It is on *that instance*, that the instance methods are called. [See `self::$instance = new self();` for the instance.] OTOH, IIRC, it *is* the case that `self::$instance` now generates a warning, and should be replaced with `Singleton::$instance`. But that's a different issue. – ToolmakerSteve Apr 12 '19 at 20:42
  • @toolmakersteve getInstance does init. It's the same thing. And then not every user of a method has to call the init. It's opaque and controlled by the class. What is "the only benefit this approach had"? Far preferable to have more code in the class so that the method use is cleaner. IMO having to init the thing outside is less appealing. – grantwparks Apr 13 '19 at 23:40
  • While singletons have their own very special usage case, the main reason we need static constructors is that we need a way to initialize some static properties before starting to create object instances and I might need a number of them. Meanwhile singletons reduce everything to only one instance which might not be what we want. – Karolis Jan 03 '20 at 01:11
  • @grantwparks - I totally agree that code should be in the class methods, not in calling code. My comment wasn't in favor of the answer here (which I don't like), rather it was the way you suggested fixing it. I'm saying that creating a singleton instance doesn't add any benefit. It isn't necessary to *either* do `getInstance` in the calling code *or* to do `getInstance` (or `init` or any other copy/paste clutter) inside *each method*. Instead use static funcs. ***In one place in the class***, executed when the class itself is created, call an `init` function. See Victor Nicollet's answer. – ToolmakerSteve Feb 16 '20 at 04:06
  • @Karolis has it right...you might need a static initializer and _still_ need regular constructor processing per instance. You can't do that with a Singleton. – scott8035 Mar 03 '22 at 17:04
103
// file Foo.php
class Foo
{
  static function init() { /* ... */ }
}

Foo::init();

This way, the initialization happens when the class file is included. You can make sure this only happens when necessary (and only once) by using autoloading.

Victor Nicollet
  • 24,361
  • 4
  • 58
  • 89
  • Thank you , thats a good solution. but my framework includes all helpers. is there a way to make it inside included file ? – user258626 Jul 22 '10 at 20:10
  • 1
    @VictorNicollet, this is ugly. Your code makes `init` a public method, and it wouldn't work if it's private. Isn't there a cleaner way like the java static class initializer? – Pacerier Aug 07 '13 at 09:28
  • 3
    @Pacerier if init() does nothing the second time it is invoked, it really doesn't matter if it is public... `static function init() { if(self::$inited) return; /* ... */ }` – FrancescoMM Jul 27 '15 at 14:10
  • 1
    @Pacerier the end result of any constructor or initializer that accepts arguments is ingesting out-of-scope data into the class. you've got to handle it somewhere. – That Realty Programmer Guy Aug 27 '15 at 12:50
  • 1
    This is what I've been doing for years, but I recently found out that the `Foo::init();` line somehow doesn't get picked up for code coverage using `phpunit.phar --coverage-html` – Jeff Mar 20 '19 at 15:30
  • 2
    This is incompatible with `opcache.preload` of PHP 7.4. If the file is preloaded in the preload script, the class will "exist", but not the effects of top-level code in that file - and autoload will NOT require the file because the class exists, and you won't require it either because it would cause the class to be redefined! – Szczepan Hołyszewski May 03 '20 at 16:16
58

Actually, I use a public static method __init__() on my static classes that require initialization (or at least need to execute some code). Then, in my autoloader, when it loads a class it checks is_callable($class, '__init__'). If it is, it calls that method. Quick, simple and effective...

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 2
    That would be my suggestion too. I did the same in the past but called it `__initStatic()`. It feels like a thing PHP needs, knowing Java. – Alex P Oct 10 '13 at 20:15
  • 3
    For those of us using composer: I found this: https://packagist.org/packages/vladimmi/construct-static – iautomation Sep 30 '15 at 04:21
  • @iautomation Didn't tried it but this is worth to be placed in an own answer! It is a straightforward and modern approach. – robsch Jan 29 '16 at 08:27
  • For those working in a professional production environment where composer is bile of the internet... this answer works very well. – IncredibleHat Feb 04 '20 at 19:58
  • i got an error `if(is_callable($class_name, "__init")) { $class_name::__init(); }` is this method not useable if i use `spl_autoload_register()`? – Irvan Hilmi Jan 08 '23 at 12:03
  • Would this not still get called multiple times if, for example, it's on class `A`, not on class `B`, but `B` extends `A`? It would call it once when loading `A` and again when loading `B` (because it exists on `B` via inheritance). So basically, anytime you load a class that extends `A` (and that class doesn't have its own `__init__()` method), `A`'s `__init__()` method just gets called again. – Leon Williams May 16 '23 at 20:53
10

NOTE: This is exactly what OP said they did. (But didn't show code for.) I show the details here, so that you can compare it to the accepted answer. My point is that OP's original instinct was, IMHO, better than the answer he accepted.


Given how highly upvoted the accepted answer is, I'd like to point out the "naive" answer to one-time initialization of static methods, is hardly more code than that implementation of Singleton -- and has an essential advantage.

final class MyClass  {
    public static function someMethod1() {
        MyClass::init();
        // whatever
    }

    public static function someMethod2() {
        MyClass::init();
        // whatever
    }


    private static $didInit = false;

    private static function init() {
        if (!self::$didInit) {
            self::$didInit = true;
            // one-time init code.
        }
    }

    // private, so can't create an instance.
    private function __construct() {
        // Nothing to do - there are no instances.
    }
}

The advantage of this approach, is that you get to call with the straightforward static function syntax:

MyClass::someMethod1();

Contrast it to the calls required by the accepted answer:

MyClass::getInstance->someMethod1();

As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.


If you are NOT using PHP 7.4's opcode.cache, then use Victor Nicollet's answer. Simple. No extra coding required. No "advanced" coding to understand. (I recommend including FrancescoMM's comment, to make sure "init" will never execute twice.) See Szczepan's explanation of why Victor's technique won't work with opcode.cache.

If you ARE using opcode.cache, then AFAIK my answer is as clean as you can get. The cost is simply adding the line MyClass::init(); at start of every public method. NOTE: If you want public properties, code them as a get / set pair of methods, so that you have a place to add that init call.

(Private members do NOT need that init call, as they are not reachable from the outside - so some public method has already been called, by the time execution reaches the private member.)

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
  • 1
    I prefer this because the class is self-contained. I only put the test in methods that might be the first ones called. Note that the test property can be one that needs to be initialised using code, by making it false, then a method can call the `init()` when it is false, else use the property's value. – Patanjali Nov 06 '19 at 04:15
  • 1
    @Patanjali - I recommend using a dedicated property `static $didInit`, rather than depending on some property specific to the class. This makes the code more obvious (and consistent if you use the same technique in multiple classes). The extra memory cost is negligible, as it is a single static property per class (if you do create instances of the class, it doesn't make the instances larger, it is on the class itself). – ToolmakerSteve Feb 16 '20 at 04:32
  • 1
    (@)ToolmakerSteve. I agree if you are wanting a generic mechanism, as it is better to be consistent. However, if not all properties can be initialised at the same time, because the class values are built up through other processes, then doing the relevant properties as and when required is OK. – Patanjali Feb 16 '20 at 17:54
  • 1
    This approach will make things hard to maintain. Consider: if you use the init method to set some property… 1) you're going to have to keep checking if it did init in every single method (WET); 2) if you want to access a property, you'll have to keep track of whether you already called a method that initialized the property. Seems to me that using an instance of the class and the benefits of a constructor is much better (in other words, that the OP is best answered in the negative, "don't do it.") – Fabien Snauwaert Mar 02 '20 at 12:38
  • @FabienSnauwaert - I mostly agree with you (and as I said in answer, this is not IMHO the **best** approach). Some comments: 1) if you need *public* properties (not just methods), then you are definitely beyond the scope where this approach is appropriate. 2) It’s only slightly WET; encapsulating logic in a method that is called several places and avoids multiple-execution with a flag is a classic programming technique long before OO: truly WET code repeats more than a single function call. 3) Like most answers here, this is a work-around because PHP lacks static initializer. – ToolmakerSteve Aug 29 '20 at 16:34
  • 1
    This approach makes `MyClass` slightly wetter, but it makes every caller of `MyClass` drier, because they don’t need to say `MyClass::getInstance->someMethod1();`. When maintaining `MyClass`, the code is right there in front of you so if you have to be wet somewhere, it should be here. – Adam Chalcraft Jul 22 '21 at 07:21
8

There is a way to call the init() method once and forbid it's usage, you can turn the function into private initializer and ivoke it after class declaration like this:

class Example {
    private static function init() {
        // do whatever needed for class initialization
    }
}
(static function () {
    static::init();
})->bindTo(null, Example::class)();
brzuchal
  • 649
  • 8
  • 19
  • 4
    this looks strangely interesting – emfi Jan 29 '19 at 15:30
  • How is it able to call `private init` from outside the class? Can you explain the details of what you are doing here? – ToolmakerSteve Apr 12 '19 at 16:45
  • 1
    @ToolmakerSteve As the [docs](https://www.php.net/Closure_bindTo) says _"Static closures cannot have any bound object (the value of the parameter newthis should be NULL), but this function can nevertheless be used to change their class scope."_ that's why the closure scope is bound to the `Example::class` so it's possible to call a private method. I have an bug cause the `init()` method should be `static` - fixed the example. – brzuchal Apr 15 '19 at 06:46
  • Edited the example and added `static` modifier for closure cause it won't be bound to any instance. – brzuchal Apr 15 '19 at 06:53
  • Get an error "Parse error: syntax error, unexpected '->' (T_OBJECT_OPERATOR)" – ivan-ivory Oct 10 '19 at 18:20
  • @ivan-ivory from 7.1 up to newest 7.4 this works, see https://3v4l.org/rpkos – brzuchal Oct 11 '19 at 06:32
  • 2
    Well, in fact, we don't even need `init()` method, i.e. we can put all initialization code directly into this anonymous function which can act as a static constructor itself. – Karolis Jan 03 '20 at 00:20
  • 2
    CAVEAT: See [Szczepan's answer](https://stackoverflow.com/a/61577684/199364), which explains that techniques like this won't work if you use PHP 7.4's `opcache.preload` mechanism. – ToolmakerSteve Jan 18 '21 at 23:18
3

I am posting this as an answer because this is very important as of PHP 7.4.

The opcache.preload mechanism of PHP 7.4 makes it possible to preload opcodes for classes. If you use it to preload a file that contains a class definition and some side effects, then classes defined in that file will "exist" for all subsequent scripts executed by this FPM server and its workers, but the side effects will not be in effect, and the autoloader will not require the file containing them because the class already "exists". This completely defeats any and all static initialization techniques that rely on executing top-level code in the file that contains the class definition.

Szczepan Hołyszewski
  • 2,707
  • 2
  • 25
  • 39
0

If you don't like public static initializer, reflection can be a workaround.

<?php

class LanguageUtility
{
    public static function initializeClass($class)
    {
        try
        {
            // Get a static method named 'initialize'. If not found,
            // ReflectionMethod() will throw a ReflectionException.
            $ref = new \ReflectionMethod($class, 'initialize');

            // The 'initialize' method is probably 'private'.
            // Make it accessible before calling 'invoke'.
            // Note that 'setAccessible' is not available
            // before PHP version 5.3.2.
            $ref->setAccessible(true);

            // Execute the 'initialize' method.
            $ref->invoke(null);
        }   
        catch (Exception $e)
        {
        }
    }
}

class MyClass
{
    private static function initialize()
    {
    }
}

LanguageUtility::initializeClass('MyClass');

?>
Takahiko Kawasaki
  • 18,118
  • 9
  • 62
  • 105
0

Some tests of assigning static public properties :

settings.json :

{
    "HOST": "website.com",
    "NB_FOR_PAGINA": 8,
    "DEF_ARR_SIZES": {
        "min": 600,
        "max": 1200
    },
    "TOKEN_TIME": 3600,
    "WEBSITE_TITLE": "My website title"
}

now we want to add settings public static properties to our class

class test {
  
  /**  prepare an array to store datas  */
  public static $datas = array();
  
 /**
  * test::init();
  */
  public static function init(){
    
    // get json file to init.
    $get_json_settings = 
      file_get_contents(dirname(__DIR__).'/API/settings.json');

    $SETTINGS = json_decode($get_json_settings, true);
                
    foreach( $SETTINGS as $key => $value ){
         
       // set public static properties
       self::$datas[$key] = $value;         
    }

  }
 /**
  * 
  */


 /**
  * test::get_static_properties($class_name);
  *
  * @param  {type} $class_name
  * @return {log}  return all static properties of API object
  */
  public static function get_static_properties($class_name) {

    $class = new ReflectionClass($class_name);

    echo '<b>infos Class : '.$class->name.'</b><br>';

    $staticMembers = $class->getStaticProperties();

    foreach( $staticMembers as $key => $value ){

        echo '<pre>';
        echo $key. ' -> ';

        if( is_array($value) ){
            var_export($value);
        }
        else if( is_bool($value) ){

            var_export($value);
        }
        else{

            echo $value;
        }

        echo '</pre>';

    }
    // end foreach

  }
 /**
  * END test::get_static_properties();
  */

}
// end class test

ok now we test this code :

// consider we have the class test in API folder
spl_autoload_register(function ($class){
    
    // call path to API folder after
    $path_API = dirname(__DIR__).'/API/' . $class . '.php';
    
    if( file_exists($path_API) ) require $path_API;
});
// end SPL auto registrer

// init class test with dynamics static properties 
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
var_dump(test::$datas['HOST']);

this return :

infos Class : test

datas -> array (
  'HOST' => 'website.com',
  'NB_FOR_PAGINA' => 8,
  'DEF_ARR_SIZES' => 
  array (
    'min' => 600,
    'max' => 1200,
  ),
  'TOKEN_TIME' => 3600,
  'WEBSITE_TITLE' => 'My website title'
)

// var_dump(test::$HOST);
Uncaught Error: Access to undeclared static property: 
test::$HOST
// var_dump(test::$datas['HOST']);
website.com

Then if we modify the class test like this :

    class test {
      
      /**  Determine empty public static properties  */
      public static $HOST;
      public static $NB_FOR_PAGINA;
      public static $DEF_ARR_SIZES;
      public static $TOKEN_TIME;
      public static $WEBSITE_TITLE;
      
     /**
      * test::init();
      */
      public static function init(){
        
        // get json file to init.
        $get_json_settings = 
          file_get_contents(dirname(__DIR__).'/API/settings.json');
    
        $SETTINGS = json_decode($get_json_settings, true);
                    
        foreach( $SETTINGS as $key => $value ){
             
           // set public static properties 
           self::${$key} = $value;                  
        }
    
      }
     /**
      * 
      */
...
}
// end class test 

// init class test with dynamics static properties 
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);

this return :

infos Class : test
    
  HOST -> website.com
  NB_FOR_PAGINA -> 8
  DEF_ARR_SIZES -> array (
  'min' => 600,
  'max' => 1200,
)
TOKEN_TIME -> 3600
WEBSITE_TITLE -> My website title

// var_dump(test::$HOST);
website.com

I actually need to initialize an object with public static properties that I will reuse in many other classes, which I think is supposed to, I don't want to do new api() in every method where I would need, for example to check the host of the site or indicate it. Also I would like to make things more dynamic so that I can add as many settings as I want to my API, without having to declare them in my initialization class. All other methods I've seen no longer work under php > 7.4 I keep looking for a solution for this problem.

-2

Note - the RFC proposing this is still in the draft state.


class Singleton
{
    private static function __static()
    {
        //...
    }
    //...
}

proposed for PHP 7.x (see https://wiki.php.net/rfc/static_class_constructor )

Cristik
  • 30,989
  • 25
  • 91
  • 127
abcd
  • 77
  • 6
    That RFC isn't out of Draft stage. Please don't link or give examples to things that haven't been approved by a vote. It will confuse new users who don't realize this isn't usable yet – Machavity Jun 14 '16 at 19:17