[PHP-DEV] Static class

Hi Internals,

I want to introduce the `static` keyword at the class declaration level. That is, the following would be valid: `static class Foo {}`.

A "static class" is one that only permits static members (methods/properties) and could have similar semantics to a static class in C# <https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members&gt;\. In particular: static classes are implied final and they cannot inherit from any other class. These specific semantics are negotiable, but in practice I've never seen the need to mix inheritance with pure static classes.

A static class can be emulated somewhat with a trait in userland, and I have long used such a micro library for this purpose <https://github.com/ScriptFUSION/StaticClass/blob/master/src/StaticClass.php&gt;\. This works by forcing the constructor to be private, thus preventing instantiation, but it cannot guarantee that the consumer does not create any instance methods anyway (just that such methods would be useless as they would be inaccessible). Ergo, it would be better to have this as a language feature so the compiler can provide the necessary guarantees and warnings against improper use.

I am not too familiar with internals, but I figure I can probably muddle my way through an implementation by following the readonly PR <Add support for readonly classes by kocsismate · Pull Request #7305 · php/php-src · GitHub, which I imagine has similar touch points. The main thing I'd like to ascertain at this stage is whether such a PR would have support?

Kind regards,
Bilge

On 15/06/2024 14:53, Rowan Tommins [IMSoP] wrote:

On 15/06/2024 12:16, Bilge wrote:

I want to introduce the `static` keyword at the class declaration level. That is, the following would be valid: `static class Foo {}`.

This has been proposed before, and was rejected at vote. It was nearly 10 years ago, so opinions may have changed, but it would be worth reading through the prior discussion to anticipate or counter the objections raised, and avoid re-treading the same ground.

- RFC: PHP: rfc:abstract_final_class
- Pre-vote discussion threads: [RFC] Abstract final classes - Externals and [RFC] Static classes (Was Abstract final classes) - Externals
- Final vote thread: [VOTE] Abstract final / Static classes - Externals

Searching my list archive, I find that it came up again a few months ago, which I'd entirely forgotten: RFC Proposal - static modifier for classes - Externals

Slightly tangential, but some of the same discussion also came up on these rather lengthy threads about "static class constructors": static constructor - Externals and New RFC draft "static class constructor" - Externals

Regards,

Hi Rowan,

That's all quite interesting, but I didn't get a good sense of why the idea was rejected, other than people didn't like "abstract final" as the syntax (thank God). As for why "static" was rejected, as stated, I don't get a good sense of it at all, but rather than fall back on the opinions of many who are mostly absent among those polled nine years ago, I'd rather get a sense of the current sentiment of those present today.

Cheers,
Bilge

On 15/06/2024 12:16, Bilge wrote:

I want to introduce the `static` keyword at the class declaration level. That is, the following would be valid: `static class Foo {}`.

This has been proposed before, and was rejected at vote. It was nearly 10 years ago, so opinions may have changed, but it would be worth reading through the prior discussion to anticipate or counter the objections raised, and avoid re-treading the same ground.

- RFC: PHP: rfc:abstract_final_class
- Pre-vote discussion threads: [RFC] Abstract final classes - Externals and [RFC] Static classes (Was Abstract final classes) - Externals
- Final vote thread: [VOTE] Abstract final / Static classes - Externals

Searching my list archive, I find that it came up again a few months ago, which I'd entirely forgotten: RFC Proposal - static modifier for classes - Externals

Slightly tangential, but some of the same discussion also came up on these rather lengthy threads about "static class constructors": static constructor - Externals and New RFC draft "static class constructor" - Externals

Regards,

--
Rowan Tommins
[IMSoP]

On 15/06/2024 15:40, Bilge wrote:

That's all quite interesting, but I didn't get a good sense of why the idea was rejected

I haven't re-read the threads in full, so this is mostly from old memories. And please note I am not saying which of these views I agree with, just attempting to summarise them.

Common arguments against:

- Static properties are a form of global state, and that's something widely advised against
- Static classes without any properties would be the same as putting functions and constants in a namespace, which we can already do

Counter-arguments to the above:

- People do it in practice anyway, so why not have a more standard way of writing it
- Functions can't be autoloaded, but static classes can (unfortunately this is a hard problem, at least in part due to the design decision 15 years ago where unqualified names fall back to the global namespace)

rather than fall back on the opinions of many who are mostly absent among those polled nine years ago, I'd rather get a sense of the current sentiment of those present today.

This line of thinking always makes me uneasy. It lends undue weight to *who* made decisions, and not enough to *why*.

Inevitably, personal opinions do have an impact on decisions, but we should always be aiming to weigh the arguments for and against. In a way, it's like the jury in a court: the system works on the assumption that any group of voters would reach the same verdict.

However long ago the discussion started, we shouldn't have to go over the same ground again and again. We should look back at what has already been raised, and think about what's changed, or what additional points weren't considered.

Regards,

--
Rowan Tommins
[IMSoP]

On Sat, Jun 15, 2024, at 16:40, Bilge wrote:

On 15/06/2024 14:53, Rowan Tommins [IMSoP] wrote:

On 15/06/2024 12:16, Bilge wrote:

I want to introduce the static keyword at the class declaration

level. That is, the following would be valid: static class Foo {}.

This has been proposed before, and was rejected at vote. It was nearly

10 years ago, so opinions may have changed, but it would be worth

reading through the prior discussion to anticipate or counter the

objections raised, and avoid re-treading the same ground.

https://externals.io/message/79338

Searching my list archive, I find that it came up again a few months

ago, which I’d entirely forgotten: https://externals.io/message/121717

Slightly tangential, but some of the same discussion also came up on

these rather lengthy threads about “static class constructors”:

https://externals.io/message/84602 and https://externals.io/message/85779

Regards,

Hi Rowan,

That’s all quite interesting, but I didn’t get a good sense of why the

idea was rejected, other than people didn’t like “abstract final” as the

syntax (thank God). As for why “static” was rejected, as stated, I don’t

get a good sense of it at all, but rather than fall back on the opinions

of many who are mostly absent among those polled nine years ago, I’d

rather get a sense of the current sentiment of those present today.

Cheers,

Bilge

I can see these mostly being used for factories, laravel facades, or utility functions. I think they would probably be useful in some codebases.

I would argue that in the codebases I work in, these types of constructs represent a code smell and it would be a lot easier to catch via static analysis and prevent. The current trick of using traits is much harder to catch except during code review.

So, I say “yes please.”

— Rob

On 15/06/2024 16:58, Rowan Tommins [IMSoP] wrote:

Common arguments against:

- Static properties are a form of global state, and that's something widely advised against

Correct, but a static class does not need to have any state. Indeed, I cannot think of an instance where I (personally) have needed state in a static class. If this ever becomes a point of contention (though I doubt it would), we could even consider prohibiting state in static classes, locking it down completely to just methods. A draconian option, but an option nonetheless.

- Static classes without any properties would be the same as putting functions and constants in a namespace, which we can already do

You already provided a counter-point to this; we can't autoload functions. Besides, there may be good reason (organizationally) to group sets of static functionality together (in a class) rather than having them as free-standing functions only so grouped in a file (which does not necessarily imply the same degree of cohesion).

rather than fall back on the opinions of many who are mostly absent among those polled nine years ago, I'd rather get a sense of the current sentiment of those present today.

This line of thinking always makes me uneasy. It lends undue weight to *who* made decisions, and not enough to *why*.

Meaning no disrespect to anyone whom was participating nine years ago (I have nothing but respect for those whom came before), I really don't care _who_ fielded opinions or decisions, my point was simple and singular: that even if an opinion was valid back then, if nobody were to uphold it today, it wouldn't carry any weight now. I think enough time has passed that gauging the sentiment of today is valid and worthwhile, especially if it has shifted (and we cannot know without asking).

Cheers,
Bilge

On 15/06/2024 17:17, Bilge wrote:

You already provided a counter-point to this; we can't autoload functions.

Just to reiterate, please do not attribute any of the arguments or counter-arguments to me. They were my attempt, from memory, to summarise previous discussions.

Besides, there may be good reason (organizationally) to group sets of static functionality together (in a class) rather than having them as free-standing functions only so grouped in a file (which does not necessarily imply the same degree of cohesion).

I don't think "free-standing functions ... grouped in a file" is a valid description of namespaced functions. In case there is some misunderstanding, the point is that "Foo::bar()" is essentially equivalent to "Foo\bar()"

even if an opinion was valid back then, if nobody were to uphold it today, it wouldn't carry any weight now

I fundamentally disagree with this assertion.

If somebody makes a valid point, it doesn't automatically become invalid because time has passed, or because nobody happens to repeat it in a later e-mail thread.

If I copy and paste the content of each e-mail from the previous thread, does that make them "carry weight" again? What if I contact the authors of each, and ask them to do so? Is that a good use of anyone's time, when we can just read the archives?

I think enough time has passed that gauging the sentiment of today is valid and worthwhile, especially if it has shifted (and we cannot know without asking).

I don't think "sentiment" is something we should place value on. As I said in my last e-mail, we should be weighing the merit of the arguments for and against, not the people who are making them.

I don't see value in repeating the same arguments every X months or years, like appointing a different jury to try the same case.

Regards,

--
Rowan Tommins
[IMSoP]

On Sat, Jun 15, 2024, at 9:40 AM, Bilge wrote:

On 15/06/2024 14:53, Rowan Tommins [IMSoP] wrote:

On 15/06/2024 12:16, Bilge wrote:

I want to introduce the `static` keyword at the class declaration
level. That is, the following would be valid: `static class Foo {}`.

This has been proposed before, and was rejected at vote. It was nearly
10 years ago, so opinions may have changed, but it would be worth
reading through the prior discussion to anticipate or counter the
objections raised, and avoid re-treading the same ground.

- RFC: PHP: rfc:abstract_final_class
- Pre-vote discussion threads: [RFC] Abstract final classes - Externals and
[RFC] Static classes (Was Abstract final classes) - Externals
- Final vote thread: [VOTE] Abstract final / Static classes - Externals

Searching my list archive, I find that it came up again a few months
ago, which I'd entirely forgotten: RFC Proposal - static modifier for classes - Externals

Slightly tangential, but some of the same discussion also came up on
these rather lengthy threads about "static class constructors":
static constructor - Externals and New RFC draft "static class constructor" - Externals

Regards,

Hi Rowan,

That's all quite interesting, but I didn't get a good sense of why the
idea was rejected, other than people didn't like "abstract final" as the
syntax (thank God). As for why "static" was rejected, as stated, I don't
get a good sense of it at all, but rather than fall back on the opinions
of many who are mostly absent among those polled nine years ago, I'd
rather get a sense of the current sentiment of those present today.

Cheers,
Bilge

Please see my comments in the thread from just a few months ago that Rowan linked. I still stand by every one of them, and absolutely oppose "all static" classes, unequivocally for the reasons stated.

Consider this comment a shorthand for copy-pasting my previous posts to this thread. :slight_smile:

--Larry Garfield

Common arguments against:

  • Static properties are a form of global state, and that’s something
    widely advised against

Correct, but a static class does not need to have any state. Indeed, I
cannot think of an instance where I (personally) have needed state in a
static class. If this ever becomes a point of contention (though I doubt
it would), we could even consider prohibiting state in static classes,
locking it down completely to just methods. A draconian option, but an
option nonetheless.

  • Static classes without any properties would be the same as putting
    functions and constants in a namespace, which we can already do

You already provided a counter-point to this; we can’t autoload
functions. Besides, there may be good reason (organizationally) to group
sets of static functionality together (in a class) rather than having
them as free-standing functions only so grouped in a file (which does
not necessarily imply the same degree of cohesion).

rather than fall back on the opinions of many who are mostly absent
among those polled nine years ago, I’d rather get a sense of the
current sentiment of those present today.

This line of thinking always makes me uneasy. It lends undue weight to
who made decisions, and not enough to why.

Meaning no disrespect to anyone whom was participating nine years ago (I
have nothing but respect for those whom came before), I really don’t
care who fielded opinions or decisions, my point was simple and
singular: that even if an opinion was valid back then, if nobody were to
uphold it today, it wouldn’t carry any weight now. I think enough time
has passed that gauging the sentiment of today is valid and worthwhile,
especially if it has shifted (and we cannot know without asking).

Cheers,
Bilge

Hi!

Static classes without any properties would be the same as putting
functions and constants in a namespace, which we can already do

In addition to the absence of autoloading, this is also not correct because PHP does not have internal/private functions.
Very often I use functions for really simple things like array_flatten (and register them via composer.json.autoload.files). But when the function requires decomposition to smaller ones, I switch to a static class with one public and some private static methods.

I tend to support this idea, because it makes intent more expressive and allows to get rid of private function __construct() {} visual debt.
I also agree that static implies final. This could be later changed without breaking BC.

Good luck, Bilge!

···

Valentin Udaltsov

I proposed this previously and have a working implementation, though it may need some updates. However, I chose not to pursue it further because it was clear it wouldn’t pass as many people here are unaware that PHP is a multi-paradigm language.

I frequently use static classes because they allow me to have private methods and constants within the class. With namespaced functions, there’s no way to declare functions private to the current scope; any function declared in that namespace will be public unless I use a separate internal namespace, which is impractical.

I also disagree with the idea that static classes imply global state. They are primarily used for grouping functions.

Additionally, there’s a general acceptance that we can’t autoload functions in PHP, and nothing is being done to address this issue. Yet, it is considered impractical or a “Code Smell” to do one of the only things that make sense.

This is a minor change that breaks nothing and would help many people, yet no one is willing to raise any valid arguments against it.

On Sat, Jun 15, 2024 at 5:19 AM Bilge <bilge@scriptfusion.com> wrote:

Hi Internals,

I want to introduce the static keyword at the class declaration level.
That is, the following would be valid: static class Foo {}.

A “static class” is one that only permits static members
(methods/properties) and could have similar semantics to a static class
in C#
<https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members>.
In particular: static classes are implied final and they cannot inherit
from any other class. These specific semantics are negotiable, but in
practice I’ve never seen the need to mix inheritance with pure static
classes.

A static class can be emulated somewhat with a trait in userland, and I
have long used such a micro library for this purpose
<https://github.com/ScriptFUSION/StaticClass/blob/master/src/StaticClass.php>.
This works by forcing the constructor to be private, thus preventing
instantiation, but it cannot guarantee that the consumer does not create
any instance methods anyway (just that such methods would be useless as
they would be inaccessible). Ergo, it would be better to have this as a
language feature so the compiler can provide the necessary guarantees
and warnings against improper use.

I am not too familiar with internals, but I figure I can probably muddle
my way through an implementation by following the readonly PR
<https://github.com/php/php-src/pull/7305/files>, which I imagine has
similar touch points. The main thing I’d like to ascertain at this stage
is whether such a PR would have support?

Kind regards,
Bilge

On 15 Jun 2024, at 14:11, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

I fundamentally disagree with this assertion.

If somebody makes a valid point, it doesn't automatically become invalid because time has passed, or because nobody happens to repeat it in a later e-mail thread.

If I copy and paste the content of each e-mail from the previous thread, does that make them "carry weight" again? What if I contact the authors of each, and ask them to do so? Is that a good use of anyone's time, when we can just read the archives?

I think enough time has passed that gauging the sentiment of today is valid and worthwhile, especially if it has shifted (and we cannot know without asking).

I don't think "sentiment" is something we should place value on. As I said in my last e-mail, we should be weighing the merit of the arguments for and against, not the people who are making them.

I don't see value in repeating the same arguments every X months or years, like appointing a different jury to try the same case.

Regards,

--
Rowan Tommins
[IMSoP]

If you appoint a different jury to try a 20 year old case, the decision of the previous jury doesn't have any more weight than any other evidence on its own. That's because society changes, law changes and people change. A 10 year old discussion in the world of technology has little value to add and a lot of harm to cause.

You may have core developers that voted no due to maintenance burden, but if said maintainer is no longer active and new maintainers don't mind it, it's a moot argument because people changed.

You may have no votes casted because at the time PHP technical debt couldn't cope with such a change, which maybe isn't relevant anymore because the project evolved.

You may have community leaders voting no because they inherently disagree with the concept but if they have moved on to other endeavors and current PHP community members like the concept, then society changes play a vital role in a different outcome.

Ultimately I can agree with you that there is no point in rehashing the same discussion under the same circumstances. But the last 10 years has completely changed the development world enough that anything that old is worth rehashing and I would even add that going through the archives is a double-edge sword because you can either come out of it with a stronger argument for why the RFC is good now or you can come out of it overwhelmed with negativity and a polluted opinion on the type of barriers that you may think that exists but that might be long gone

Let's start getting specific so we can make some progress.

The goals of this RFC are fairly straightforward:

  * Introduce the `static` keyword at the class level to preclude the
    need to create a private constructor. That is, `__construct` would
    be invalid in a static class.
  * Prohibit instantiation of a static class with a language-level error.
  * Prohibit declaration of instance members in a static class with a
    language-level error.

However, as ever, there's a devil in the details. In particular, we need to consider the following:

1. Since a "static class" implies all members are static, should we still allow explicit static method declarations with the `static` keyword?
2. Should `static` imply `final`?
2a. If yes, should `final` still be allowed to be explicitly declared, despite being implied?
3. Should a "static class" prohibit inheritance?
4. Should a "static class" permit state?
5. Should traits be permitted in a static class?
6. Which magic methods (if any) should be valid in a static class?

Based on my current understanding, I would propose the following answers to these questions:

1. In order to make upgrading simple and keep method intentions clear, `static` should not only be valid but also required on each method declaration, as usual for static methods.
2. Inheritance doesn't make much sense in a purely static context, despite the fact that PHP has a static resolution operator; I often find this is an anti-pattern. In any case, this is a non-BC issue if we lock down inheritance for now and later decide to open it up. Disabling inheritance also is in-line with the C# implementation of the same.
2a. Since under this proposal, `final` is implied, it should not be necessary (or allowed) to be specified. Upgrading would be simply a case of replacing `final` with `static` in-place.
3. As already mentioned, inheritance in a purely static context doesn't make much sense, and it's a non-BC break to prohibit it and later enable it. This decision is also in-line with the C# implementation.
4. Perhaps the most contentious decision, we could disable state and later enable it if there is a need, without BC. I personally cannot think of a time when I needed state in a static class. That said, I do not agree with disabling state within the language. In case someone is relying on static state in such a class, upgrading would be impossible; they would instead have to avoid marking the class as static, which defeats the purpose of this RFC. I believe we should support state, and if someone dislikes static state, they should enforce that with a code style rule in their project; this is not something that should be prohibited by the language itself as "regular" classes already allow this.
5. Provided a trait follows the rules of the static class (i.e. all members are static), it seems to me this should be allowed, though I have little use for it myself.
6. Given there are many magic methods, this topic probably deserves a separate discussion; it is not something I have spent a lot of time on thus far so it is just included for visibility at this time.

If there are any strongly dissenting opinions on any of these points, or any significant points I may have missed, please share. Otherwise, I would be happy to draw up and RFC along these lines (notwithstanding I have no RFC karma at present), followed by an implementation once all outstanding questions are answered.

Cheers,
Bilge

On 15/06/2024 14:53, Rowan Tommins [IMSoP] wrote:

On 15/06/2024 12:16, Bilge wrote:

I want to introduce the `static` keyword at the class declaration level. That is, the following would be valid: `static class Foo {}`.

This has been proposed before, and was rejected at vote. It was nearly 10 years ago, so opinions may have changed, but it would be worth reading through the prior discussion to anticipate or counter the objections raised, and avoid re-treading the same ground.

- RFC: PHP: rfc:abstract_final_class
- Pre-vote discussion threads: [RFC] Abstract final classes - Externals and [RFC] Static classes (Was Abstract final classes) - Externals
- Final vote thread: [VOTE] Abstract final / Static classes - Externals

Searching my list archive, I find that it came up again a few months ago, which I'd entirely forgotten: RFC Proposal - static modifier for classes - Externals

Slightly tangential, but some of the same discussion also came up on these rather lengthy threads about "static class constructors": static constructor - Externals and New RFC draft "static class constructor" - Externals

Regards,

On 16 June 2024 03:00:39 BST, "Marco Aurélio Deleu" <deleugyn@gmail.com> wrote:

If you appoint a different jury to try a 20 year old case, the decision of the previous jury doesn't have any more weight than any other evidence on its own.

You missed the point of the analogy. The point is that that would only happen if you have some specific reason why the case needs to be reopened.

I'm not saying nobody is allowed to talk about this. I'm just saying, let's start by looking at the old discussion, and discuss *specifically* what might have changed, rather than waving our hands and saying "10 years is a long time, so no opinion from that long ago can possibly be valid".

You may have core developers that voted no due to maintenance burden, but if said maintainer is no longer active and new maintainers don't mind it, it's a moot argument because people changed.

The maintenance burden argument is actually a good example of *not* being about individuals. The argument is not "I don't want to maintain it", it's "we shouldn't burden future maintainers with this".

You may have no votes casted because at the time PHP technical debt couldn't cope with such a change, which maybe isn't relevant anymore because the project evolved.

This, on the other hand, is a good example of one where we don't need to guess. Look at the archives - were people concerned about the implementation? If so, pointing out that the implementation would now be simpler would absolutely be a reason to bring it back to discussion.

You may have community leaders voting no because they inherently disagree with the concept but if they have moved on to other endeavors and current PHP community members like the concept, then society changes play a vital role in a different outcome.

Again, let's stop talking in the abstract, and look at this specific case. Can you point to changes in the usage of PHP that make this feature more likely to see wide use or acceptance? Do you think the community at large, who we are trying to represent here, is more or less likely to write a purely static class in 2024 than in 2014?

All I'm asking is that if we are going to revisit features we previously rejected, we start with "here's why I think the arguments for and against this feature have changed", rather than "I don't like the old result, I demand a new vote".

Regards,
Rowan Tommins
[IMSoP]

On Sun, Jun 16, 2024, at 10:33, Rowan Tommins [IMSoP] wrote:

On 16 June 2024 03:00:39 BST, “Marco Aurélio Deleu” <deleugyn@gmail.com> wrote:

If you appoint a different jury to try a 20 year old case, the decision of the previous jury doesn’t have any more weight than any other evidence on its own.

You missed the point of the analogy. The point is that that would only happen if you have some specific reason why the case needs to be reopened.

I’m not saying nobody is allowed to talk about this. I’m just saying, let’s start by looking at the old discussion, and discuss specifically what might have changed, rather than waving our hands and saying “10 years is a long time, so no opinion from that long ago can possibly be valid”.

You may have core developers that voted no due to maintenance burden, but if said maintainer is no longer active and new maintainers don’t mind it, it’s a moot argument because people changed.

The maintenance burden argument is actually a good example of not being about individuals. The argument is not “I don’t want to maintain it”, it’s “we shouldn’t burden future maintainers with this”.

You may have no votes casted because at the time PHP technical debt couldn’t cope with such a change, which maybe isn’t relevant anymore because the project evolved.

This, on the other hand, is a good example of one where we don’t need to guess. Look at the archives - were people concerned about the implementation? If so, pointing out that the implementation would now be simpler would absolutely be a reason to bring it back to discussion.

You may have community leaders voting no because they inherently disagree with the concept but if they have moved on to other endeavors and current PHP community members like the concept, then society changes play a vital role in a different outcome.

Again, let’s stop talking in the abstract, and look at this specific case. Can you point to changes in the usage of PHP that make this feature more likely to see wide use or acceptance? Do you think the community at large, who we are trying to represent here, is more or less likely to write a purely static class in 2024 than in 2014?

All I’m asking is that if we are going to revisit features we previously rejected, we start with “here’s why I think the arguments for and against this feature have changed”, rather than “I don’t like the old result, I demand a new vote”.

Regards,

Rowan Tommins

[IMSoP]

I don’t understand why we are comparing this to a jury and/or court case. In many countries, juries don’t even exist (such as the one I currently reside in) so the only context is US TV shows for what that even means. Secondly, RFC’s are not “on trial” and can be presented over and over again without much change. To say “go read the history” is a cop out.

If it keeps coming up, by random individuals, then there is clearly enough demand that people would go through the challenge of arriving here and presenting it. Even if it is the hundredth time, those people deserve our respect to at least copy and paste our previous emails instead of sending them on a wild goose chase.

I did go back and read them, and the original from 10 years ago was so convoluted it wasn’t even worth it. The one from Lanre was basically kicked (possibly by a vocal minority) for people not understanding the value, not because the idea was invalid.

Here we are, another person who may present it differently to help you understand the value. I understand the value, though I don’t agree with it. If I could vote, I would vote yes; even if I never used it because I understand how it would be used and its usefulness for certain types of projects (I’ve def used this sort of pattern on single-file proof of concepts).

To Lanre and Bilge, good luck! From watching this list for the last few years, many people won’t contribute to the conversation until there is a real RFC/implementation to discuss. It may be worth teaming up. :wink:

— Rob

On Sun, Jun 16, 2024, at 11:17, Bilge wrote:

Let’s start getting specific so we can make some progress.

The goals of this RFC are fairly straightforward:

  • Introduce the static keyword at the class level to preclude the need to create a private constructor. That is, __construct would be invalid in a static class.

  • Prohibit instantiation of a static class with a language-level error.

  • Prohibit declaration of instance members in a static class with a language-level error.

However, as ever, there’s a devil in the details. In particular, we need to consider the following:

  1. Since a “static class” implies all members are static, should we still allow explicit static method declarations with the static keyword?

To keep it inline with readonly and abstract. I would look to those and follow their rules.

  1. Should static imply final?

2a. If yes, should final still be allowed to be explicitly declared, despite being implied?

I would allow it, though emit a warning that it’s unnecessary. That’s what I would expect from a compiled language, anyway.

  1. Should a “static class” prohibit inheritance?

I would think it should be allowed. This is one of the most annoying things about static classes in C#. That being said, PHP has traits, so static classes could theoretically use traits to provide inheritance-like behavior. Can traits be marked static, or just classes?

  1. Should a “static class” permit state?

Here’s the thing, there are still ways to get state even if you disallow it (create a function called getState() that has an internal static array). Using state in a static class is a code smell, for sure. However, constants should be allowed and even those are quite limited in what values they can hold. Until that gets cleared up, we should perhaps allow variables so complex constants can exist (implementing the construction of them, however, should be left as an exercise for the developer).

  1. Should traits be permitted in a static class?

I hope so. Traits are some of the most abused super powers of PHP.

  1. Which magic methods (if any) should be valid in a static class?

Are magic methods allowed in a static context? If so, only those should be implementable.

Based on my current understanding, I would propose the following answers to these questions:

  1. In order to make upgrading simple and keep method intentions clear, static should not only be valid but also required on each method declaration, as usual for static methods.

  2. Inheritance doesn’t make much sense in a purely static context, despite the fact that PHP has a static resolution operator; I often find this is an anti-pattern. In any case, this is a non-BC issue if we lock down inheritance for now and later decide to open it up. Disabling inheritance also is in-line with the C# implementation of the same.

2a. Since under this proposal, final is implied, it should not be necessary (or allowed) to be specified. Upgrading would be simply a case of replacing final with static in-place.

  1. As already mentioned, inheritance in a purely static context doesn’t make much sense, and it’s a non-BC break to prohibit it and later enable it. This decision is also in-line with the C# implementation.

  2. Perhaps the most contentious decision, we could disable state and later enable it if there is a need, without BC. I personally cannot think of a time when I needed state in a static class. That said, I do not agree with disabling state within the language. In case someone is relying on static state in such a class, upgrading would be impossible; they would instead have to avoid marking the class as static, which defeats the purpose of this RFC. I believe we should support state, and if someone dislikes static state, they should enforce that with a code style rule in their project; this is not something that should be prohibited by the language itself as “regular” classes already allow this.

  3. Provided a trait follows the rules of the static class (i.e. all members are static), it seems to me this should be allowed, though I have little use for it myself.

  4. Given there are many magic methods, this topic probably deserves a separate discussion; it is not something I have spent a lot of time on thus far so it is just included for visibility at this time.

If there are any strongly dissenting opinions on any of these points, or any significant points I may have missed, please share. Otherwise, I would be happy to draw up and RFC along these lines (notwithstanding I have no RFC karma at present), followed by an implementation once all outstanding questions are answered.

Cheers,

Bilge

On 15/06/2024 14:53, Rowan Tommins [IMSoP] wrote:

On 15/06/2024 12:16, Bilge wrote:

I want to introduce the static keyword at the class declaration level. That is, the following would be valid: static class Foo {}.

This has been proposed before, and was rejected at vote. It was nearly 10 years ago, so opinions may have changed, but it would be worth reading through the prior discussion to anticipate or counter the objections raised, and avoid re-treading the same ground.

Searching my list archive, I find that it came up again a few months ago, which I’d entirely forgotten: https://externals.io/message/121717

Slightly tangential, but some of the same discussion also came up on these rather lengthy threads about “static class constructors”: https://externals.io/message/84602 and https://externals.io/message/85779

Regards,

— Rob

https://github.com/oplanre/php-src/tree/feature/static-class

On Sat, Jun 15, 2024 at 3:17 PM Lanre <lnearwaju@gmail.com> wrote:

I proposed this previously and have a working implementation, though it may need some updates. However, I chose not to pursue it further because it was clear it wouldn’t pass as many people here are unaware that PHP is a multi-paradigm language.

I frequently use static classes because they allow me to have private methods and constants within the class. With namespaced functions, there’s no way to declare functions private to the current scope; any function declared in that namespace will be public unless I use a separate internal namespace, which is impractical.

I also disagree with the idea that static classes imply global state. They are primarily used for grouping functions.

Additionally, there’s a general acceptance that we can’t autoload functions in PHP, and nothing is being done to address this issue. Yet, it is considered impractical or a “Code Smell” to do one of the only things that make sense.

This is a minor change that breaks nothing and would help many people, yet no one is willing to raise any valid arguments against it.

On Sat, Jun 15, 2024 at 5:19 AM Bilge <bilge@scriptfusion.com> wrote:

Hi Internals,

I want to introduce the static keyword at the class declaration level.
That is, the following would be valid: static class Foo {}.

A “static class” is one that only permits static members
(methods/properties) and could have similar semantics to a static class
in C#
<https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members>.
In particular: static classes are implied final and they cannot inherit
from any other class. These specific semantics are negotiable, but in
practice I’ve never seen the need to mix inheritance with pure static
classes.

A static class can be emulated somewhat with a trait in userland, and I
have long used such a micro library for this purpose
<https://github.com/ScriptFUSION/StaticClass/blob/master/src/StaticClass.php>.
This works by forcing the constructor to be private, thus preventing
instantiation, but it cannot guarantee that the consumer does not create
any instance methods anyway (just that such methods would be useless as
they would be inaccessible). Ergo, it would be better to have this as a
language feature so the compiler can provide the necessary guarantees
and warnings against improper use.

I am not too familiar with internals, but I figure I can probably muddle
my way through an implementation by following the readonly PR
<https://github.com/php/php-src/pull/7305/files>, which I imagine has
similar touch points. The main thing I’d like to ascertain at this stage
is whether such a PR would have support?

Kind regards,
Bilge

On 16 June 2024 10:27:27 BST, Rob Landers <rob@bottled.codes> wrote:

I don’t understand why we are comparing this to a jury and/or court case. In many countries, juries don’t even exist (such as the one I currently reside in) so the only context is US TV shows for what that even means.

Apologies, I'm from the UK, and forgot that systems vary so much. I think it roughly works to replace "jury" with "judge" or "magistrate", or whoever decides legal cases in your jurisdiction.

The analogy I was trying to draw is that we should be aiming to make a decision based on the merit of the case, not our personal biases; and we should give previous voters the respect of assuming that they did so as well.

Secondly, RFC’s are not “on trial” and can be presented over and over again without much change.

That's exactly what I'm saying should *not* happen.

To say “go read the history” is a cop out.

Saying "I can't be bothered to read the history" is *also* a cop out. It places all the burden on long-term contributors to repeat the same arguments every time someone joins the list and revives an old topic.

Why is it up to long-term contributors to defend the previous decision, rather than up to someone new to defend reopening it?

Even if it is the hundredth time, those people deserve our respect to at least copy and paste our previous emails instead of sending them on a wild goose chase.

I completely reject the characterisation of sending anyone on a wild goose chase. I searched the archive, and found the specific threads, and even summarised the points as I remembered them.

But the reasoning given wasn't that it was too much effort to understand the previous discussion; it was that all opinions from 10 years ago were automatically irrelevant, and that is the attitude I am fundamentally opposing.

To be fair, this particular topic hasn't come up many times, but I'm taking a hard line because I don't want the next new contributor to say "but you voted on that one twice, so let's revote my favourite one as well".

If you think things have changed, that's fine - but be explicit *what* you think has changed, don't just talk in the abstract and make us repeat ourselves.

Regards,
Rowan Tommins
[IMSoP]

Marco Deleu

You may have core developers that voted no due to maintenance burden, but if said maintainer is no longer active and new maintainers don't mind it, it's a moot argument because people changed.

The maintenance burden argument is actually a good example of *not* being about individuals. The argument is not "I don't want to maintain it", it's "we shouldn't burden future maintainers with this".

I don't agree that maintainers 10 years ago choosing to not "burden future maintainers with this" is more valid than current maintainers choosing otherwise. As I said, too much has changed and so has the weight of the burden (for better or worse).

All I'm asking is that if we are going to revisit features we previously rejected, we start with "here's why I think the arguments for and against this feature have changed", rather than "I don't like the old result, I demand a new vote".

You think a good place to start is to pinpoint what changed. I think that in 10 years *everything* has changed. I wasn't giving a list of possible abstracts" for us to discuss each of them, I was pointing out how easy it is to come up with multiple reasons why a new vote might go a different direction.

I don't like the old result but I don't think there is any "demanding a new vote". There is simply new people interested in something that coincidentally happened to have had an interest a decade ago.

On 15 June 2024 18:39:20 BST, Valentin Udaltsov <udaltsov.valentin@gmail.com> wrote:

PHP does not have internal/private functions.
Very often I use functions for really simple things like `array_flatten`
(and register them via composer.json.autoload.files). But when the function
requires decomposition to smaller ones, I switch to a static class with one
public and some private static methods.

Namespace visibility or a native "internal" attribute is something that would be really useful, I think, because it would be able to apply to whole classes as well as functions and constants. It would also allow you to make the refactoring you mention without having to switch all calls from Foo\bar() to Foo::bar()

In general, I think PHP should embrace the concept of "packages" much more - for larger apps, there's a much greater scope for performance if OpCache could analyse multiple files at once, and make assumptions about them. (A consideration which didn't apply when autoloading was first introduced.)

With that in mind, instead of function autoloading, maybe we should think about an "include_all" function, which accepted a wildcard/glob pattern. That would make it much easier to use preloading, and move away from the "one class per file" layout, without blessing an official tool like Composer, or a particular file layout.

Put together, those features would provide a powerful alternative to a lot of static classes - i.e. those where no state or inheritance is required, just a grouping of code.

Regards,
Rowan Tommins
[IMSoP]

To start, any limitations imposed on these new static classes that current classes can do that are not intrinsically linked to their “staticness” would result in developers grudgingly writing a class “statically” that is not declared as static. Imposing restrictions on features that developers use in the wild would certainly cause them to consider the feature to be dubiously excluded.

Not supporting all features used in the wild would result in developers not being able to use static classes for new and/or refactored code if they believe they need those excluded features. This would stop developers from signaling their intent to readers of their code and would stop developers from being able to rely on reflection to discern static classes for new and/or refactored code when that otherwise could have been possible.

Disabling existing features just for static classes would result in creating two forms of static classes — explicit and implicit — and that IMO would be a miss for the addition of static classes.

That said…

Yes, but optional.

Supporting the static keyword for members would ease the transition for tooling, and support those who prefer to be explicit in all the things.

OTOH, of all the things I mention below, this is my least firmly-held opinion.

No.

The primary PHP framework I wrote before I quite working professionally in PHP — although others still use it — has a series of implied static classes, all of which inherit from a base implied static class that provided services to all other implied static classes. To update this library to a newer version of PHP with static classes would not be possible if static classes are assumed to be final.

Further, Laravel — which I have never used but is simultaneously the most beloved framework in PHP but also the most hated, depending on who you ask — has a base Model class that Eloquent models inherit from, and a BaseController for custom controllers. Enforcing final would leave Laravel out in the lurch for classes that are exactly the type of classes that should be declared static.

If a developer wants their declared static class to be final they should be required to use the final keyword, IMO.

BTW, see #4 and #5 why this is important.

Isn’t #3 just a rewording of #2?

Absolutely, without a doubt.

My PHP library uses static properties to keep track of certain data which I treated as immutable — registering business information for use by static factory methods, and in some cases the factory methods use arrays to store instances they create for processing and/or dispensing on demand late by other static methods.

Laravel uses static properties in their service containers such as Illuminate\Support\Facades\App, their Cache facade uses static properties, Eloquent models maintain state in static properties, and developer written factory classes often use static properties to track instances.

While we can ignore my library it would be much harder to ignore Laravel’s use of state in static properties.

Of course.

Laravel makes extensive use of traits in their implied static classes, in their core framework and in Eloquent models, and they encourage Laravel application developers to use traits in their applications.

Traits are a valuable code structuring mechanism as an alternate to inheritance more like containment, and not supporting traits would, again, be leaving developers depending on it out in the cold.

Also, final/inheritance might not be as critical to support if it were not for one capability that PHP’s traits are glaringly missing and that is their inability to allow developers to declare properties and methods as private to the trait itself.

Of course if we add trait-private to traits then it would be less problematic to implied final/not support inheritance, but I doubt we’d want to couple this potential RFC with one that hasn’t even been considered yet.

Even so, not supporting inheritance would mean not allowing implied static classes that use inheritance to be refactored to use the static declaration.

Unless I am mistaken, the only magic method that support static function calls is __callStatic() as everything else deals with instances, so I assume that it is the only one that needs to be supported.

And yes, IMO it is critical to support __callStatic() since Laravel Facades (as well as my own library) make use of it, and in Laravel’s case, extensive use.

Of course there may(?) be newer PHP features on the horizon that are an alternate to __callStatic() but supporting __callStatic() would make it easier for developers to move to static classes as even automated tooling could make the conversion if __callStatic() and all other features used in the wild are supported.

Anyway, that is my take. #fwiw

-Mike

P.S. To learn about Laravel’s uses of implied static classes I asked ChatGPT[1], so if I got anything wrong about Laravel I apologize as I was relying on ChatGPT not to hallucinate on this topic. I assumed it was unlikely to hallucinate given there was so much info on the web there is regarding Laravel for ChatGPT to train on.

[1] https://chatgpt.com/share/79eb330b-69ae-4104-b2ac-6e77955ec914

···

Let’s start getting specific so we can make some progress.

The goals of this RFC are fairly straightforward:

  • Introduce the static keyword at the class level to preclude the need to create a private constructor. That is, __construct would be invalid in a static class.
  • Prohibit instantiation of a static class with a language-level error.
  • Prohibit declaration of instance members in a static class with a language-level error.

However, as ever, there’s a devil in the details. In particular, we need to consider the following:

  1. Since a “static class” implies all members are static, should we still allow explicit static method declarations with the static keyword?

  2. Should static imply final?

  3. Should a “static class” prohibit inheritance?

  4. Should a “static class” permit state?

  5. Should traits be permitted in a static class?

  6. Which magic methods (if any) should be valid in a static class?