117

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.

3
  • 5
    Under discussion is the Static Class Constructor RFC, which would offer an alternative approach. Commented Feb 26, 2016 at 19:41
  • 3
    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. Commented Apr 12, 2019 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. Commented Jul 22, 2021 at 7:29

10 Answers 10

137

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();
Sign up to request clarification or add additional context in comments.

22 Comments

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 - 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
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!
@PeterBailey Lines of code that don't accomplish anything but glue are a distraction and makes code less maintainable.
@ekevoo I'm not the author of the Singleton Pattern, you know. Don't kill the messenger.
|
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.

6 Comments

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

6 Comments

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.
For those of us using composer: I found this: packagist.org/packages/vladimmi/construct-static
@iautomation Didn't tried it but this is worth to be placed in an own answer! It is a straightforward and modern approach.
For those working in a professional production environment where composer is bile of the internet... this answer works very well.
i got an error if(is_callable($class_name, "__init")) { $class_name::__init(); } is this method not useable if i use spl_autoload_register()?
|
11

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

6 Comments

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 - 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. 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.
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.")
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.
|
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)();

8 Comments

this looks strangely interesting
How is it able to call private init from outside the class? Can you explain the details of what you are doing here?
@ToolmakerSteve 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.
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.
CAVEAT: See Szczepan's answer, which explains that techniques like this won't work if you use PHP 7.4's opcache.preload mechanism.
|
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.

Comments

1

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

Comments

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

?>

1 Comment

This is incompatible with PHP 7.4 opcache.preload; see my answer.
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.

1 Comment

This is outside of the scope of the OPs question or else I'd upvote, but big kudos for the concept involved
-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 )

1 Comment

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.