Hello everybody.
I found myself wanting this feature (that I first encountered when
programming in C#) for removing a workaround from a codebase I work from
time to time.
I searched internals and found a discussion from almost a decade ago. That
discussion did not end well, mostly because of insulting accusations.
I then decided to do some research on this subject and found out that it's
a pretty common feature in other OOP languages.
Also, as I started studying the php-src (and missed the days when I used
to program in C in my main job), I decided to do an implementation myself
even before presenting the RFC.
The implementation link can also be found at the RFC.
You can read the RFC here:
https://wiki.php.net/rfc/static_constructor
Regards,
Erick
Hi Erick,
śr., 19 cze 2024 o 14:35 Erick de Azevedo Lima ericklima.comp@gmail.com
napisał(a):
Hello everybody.
I found myself wanting this feature (that I first encountered when
programming in C#) for removing a workaround from a codebase I work from
time to time.
I searched internals and found a discussion from almost a decade ago. That
discussion did not end well, mostly because of insulting accusations.
I then decided to do some research on this subject and found out that it's
a pretty common feature in other OOP languages.
Also, as I started studying the php-src (and missed the days when I used
to program in C in my main job), I decided to do an implementation myself
even before presenting the RFC.
The implementation link can also be found at the RFC.You can read the RFC here:
https://wiki.php.net/rfc/static_constructorRegards,
Erick
I like the idea of having a static initializer.
I think we could propose a better naming, method name function __staticConstructor
is a concatenation of the words static and constructor
while constructor is used as an initializer when building constructing
objects.
Have you considered naming it for example shortly function __static()
?
It is somehow similar to https://wiki.php.net/rfc/static_constructor#java
in static-block.
Cheers,
Michał Marcin Brzuchalski
Hi, Michał.
Have you considered naming it for example shortly
function __static()
?
I have considered some names, actually. I just chose this one for the
implementation because
I tried to design it to be as close as possible to the C# implementation
and they call it "static constructor".
But the name can be changed to another one without any problem at all.
Regards,
Erick
Hi
I have considered some names, actually. I just chose this one for the
implementation because
I tried to design it to be as close as possible to the C# implementation
and they call it "static constructor".
But the name can be changed to another one without any problem at all.
I would suggest __constructStatic()
. This matches the existing naming
pattern of __callStatic()
being the companion to __call()
.
Best regards
Tim Düsterhus
Hi
I have considered some names, actually. I just chose this one for the
implementation because
I tried to design it to be as close as possible to the C# implementation
and they call it "static constructor".
But the name can be changed to another one without any problem at all.I would suggest
__constructStatic()
. This matches the existing
naming pattern of__callStatic()
being the companion to__call()
.Best regards
Tim Düsterhus
Why can't it just be public static function __construct()
?
Cheers,
Bilge
Hi
I have considered some names, actually. I just chose this one for the
implementation because
I tried to design it to be as close as possible to the C# implementation
and they call it "static constructor".
But the name can be changed to another one without any problem at all.I would suggest
__constructStatic()
. This matches the existing
naming pattern of__callStatic()
being the companion to__call()
.Best regards
Tim DüsterhusWhy can't it just be
public static function __construct()
?Cheers,
Bilge
Two functions with the same name in the same class?
Hello, everyone.
I'm sorry for not being active for a time on the internals, including about
this RFC of mine.
I just had a huge house-related problem and recently also health problems
with my family.
So, I'll have to stay away for some time. But I'll go ahead with the talks
about this RFC as soon as possible.
Thank you!
--
Erick
Hello everybody.
I found myself wanting this feature (that I first encountered when
programming in C#) for removing a workaround from a codebase I work
from time to time.
I searched internals and found a discussion from almost a decade ago.
That discussion did not end well, mostly because of insulting
accusations.
I then decided to do some research on this subject and found out that
it's a pretty common feature in other OOP languages.
Also, as I started studying the php-src (and missed the days when I
used to program in C in my main job), I decided to do an implementation
myself even before presenting the RFC.
The implementation link can also be found at the RFC.You can read the RFC here:
https://wiki.php.net/rfc/static_constructorRegards,
Erick
Unsurprisingly, I have concerns. :-) Perhaps surprisingly, I don't outright hate it. In particular, the examples you show are on the edge of what I'd consider valid use cases: More complex initialization of things like lookup tables or "dynamic constants" (like if you wanted to record "now" to use for later comparisons).
For that reason, therefore, I don't like the current approach, especially for this line: "Programmers have the option to call the __staticConstruct method to reset static properties values if desired."
It's screwy enough that you can explicitly call __construct(). I wouldn't want to perpetuate that weirdness. It also feels like it leaves the door open to more abuse than is tolerable. As some of the comments note, half the use cases would necessarily involve reaching out to some global state (file system, DB, etc.), which is already problematic, especially for testing.
Which also brings up another question: How would one even mock this? If I can't test it, I can't use it.
I would favor the "Java 2" style: Referencing a static method that will be called to initialize the value. That makes it clearer what is happening and encourages the intended path/use case: Lazy property initialization. It also avoids a "big blob" function in favor of small, specific functions. It also allows the author to more easily decide if they want to expose that logic to child classes for overriding: Make it private or protected, as they prefer. (Public I am fine with forbidding.)
A few other notes:
Your examples would be clearer if you leveraged the ??= operator, which would reduce your initializeMinDate() methods to a single line.
I also take issue with this paragraph:
Object-oriented languages like Java, which adhere more strictly to object-oriented principles, include static properties and offer mechanisms for their initialization with non-trivial expressions. Java uses method calls or static blocks for this purpose, as will be demonstrated later in this text, illustrating that even in environments stricter about OOP principles than PHP, static properties are sometimes useful and require appropriate initialization methods.
Not because other languages are wrong to reference; I do so very frequently, and do consider "everyone else is doing it" to be a useful (though not always winning) argument. What I object to is holding up Java as being "stricter about OO principles." OO principles are not a uniform, monolithic thing. In fact, the person who invented the term Object-Oriented has said before that C++ and Java are not what he had in mind. "Class based programming" is not what OOP was intended to be. OOP is about "message passing," which is often forgotten or misunderstood or ignored. Also, my day job is now working in Kotlin, which means I am faced with a lot of Java code I have to interact with. To be polite, holding up "Java style" design as anything to emulate is... a categorical error.
Referencing other languages is fine, do that (and I appreciate that you did; I didn't realize this was so common a feature, and that does help make me amenable to it), but please do not in any way suggest that Java is the definition of "good and proper OOP." It is extremely not.
I'm still not sold on the idea, but... I think I could be, which is more than I expected from the title.
--Larry Garfield
Hi Erick
On Wed, Jun 19, 2024 at 2:34 PM Erick de Azevedo Lima
ericklima.comp@gmail.com wrote:
You can read the RFC here:
https://wiki.php.net/rfc/static_constructor
I see that you're using zend_class_init_statics() as a hook to call
__static_construct(). This makes the initialization order
unpredictable, because static properties are initialized lazily when
the class is first used (when instantiated, when accessing constants,
etc.). Essentially, this recreates the same problem described in the
"new in initializer" RFC:
https://wiki.php.net/rfc/new_in_initializers#unsupported_positions
New expressions continue to not be supported in (static and non-static) property initializers and class constant initializers. The reasons for this are twofold:
[snip]
For static property initializers and class constant initializers a different evaluation order issue arises. Currently, these initializers are evaluated lazily the first time a class is used in a certain way (e.g. instantiated). Once initializers can contain potentially side-effecting expressions, it would be preferable to have a more well-defined evaluation order. However, the straightforward approach of evaluating initilizers when the class is declared would break certain existing code patterns. In particular, referencing a class that is declared later in the same file would no longer work.
Lazy evaluation might be ok if order is clearly defined. Making the
order undefined makes it hard (or impossible) to understand which
symbols declared in the current file may be used from
__static_construct(). The alternative mentioned above (calling
__static_construct() at class-declaration-time) likely breaks too much
existing code (because it would also require calling static
initializers just before that, which may reference symbols declared
later on), and is further complicated by early-binding. I'm not sure
what the best approach is here.
Ilija
On Wed, Jun 19, 2024 at 2:34 PM Erick de Azevedo Lima
ericklima.comp@gmail.com wrote:New expressions continue to not be supported in (static and non-static) property initializers and class constant initializers. The reasons for this are twofold:
[snip]
For static property initializers and class constant initializers a different evaluation order issue arises. Currently, these initializers are evaluated lazily the first time a class is used in a certain way (e.g. instantiated). Once initializers can contain potentially side-effecting expressions, it would be preferable to have a more well-defined evaluation order. However, the straightforward approach of evaluating initilizers when the class is declared would break certain existing code patterns. In particular, referencing a class that is declared later in the same file would no longer work.Lazy evaluation might be ok if order is clearly defined.
Consider that some uses for a static function need to always occur no matter whether or not any other method of the class is called. My previous email [1] covered several.
Here [2] is a discussion on StackOverflow of "hooks" that should be eagerly loaded.
Thus I think it important that if lazy evaluation is used for static initializers then it should be an option, and ideally one that is opt-in vs. opt-out, e.g.:
class Foo {
private static lazy function __staticConstruct():void {
// Do initialization stuff here
}
}
-Mike
[1] https://externals.io/message/123675#123684 https://externals.io/message/123675#123684
[2] https://stackoverflow.com/a/2400206/102699 <https://stackoverflow.com/a/2400206/102699
On Wed, Jun 19, 2024 at 2:34 PM Erick de Azevedo Lima
ericklima.comp@gmail.com wrote:New expressions continue to not be supported in (static and non-static)
property initializers and class constant initializers. The reasons for this
are twofold:
[snip]
For static property initializers and class constant initializers a
different evaluation order issue arises. Currently, these initializers are
evaluated lazily the first time a class is used in a certain way (e.g.
instantiated). Once initializers can contain potentially side-effecting
expressions, it would be preferable to have a more well-defined evaluation
order. However, the straightforward approach of evaluating initilizers when
the class is declared would break certain existing code patterns. In
particular, referencing a class that is declared later in the same file
would no longer work.Lazy evaluation might be ok if order is clearly defined.
Consider that some uses for a static function need to always occur no
matter whether or not any other method of the class is called. My previous
email [1] covered several.Here [2] is a discussion on StackOverflow of "hooks" that should be
eagerly loaded.Thus I think it important that if lazy evaluation is used for static
initializers then it should be an option, and ideally one that is opt-in
vs. opt-out, e.g.:class Foo {
private static lazy function __staticConstruct():void {
// Do initialization stuff here
}
}-Mike
[1] https://externals.io/message/123675#123684
[2] https://stackoverflow.com/a/2400206/102699
Seeing an extra keyword made me think of:
class Foo
{
public constructor __construct() {}
public destructor __destruct() {}
public static constructor __construct() {}
public static destructor __destruct() {} // probably doesn't make sense
right now
}
This syntax would allow named constructors in the future, or even omitting
the default name.
class Foo
{
public constructor() {}
public destructor() {}
public constructor create(int $id)
{
$this->id = $id; // no need to call the default __construct, nor
needs to return
}
}
Not sure if this is even a desired path to take, just wanted to throw the
idea out there.
Hi all.
Answering to Larry:
More complex initialization of things like lookup tables or "dynamic
constants" (like if you wanted to record "now" to use for later
comparisons).
Those are my use cases, to be honest.
For that reason, therefore, I don't like the current approach, especially
for this line: "Programmers have the option to call the __staticConstruct
method to reset static properties values if desired."
I just laughed at myself when I read this. As I said, my main inspiration
was the C# implementation and there, the static constructor can only be
called by the engine.
And that was my intention, but then I remembered that the "__construct"
method can be called on an already constructed object and I implemented it
this way.
If it's a consensus to remove it, I'll gladly do it. :-)
Your examples would be clearer if you leveraged the ??= operator, which
would reduce your initializeMinDate() methods to a single line.
Yes, it's true. As a former teaching assistant, I tend to give examples
that might be longer than necessary, with the intention of being as clear
as possible.
In this case, it was to emphasize the fact that we must implement a logic
to check (the "if" at the beginning of the method) if the property was
already initialized.
What I object to is holding up Java as being "stricter about OO
principles." OO principles are not a uniform, monolithic thing.
In fact, the person who invented the term Object-Oriented has said before
that C++ and Java are not what he had in mind.
"Class based programming" is not what OOP was intended to be.
To be honest, when I wrote this part, I was indeed thinking about "class
based" programming.
And Java is stricter than PHP and C++ in this sense: It does not have loose
functions, everything must be inside a class.
And even though "Class based programming" is not what OOP was intended to
be, it's what's mostly found out there: OOP = Class based programming
But my main point here is to show that this feature is often provided
because it's proven to be useful for programmers of multiple programming
languages.
Answering to Mike:
I noticed you did not include an example for Go so I wrote one up for you
in a Go playground. Hopefully you can include Go's approach in your RFC?
No, I'm not familiar enough with Go to provide examples, so I did not
consider it. As you provided the example, I'll gladly include it. Thank you!
To elaborate more about "best practices" where I see static initializers
being especially valuable it when you want to initialize immutable data,
and especially when that data is in complex form such as an object vs.
just a simple value.
Yes, I think that's "the" use case.
Tim:
I would suggest
__constructStatic()
. This matches the existing naming
pattern of__callStatic()
being the companion to__call()
.
Noted. I'm not really attached to any name and your point makes a lot of
sense.
Ilija:
I see that you're using zend_class_init_statics() as a hook to call
__static_construct().
Yes, I thought that lazy initialization would cover most of the use-cases
for initializing static variables, so that seemed the best place to do it.
This makes the initialization order
unpredictable, because static properties are initialized lazily when
the class is first used (when instantiated, when accessing constants,
etc.).
From my tests, the static variables initialization is even lazier: The
"zend_class_init_statics" is only called when you try to access a static
variable.
See this example:
class C1
{
private static \DateTimeInterface $v1;
public function __construct()
{
echo __METHOD__ . PHP_EOL;
}
private static function __staticConstruct()
{
self::$v1 = new \DateTimeImmutable('2024-06-19 00:00:00');
echo 'calling ' . __METHOD__ . PHP_EOL;
echo C2::USING_SYMBOL_DECLARED_LATER . PHP_EOL;
}
public static function getV1()
{
echo 'Before accessing C1::$v1 variable' . PHP_EOL;
return self::$v1;
}
}
class C2
{
public const USING_SYMBOL_DECLARED_LATER =
'C2::USING_SYMBOL_DECLARED_LATER';
}
$obj1 = new C1(); //should not trigger zend_class_init_statics()
echo C1::getV1()->format('Y-m-d') . PHP_EOL; //
This example prints:
C1::__constructBefore accessing C1::$v1 variablecalling
C1::__staticConstructC2::USING_SYMBOL_DECLARED_LATER2024-06-19
Lazy evaluation might be ok if order is clearly defined. Making the
order undefined makes it hard (or impossible) to understand which
symbols declared in the current file may be used from __static_construct()
Can you provide an example for that? I'm not sure if I understood
everything correctly.
... is further complicated by early-binding. I'm not sure what the best
approach is here.
One of my first thoughts was to perform this at the end of class linking
(on function zend_do_link_class()),
but then I came to a conclusion that performing it during the already
existing lazy init would cover most use cases.
Mike again:
Consider that some uses for a static function need to always occur no
matter
whether or not any other method of the class is called. My previous email
[1] covered several.
Here [2] is a discussion on StackOverflow of "hooks" that should be
eagerly loaded.
Yes, I considered this approach before. But then I realized that for
covering the properties initialization,
this already existing lazy loading mechanism would suffice. At the end of
the RFC,
I consider future scope to create an early-init. In my opinion, as it
covers a more general use for this init,
it should be implemented maybe using another magic method, like "__load" or
"__link",
which would be hooks on class loading process and not specific to static
variables initialization
Lynn:
Not sure if this is even a desired path to take, just wanted to throw the
idea out there.
Looking at the idea, I like the new "method modifiers". But it would be a
much bigger scope to tackle right now.
Best regards,
Erick
On Jun 20, 2024, at 9:01 AM, Erick de Azevedo Lima ericklima.comp@gmail.com wrot
Mike again:Consider that some uses for a static function need to always occur no matter
whether or not any other method of the class is called. My previous email [1] covered several.
Here [2] is a discussion on StackOverflow of "hooks" that should be eagerly loaded.
Yes, I considered this approach before. But then I realized that for covering the properties initialization,
this already existing lazy loading mechanism would suffice. At the end of the RFC,
I consider future scope to create an early-init. In my opinion, as it covers a more general use for this init,
it should be implemented maybe using another magic method, like "__load" or "__link",
which would be hooks on class loading process and not specific to static variables initialization
Well, that sure is depressing to hear.
Looking back on all my uses for static methods, my primary use-case when building WordPress websites was to wire up hooks thus what excited me most about the prospect of getting "static constructors" in PHP was for wiring up said hooks.
I get the lack of desire to support a lazy keyword, especially giving the lazy loading thread happening on the list, and I agree that lazy loading is better for the use-cases that motivated you, but it is depressing because the likelihood of someone else after so many years would have the passion and motivation to propose a __load
method AND to have the skills to implement it is slim-to-none IMO.
So IOW, a __load
method it's almost certainly never going to happen. Such is life I guess.
Well at least let me submit a parting bikeshed on the name. Rather than any form of "construct" can you at least name it __init
or __initialize
? It is not construction, and even in your comment where you wrote unprompted "...not specific to static variables initialization" you christened it to be "initialization."
P.S. I do think lazy-loading without an explicit keyword will be counter-intuitive to many, and will result in many a StackOverflow question asking "Why are my __staticConstruct
(or __init
as proposed) methods not being called?!?" So if it were me I would implement them w/o default lazy loading so there would be motivation in the future to address it another way.
That, or go ahead and also propose a __load
method as part of this RFC.
Seeing an extra keyword made me think of:
class Foo { public constructor __construct() {} public destructor __destruct() {} public static constructor __construct() {} public static destructor __destruct() {} // probably doesn't make sense right now }
This syntax would allow named constructors in the future, or even omitting the default name.
class Foo { public constructor() {} public destructor() {} public constructor create(int $id) { $this->id = $id; // no need to call the default __construct, nor needs to return } }
Not sure if this is even a desired path to take, just wanted to throw the idea out there.
I do like the constructor/destructor vs. function naming, but as I wrote above I think "__init" — and for your approach Lynn: "initializer" — would be more applicable for static classes methods, with or without an explicit prefixed static keyword.
#fwiw
-Mike
Hi Erick,
It would be useful to include the example of Singleton implementation with
a static constructor.
Kind regards,
Jorg
I searched internals and found a discussion from almost a decade ago.
That discussion did not end well, mostly because of insulting accusations.
If you're talking about the March/April 2015 thread, I don't think
that's a fair summary. There were some slightly ill-mannered comments
about the use cases, but mostly even those instinctively against were
willing to discuss the details of the proposal.
The discussion ended not because of any bad feelings, but because the
person proposing it had to put it on pause because of things happening
in their personal life, and evidently did not pick it back up again:
https://externals.io/message/85779#85985
- Initial thread: https://externals.io/message/84602
- RFC draft thread: https://externals.io/message/85779
- RFC draft: https://wiki.php.net/rfc/static_class_constructor
Digging through the archives, I actually found some even older threads,
though none of them seems to have reached a full proposal:
- 2004 - https://externals.io/message/13183
- 2010 - https://externals.io/message/49435
- 2013 - https://externals.io/message/65078
So, there's definitely desire out there, but also some challenges. A few
things look to have come up again and again (other than whether the use
cases are justified):
- Naming (yay bikeshedding!)
- Whether it should be declared the same way as other magic methods, or
have special rules (e.g. different syntax, private even though it's
called by the engine, error if you explicitly call it) - Exactly when it should run (on class load? on first access?). (Like
Ilija, I was also reminded of Nikita's comments on the "new in
initializers" RFC, which touch on this point) - Concerns about implementation details and interaction with other features
Personally, I'm on the fence. I've never had a particular desire for it
myself, and worry that it would be hard to understand or debug exactly
when it was triggered, but can see there is both a desire and a precedent.
Regards,
--
Rowan Tommins
[IMSoP]