Static class initializer in PHP
Asked Answered
S

10

115

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.

Sarraceniaceous answered 22/7, 2010 at 19:49 Comment(3)
Under discussion is the Static Class Constructor RFC, which would offer an alternative approach.Cornstarch
Future readers: Here are code details and a discussion of the approach user258626 said he was thinking of doing. 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.Weka
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.Similar
V
135

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();
Voluntarism answered 22/7, 2010 at 19:56 Comment(22)
Thank you very much. I probably be doing this waySarraceniaceous
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...Loan
@Loan - 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 differentVoluntarism
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)...Loan
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(); ....}Ellersick
ou, i see, this way the class is instantiated once and keeps itself in the $instance paramEllersick
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.Medievalist
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!Voluntarism
@PeterBailey Lines of code that don't accomplish anything but glue are a distraction and makes code less maintainable.Medievalist
@ekevoo I'm not the author of the Singleton Pattern, you know. Don't kill the messenger.Voluntarism
@PeterBailey Oh, I love the Singleton Pattern. It's a great pattern for compiled languages. My problem is that PHP isn't one.Medievalist
I just slip the call to getInstance inside each static method for convenience.Galagalactagogue
@PeterBailey, shouldn't that be static::$instance instead of self::$instance?Singsong
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.Lisa
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. linkSike
Consider new static() instead of new self() (that way you can override the class)Stocking
@Galagalactagogue 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.Weka
@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.Weka
@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.Galagalactagogue
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.Simply
@Galagalactagogue - 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.Weka
@Simply 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.Berber
C
106
// 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.

Celandine answered 22/7, 2010 at 19:58 Comment(6)
Thank you , thats a good solution. but my framework includes all helpers. is there a way to make it inside included file ?Sarraceniaceous
@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?Singsong
@Singsong 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; /* ... */ }Lexicographer
@Singsong 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.Formulary
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-htmlConform
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!Abeyta
L
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...

Loan answered 22/7, 2010 at 20:29 Comment(6)
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.Kerbela
For those of us using composer: I found this: packagist.org/packages/vladimmi/construct-staticWestney
@Westney Didn't tried it but this is worth to be placed in an own answer! It is a straightforward and modern approach.Ammadas
For those working in a professional production environment where composer is bile of the internet... this answer works very well.Hautevienne
i got an error if(is_callable($class_name, "__init")) { $class_name::__init(); } is this method not useable if i use spl_autoload_register()?Leoni
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.Submarine
W
12

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

Weka answered 12/4, 2019 at 20:7 Comment(6)
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.Hiltner
@Hiltner - 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).Weka
(@)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.Hiltner
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.")Lim
@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.Weka
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.Similar
R
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)();
Rile answered 10/9, 2018 at 8:19 Comment(8)
this looks strangely interestingTradespeople
How is it able to call private init from outside the class? Can you explain the details of what you are doing here?Weka
@Weka As the docs 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.Rile
Edited the example and added static modifier for closure cause it won't be bound to any instance.Rile
Get an error "Parse error: syntax error, unexpected '->' (T_OBJECT_OPERATOR)"Chloroplast
@Chloroplast from 7.1 up to newest 7.4 this works, see 3v4l.org/rpkosRile
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.Simply
CAVEAT: See Szczepan's answer, which explains that techniques like this won't work if you use PHP 7.4's opcache.preload mechanism.Weka
M
4

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.

Massy answered 3/5, 2020 at 16:11 Comment(0)
D
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');

?>
Dalmatic answered 15/1, 2018 at 20:44 Comment(1)
This is incompatible with PHP 7.4 opcache.preload; see my answer.Abeyta
B
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.

Banger answered 6/4, 2022 at 0:12 Comment(1)
This is outside of the scope of the OPs question or else I'd upvote, but big kudos for the concept involvedUrethra
O
0

This is a variation of both answers by Victor Nicollet and ToolmakerSteve which seeks to solve some of the problems therein as well as eliminate the slightly more complex requirement of calling methods in the singleton pattern described in the accepted answer. Namely this approach is:

  • compatible with opcode.cache and does not require any initializing function call outside of the class itself
  • easier to maintain because it eliminates the cost of adding init() inside every method.
  • flexible because if you have methods that don't require initialization, you can simply make them public
class Foo
{
    private static $is_init = false;
    private static function _init() {
        if (!self::$is_init) {
            self::$is_init = true;
            // do init stuff here
        }
    }
    public static function __callStatic($method, $args) {
        self::_init();
        return self::$method(...$args);
    }
    private static function method_a() {
        // this method is private so gets executed by __callStatic()
        // which handles the init process
    }
    public static function method_b() {
        // this function does not call init function because it is public
    }
}

Simply call any of the private methods as you normally would and they will run _init() if needed:

Foo::method_a();
Orbiculate answered 5/12, 2023 at 18:52 Comment(0)
S
-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 )

Synergist answered 14/6, 2016 at 18:2 Comment(1)
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 yetHotbed

© 2022 - 2024 — McMap. All rights reserved.