Class static initialization block

  116031
September 11, 2021 23:28 david.proweb@gmail.com (David Rodrigues)
Hello!

I would like to suggest a feature I saw being implemented in the V8 9.4
engine called "*class static initialization block*".

https://v8.dev/blog/v8-release-94

In short, it is a method that is executed once when a class is imported and
loaded, allowing for some more advanced initialization process to be done.

class Test {
    static readonly Carbon $now;

    static () {
        self::$now = now();
    }
}

Currently I can only do this in a very hacky way, usually by creating a
public static method and calling it after class initialization.

class Test {
    static Carbon $now;

    public static function init(): void {
        self::$now = now();
    }
}

Test::init();

I think the way the V8 does is much more interesting.

Another alternative would be to create a magic method like __initialize().
  116032
September 11, 2021 23:35 ocramius@gmail.com (Marco Pivetta)
Hey David,

On Sun, 12 Sep 2021, 01:28 David Rodrigues, proweb@gmail.com> wrote:

> Hello! > > I would like to suggest a feature I saw being implemented in the V8 9.4 > engine called "*class static initialization block*". > > https://v8.dev/blog/v8-release-94 > > In short, it is a method that is executed once when a class is imported and > loaded, allowing for some more advanced initialization process to be done. > > class Test { > static readonly Carbon $now; > > static () { > self::$now = now(); > } > } > > Currently I can only do this in a very hacky way, usually by creating a > public static method and calling it after class initialization. > > class Test { > static Carbon $now; > > public static function init(): void { > self::$now = now(); > } > } > > Test::init(); > > I think the way the V8 does is much more interesting. > > Another alternative would be to create a magic method like __initialize(). >
This already works without any magic. In `MyClass.php`: ```php class MyClass { public static DateTimeImmutable $startup; public function horribleInitializationPractices() { self::$startup = new DateTimeImmutable(); } } MyClass::horribleInitializationPractices(); ``` That's really all there is to it. What you define as hacky is really normal/boring, and does not need special constructs. Also, it's generally not a good idea to sprinkle static mutable runtime-bound all over the place: it should be an exception, not a rule.
>
  116038
September 13, 2021 14:05 mike@newclarity.net (Mike Schinkel)
> On Sep 11, 2021, at 7:35 PM, Marco Pivetta <ocramius@gmail.com> wrote: > > On Sun, 12 Sep 2021, 01:28 David Rodrigues, proweb@gmail.com> wrote: > >> Hello! >> >> I would like to suggest a feature I saw being implemented in the V8 9.4 >> engine called "*class static initialization block*". >> >> https://v8.dev/blog/v8-release-94 >> >> In short, it is a method that is executed once when a class is imported and >> loaded, allowing for some more advanced initialization process to be done. >> >> class Test { >> static readonly Carbon $now; >> >> static () { >> self::$now = now(); >> } >> } >> >> Currently I can only do this in a very hacky way, usually by creating a >> public static method and calling it after class initialization. >> >> class Test { >> static Carbon $now; >> >> public static function init(): void { >> self::$now = now(); >> } >> } >> >> Test::init(); >> >> I think the way the V8 does is much more interesting. >> >> Another alternative would be to create a magic method like __initialize(). >> > > This already works without any magic.
Not true. Here are three (3) things you cannot currently do with your proposed solution and without said magic: 1.) Use static analysis to recognize a class has an initialization functionality and thus provide lint-specific to best practices for static initialization. 2.) Make the initialization function private. 3.) Override and/or change order of initialization of parent in child class. I do not know of use-cases of #3, but #1 and #2 use-case should be obvious.
> ``` > MyClass::horribleInitializationPractices(); > ```
You named that in a rather condescending and passive-aggressive way. Is that really helpful for respectful dialog?
> That's really all there is to it. > > What you define as hacky is really normal/boring, and does not need special > constructs. > > Also, it's generally not a good idea to sprinkle static mutable > runtime-bound all over the place: it should be an exception, not a rule.
Valid use-cases I have come across: 1. Hooking actions and filters in plugins for frameworks and CMS that use hooks as an extension mechanism. 2. Initializing what would ideally be constants but because of PHP's constraints regarding constant initializes cannot be constants.
> On Sep 12, 2021, at 6:42 AM, Rowan Tommins collins@gmail.com> wrote: > > On 12 September 2021 00:28:02 BST, David Rodrigues proweb@gmail.com> wrote: >> Hello! >> >> I would like to suggest a feature I saw being implemented in the V8 9.4 >> engine called "*class static initialization block*". > > > Hi David, > > There was a similar proposal for PHP a few years ago: https://wiki.php.net/rfc/static_class_constructor > > Although it didn't go to a vote, the proposal was dropped for lack of support. There's some discussion summarised in that RFC, and you can probably find the rest by searching for its title on https://externals.io > > Regards, > > -- > Rowan Tommins
Here is the external thread: https://externals.io/message/84602 Rowan, you were definitely the most vocal one to argue against it with the most messages on that thread. But all the arguments against appeared to bog down into discrediting Johannes Ott's desired use-case for static class constructors and did not really look for other reason why they would be useful. As I mentioned above, hooking actions and filters in plugins for frameworks and CMS is where it would be useful, especially since these generally need to be hooked upon initial load of the plugin. Also, initializing variables to act as constants because PHP does not support complex initializations on constants is another use-case. One of your arguments against was the timing of the initialization because evidently Johannes Ott wanted them to be lazily evaluated. Given the use-case I just stated I think they should always be loaded immediately whenever the file is loaded. That makes that argument about debugging cause-and-effect becomes a moot point. And lazy evaluation should be considered as a separate feature. I blogged about that a while back but never brought to the list: https://mikeschinkel.org/2021/lets-add-lazy-evaluation-to-php/ The other main argument you had against was Singletons. But that was a moot point because one of the reasons to use static initialization *is* to instantiate a Singleton. Java has static initialization blocks[1], C# has static constructors[2] so the idea they would be useful for PHP is not a stretch, -Mike [1] https://www.geeksforgeeks.org/g-fact-79/ [2] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors
  116039
September 13, 2021 16:26 rowan.collins@gmail.com (Rowan Tommins)
Hi Mike,

I will leave others to discuss the use cases. It would be interesting if 
people could find the motivating use cases that led to support in C#, 
Java, and JS.


I just wanted to comment quickly on these few points, though:


On 13/09/2021 15:05, Mike Schinkel wrote:

> 1.) Use static analysis to recognize a class has an initialization functionality and thus provide lint-specific to best practices for static initialization.
Theoretically, a convention is enough for this, since PHP has no official static analyser. But certainly a built-in syntax is the strongest kind of convention, so point taken.
> 2.) Make the initialization function private.
I'm not sure how that would work: a private constructor means that it has to be explicitly called from within the same class, but a static initialiser would be called by the engine, before the class scope even existed.
> 3.) Override and/or change order of initialization of parent in child class.
Similarly, I'm not sure this makes sense: it would imply that static initialisers are inherited, and would be re-run every time a new sub-class was defined, rather than being run strictly once when the class is loaded. If the child class wants to re-use code in its static initialiser, it would make more sense to call a (protected) static method than to directly repeat the parent's initialiser block. From your link, it seems that C# restricts them much more than methods or constructors:
> * > > A static constructor doesn't take access modifiers or have parameters. > > * > > A class or struct can only have one static constructor. > > * > > Static constructors cannot be inherited or overloaded. > > * > > A static constructor cannot be called directly and is only meant > to be called by the common language runtime (CLR). It is invoked > automatically. >
Apparently, the JS implementation does allow multiple static initialiser blocks in one class, which are simply processed in order. But JS classes are weird creations, implemented as a complex sugar on top of a classless object system, so may not be the best point of reference for PHP, whose object system much more closely resembles Java or C#. Regards, -- Rowan Tommins [IMSoP]
  116040
September 13, 2021 17:42 mike@newclarity.net (Mike Schinkel)
> On Sep 13, 2021, at 12:26 PM, Rowan Tommins collins@gmail.com> wrote: > >> 2.) Make the initialization function private. > > I'm not sure how that would work: a private constructor means that it has to be explicitly called from within the same class, but a static initialiser would be called by the engine, before the class scope even existed.
I think we are getting in the weeds here and loosing sight of the desired outcome. Currently it is not possible for a developer to disallow calling a class initialization method from outside of the class. However that would end up being achieved — via private methods, protected methods, or even voodoo — those details are unimportant to ultimately disallowing the calling of initialization code from outside the class.
> From your link, it seems that C# restricts them much more than methods or constructors: >> >> A static constructor doesn't take access modifiers or have parameters. >> A class or struct can only have one static constructor. >> Static constructors cannot be inherited or overloaded. >> A static constructor cannot be called directly and is only meant >> to be called by the common language runtime (CLR). It is invoked >> automatically.
Those all sound like they would be appropriate restrictions for PHP too.
> Apparently, the JS implementation does allow multiple static initialiser blocks in one class, which are simply processed in order. But JS classes are weird creations, implemented as a complex sugar on top of a classless object system, so may not be the best point of reference for PHP, whose object system much more closely resembles Java or C#.
I did not reference JS — I only referenced Java and C# — so JS' approach was not part of my argument. -Mike
  116033
September 12, 2021 10:42 rowan.collins@gmail.com (Rowan Tommins)
On 12 September 2021 00:28:02 BST, David Rodrigues proweb@gmail.com> wrote:
>Hello! > >I would like to suggest a feature I saw being implemented in the V8 9.4 >engine called "*class static initialization block*".
Hi David, There was a similar proposal for PHP a few years ago: https://wiki.php.net/rfc/static_class_constructor Although it didn't go to a vote, the proposal was dropped for lack of support. There's some discussion summarised in that RFC, and you can probably find the rest by searching for its title on https://externals.io Regards, -- Rowan Tommins [IMSoP]
  116041
September 13, 2021 21:12 drealecs@gmail.com (=?UTF-8?Q?Alexandru_P=C4=83tr=C4=83nescu?=)
On Sun, Sep 12, 2021 at 2:28 AM David Rodrigues proweb@gmail.com>
wrote:

> Hello! > > I would like to suggest a feature I saw being implemented in the V8 9.4 > engine called "*class static initialization block*". > > https://v8.dev/blog/v8-release-94 > > In short, it is a method that is executed once when a class is imported and > loaded, allowing for some more advanced initialization process to be done. > > class Test { > static readonly Carbon $now; > > static () { > self::$now = now(); > } > } > > Currently I can only do this in a very hacky way, usually by creating a > public static method and calling it after class initialization. > > class Test { > static Carbon $now; > > public static function init(): void { > self::$now = now(); > } > } > > Test::init(); > > I think the way the V8 does is much more interesting. > > Another alternative would be to create a magic method like __initialize(). >
Hey David, The first time I built static initializers for classes was about 15 years ago when, in my class loader (composer was not a thing), if the class had a static method called __init(), it would call it after loading. I don't remember why I needed it but I'm pretty sure I was used with the Java static initialization blocks. Maybe also the limitations that exist on the static values not being able to be dynamically defined could have played a role but I mainly blame it on my lower skill as a programmer. I have never used it in a similar way in the past 10 years, as far as I remember. That's because I almost never use static state as that's just global state and it should be avoided. If you really want to compute some static state, I think that's going to be in PHP in a few years. An attempt to add it with the new keyword was done in https://wiki.php.net/rfc/new_in_initializers#unsupported_positions but was eventually dropped for class static properties and constants. You can read details about it there. Once the identified problems will be sorted out and probably the option to have static initialization blocks will be possible. So far, the current possible approach with having a public static method and calling it after class definition works well in most of the cases. If it's really needed, I think we can use it and be happy that it is supported on all past PHP versions. There is only one place that I know where I think it will not work, if the class is preloaded. But I guess you can just avoid it for now. Alex