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
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: https://wiki.php.net/rfc/abstract_final_class
- Pre-vote discussion threads: https://externals.io/message/79211 and
https://externals.io/message/79338 - Final vote thread: https://externals.io/message/79601
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,
--
Rowan Tommins
[IMSoP]
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: https://wiki.php.net/rfc/abstract_final_class
- Pre-vote discussion threads: https://externals.io/message/79211 and
https://externals.io/message/79338- Final vote thread: https://externals.io/message/79601
Searching my list archive, I find that it came up again a few months
ago, which I'd entirely forgotten: https://externals.io/message/121717Slightly 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/85779Regards,
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 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: https://wiki.php.net/rfc/abstract_final_class
- Pre-vote discussion threads: https://externals.io/message/79211 and
https://externals.io/message/79338- Final vote thread: https://externals.io/message/79601
Searching my list archive, I find that it came up again a few months
ago, which I'd entirely forgotten: https://externals.io/message/121717Slightly 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/85779Regards,
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
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]
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
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]
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
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]
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. ;)
— Rob
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.
Common arguments against:
- Static properties are a form of global state, and that's something
widely advised againstCorrect, 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 doYou 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
PHP does not have internal/private functions.
Very often I use functions for really simple things likearray_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]
PHP does not have internal/private functions.
Very often I use functions for really simple things likearray_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]
Considering that I brought up an internal attribute not too long ago and it seems like “packaging” is a giant can of worms, I’d like to suggest we don’t even approach the topic. People on this list have very specific ideas of what that means and unless someone is going to finally try championing an rfc after 30 years, it shouldn’t even be a factor.
— Rob
Regarding function autoloading:
A more interesting question to me is a convention where to put each function.
With classes, PSR-4 tells us exactly what one can expect to find in a
class file with a given name.
Perhaps a separate directory tree per package, with one file per sub-namespace?
Or just a package-wide functions.php?
Regarding static methods vs functions.
From the older thread, I can see different levels of possible
objection to all-static classes:
- "Every static method should instead be either a non-static method,
or a regular procedural function." - "Static methods are acceptable, but no class should exist that has
only static methods." - "Static methods and all-static classes are acceptable, but no new
language feature is needed to specifically support all-static
classes." - "A new language feature for all-static classes could be acceptable,
but it should be very minimal and declarative."
I see myself somewhere between 3 and 4.
The DX of static methods can be evaluated from two perspectives: The
calling side, and the declaration side.
On the calling code side, the class of a static method call is usually
visible in the place where the call is made,
For a function call, the namespace is usually only visible in the
imports list at the top of the file.
The class name that is part of a static method call hints at an object
type, whereas for a namespace fragment it may not be really clear what
it describes.
In a static factory call, like Color::fromRgb($red, $green, $blue)
,
I would argue that having the class name as part of the call improves
DX.
On the declaration side, I like to have a static factory in the same
place as the class itself.
For other cases, it can vary whether having a class name as part of
the call improves DX or not.
On the declaration side, static methods do bring some additional
features, that is, to split out some parts into private and protected
methods.
This said, in many cases such classes could be converted to non-static
instantiable classes.
One benefit of static methods is how they can be part of incremental
refactoring.
- Split some functionality into a private method.
- Notice that this method does not depend on any object state, so we
make it static. - Now that it is static, it could as well be public, if we clean up
the signature a bit. This way it can be reused and tested
independently. - Now that it is public static, I rather want to move it out of the
class, into a utility class. - Now that we have this standalone method, we could convert it to a
procedural function. - OR, we may decide that the entire utility class should actually be
converted to a service, and the method to be not static.
Or:
- Move some functionality out of the constructor into a static factory.
- Let the static factory return different implementations based on the
parameters. E.g. Color::fromRbg() could return a GrayscaleColor object
or a RainbowColor object, depending on parameter values. - Make
Color
an all-static class, because the specific
implementations have dedicated classes. - Convert the static methods on
Color
into object methods on
ColorFromRgbFactory
. - OR, we discover static polymorphism (*) and "abstract protected
static function". - Later we might decide that this was awkward, and convert everything
to object methods.
In both of these scenarios we have steps with an all-static class.
There are possibilities to move away from that.
But in some cases the all-static class is actually the sweet spot
where we want to stay.
Another case for an all-static class would be a class that only
contains class constants.
Yes, we can use define(...)
, but sometimes we want class constants,
for similar arguments as above.
A third case is if the all-static class already exists, as part of a
legacy codebase, and we could not break it up even if we wanted to.
If we do end up with an all-static class, even as an intermediate
state, it can be useful to declare this in some way.
Developers should immediately see whether a class is instantiable or not.
A trait or a base class or an explicit private constructor can do the
job, but it won't be as universal as a language feature.
On the other hand:
From the older proposal by Lanre Waju:
All methods and variables in the class are implicitly static.
I would very much oppose this.
These implicit semantics would make it harder and error-prone to move
methods around from a regular class to an all-static class.
And it would be confusing to look at such a method, because typically
the class-level static declaration won't be visible when you look at
the method.
Also think of the noise in git commits caused by the conversion to an
all-static class.
(Btw the same problem applies to class-level readonly)
To me, the best version of a class-level "static" keyword would be as
a contract, and nothing more:
- The class cannot be instantiated.
- It cannot have a constructor or destructor, or any magic methods
that only apply to objects. - It can only extend or be extended by other static classes.
(otherwise it should be declared "abstract" instead)
The same could be achieved with a native base class or trait with
private constructor, or even the same thing from php-fig.
This would be less clear and less powerful than a static keyword, but
also less invasive to the language.
(One such language feature could be a language-level base class or
trait, simply for the sake of unification)
From this thread:
In particular: static classes are implied final and they cannot inherit from any other class.
With the version of the static keyword as above, I would be against
this limitation.
Static polymorphism (*) is possible today, and it should be perfectly
fine to slap a "static" keyword on these classes, if they are
all-static.
This is not about whether or not we like or dislike static
polymorphism, but about not attaching unrelated meaning to a keyword.
An explicit "static final class" is more obvious than "static class"
with implicit final.
(*) The term "static polymorphism" is a bit questionable.
I obviously mean this:
class B {
static function f() {
return static::g();
}
protected static function g() {
return null;
}
}
class C extends B {
protected static function g() {
return 5;
}
}
assert(C::f() === 5);
I have used this in the past, and I am now questioning my choices in
some of these cases.
Still I would not consider it an anti-pattern.
--- Andreas
Regarding function autoloading:
A more interesting question to me is a convention where to put each function.
With classes, PSR-4 tells us exactly what one can expect to find in a
class file with a given name.
Perhaps a separate directory tree per package, with one file per sub-namespace?
Or just a package-wide functions.php?Regarding static methods vs functions.
From the older thread, I can see different levels of possible
objection to all-static classes:
- "Every static method should instead be either a non-static method,
or a regular procedural function."- "Static methods are acceptable, but no class should exist that has
only static methods."- "Static methods and all-static classes are acceptable, but no new
language feature is needed to specifically support all-static
classes."- "A new language feature for all-static classes could be acceptable,
but it should be very minimal and declarative."I see myself somewhere between 3 and 4.
For reference, I have documented my stance on statics here:
https://peakd.com/hive-168588/@crell/cutting-through-the-static
--Larry Garfield
Regarding function autoloading:
A more interesting question to me is a convention where to put each function.
With classes, PSR-4 tells us exactly what one can expect to find in a
class file with a given name.
Perhaps a separate directory tree per package, with one file per sub-namespace?
Or just a package-wide functions.php?Regarding static methods vs functions.
From the older thread, I can see different levels of possible
objection to all-static classes:
- "Every static method should instead be either a non-static method,
or a regular procedural function."- "Static methods are acceptable, but no class should exist that has
only static methods."- "Static methods and all-static classes are acceptable, but no new
language feature is needed to specifically support all-static
classes."- "A new language feature for all-static classes could be acceptable,
but it should be very minimal and declarative."I see myself somewhere between 3 and 4.
For reference, I have documented my stance on statics here:
https://peakd.com/hive-168588/@crell/cutting-through-the-static
--Larry Garfield
Thanks, Larry!
This aligns more or less with your messages from the older thread.
Make it a function.
This is fine, but:
If you would replace all of your functions with static methods in
all-static classes, your code would still be as testable as before.
The calls would look more verbose, perhaps more confusing.
Developers may find reasons to use static methods and possibly
all-static classes over functions:
- Conformity with an existing code base or ecosystem.
- Lack of a convention on where to put your namespaced methods, and
which namespace to use. - Personal habit and familiarity.
- Convenience of moving code between static and non-static methods.
- The possibility of using private and protected static methods, and
static polymorphism (whether that is good or bad). - Easy access to class constants.
- The static methods already exist.
Besides, the namespace lookup is generally less ambiguous for classes
than it is for methods.
A str_starts_with()
call could reference a function
Current\Name\Space\str_starts_with()
, or the global
\str_starts_with()
.
A class always needs the explicit alias or a \\
prefix.
At the same time, the top-level namespace is crowded with native
procedural functions, but not so much with native classes.
At first, this is just an inconvenience when reading the code.
But it can also lead to real bugs, if a function alias gets lost when
you resolve a merge conflict, and you don't notice it because there is
a top-level function with the same name.
So, the above would be reasons why developers might not go all the way
to namespaced procedural functions.
Then, the mere presence and habit of static functions will lead to
some cases of all-static classes.
Even if you only use static methods as named constructors (static
factory method), you can end up with an all-static class, as in my
example above:
- You start with private constructor + public static factory.
- You rename the part of the class that is instantiable, to better
distinguish different implementations, but you keep the static methods
in place for BC reasons.
(this assumes that all dependent packages depend on the static
factory and on the interface, but not on the class directly). - Now it would be nice to visibly mark the class non instantiable.
-- Andreas
For a function call, the namespace is usually only visible in the
imports list at the top of the file.
This is one of those interesting cases where the language itself is
flexible, but people are used to a specific style: a use statement can
import any level of namespace, to provide as much or as little context
as needed in a particular piece of code.
namespace Acme\Artistry\Color {
function fromRgb($red, $green, $blue) { /.../ }
}
namespace My\Own\Application {
use Acme\Artistry\Color;
// ...
$color = Color\fromRgb(1,2,3);
// ...
}
Maybe that's rare in practice, but it's always tricky to judge how much
the language should account for such habits - see also "every class
needs a new file", and my pet hate "deprecation notices get promoted to
errors".
The class name that is part of a static method call hints at an object
type, whereas for a namespace fragment it may not be really clear what
it describes.
In a static factory call, likeColor::fromRgb($red, $green, $blue)
,
I would argue that having the class name as part of the call improves
DX.
This example seems slightly off: if the static method is on the class it
is a factory for, that class clearly isn't completely static.
Presumably what we're actually talking about is something like
"ColorFactory::fromRgb(...)" - or maybe
"GraphicsFactory::colorFromRgb(...)", where you might well want to
abbreviate to "colorFromRgb(...)".
Your points are generally well made, I just wanted to point out there
are some nuances in that particular area.
Regards,
--
Rowan Tommins
[IMSoP]
On Sun, 16 Jun 2024 at 19:09, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
For a function call, the namespace is usually only visible in the
imports list at the top of the file.This is one of those interesting cases where the language itself is flexible, but people are used to a specific style: a use statement can import any level of namespace, to provide as much or as little context as needed in a particular piece of code.
namespace Acme\Artistry\Color {
function fromRgb($red, $green, $blue) { /.../ }
}namespace My\Own\Application {
use Acme\Artistry\Color;// ... $color = Color\fromRgb(1,2,3); // ...
}
Maybe that's rare in practice, but it's always tricky to judge how much the language should account for such habits - see also "every class needs a new file", and my pet hate "deprecation notices get promoted to errors".
Yes, the possibility for namespace part imports exists.
Unfortunately these namespace imports usually need to be set up
manually, whereas the regular class or function import has better
automatic support from the IDE.
Also, namespace fragments are often more generic than function names,
so more likely to need a custom alias.
The class name that is part of a static method call hints at an object
type, whereas for a namespace fragment it may not be really clear what
it describes.
In a static factory call, likeColor::fromRgb($red, $green, $blue)
,
I would argue that having the class name as part of the call improves
DX.This example seems slightly off: if the static method is on the class it is a factory for, that class clearly isn't completely static.
There are two separate argument tracks here.
The Color::fromRgb($red, $green, $blue)
has a nice DX, and justifies
the eixstence of static methods in general.
This also aligns with Larry's argument about "named constructors", or
the more general "type context".
Then, you may rename the instantiable part of the class, while leaving
the static factories in place.
You end up with an all-static class.
Or you already start with different implementation classes like
RgbColor and HexColor, all implementing ColorInterface, but you want
one centralized place for static factories.
I personally prefer to call this Color
instead of ColorFactory
.
For me ColorFactory::create()
would give me a ColorFactoryInterface
object, not a ColorInterface object.
Presumably what we're actually talking about is something like "ColorFactory::fromRgb(...)" - or maybe "GraphicsFactory::colorFromRgb(...)",
Basically yes.
where you might well want to abbreviate to "colorFromRgb(...)".
I assume you mean as a procedural function?
Technically that would be possible.
But I still like to keep the Color::
part which is suggestive of the
ColorInterface
, even when the class Color
itself is not really
instantiable.
A namespace fragment or a function prefix is not as suggestive that
this produces a ColorInterface object
Your points are generally well made, I just wanted to point out there are some nuances in that particular area.
Thanks.
Yes, the possibility for namespace part imports exists.
Unfortunately these namespace imports usually need to be set up
manually, whereas the regular class or function import has better
automatic support from the IDE.
Also, namespace fragments are often more generic than function names,
so more likely to need a custom alias.
That's why I said it was tricky: the language doesn't prevent IDEs
suggesting different imports; nor does it require you to name namespace
fragments differently from classes; but that's what happens in practice.
So to what extent do we build the language based on how it can be
used, and to what extent do we build it based on how it is used? What
if that usage changes over time? What if we add more ways to do it, and
those aren't used either? I don't think there's an easy answer.
--
Rowan Tommins
[IMSoP]
On Sun, 16 Jun 2024 at 20:08, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
Yes, the possibility for namespace part imports exists.
Unfortunately these namespace imports usually need to be set up
manually, whereas the regular class or function import has better
automatic support from the IDE.
Also, namespace fragments are often more generic than function names,
so more likely to need a custom alias.That's why I said it was tricky: the language doesn't prevent IDEs suggesting different imports; nor does it require you to name namespace fragments differently from classes; but that's what happens in practice.
Right.
The main thing here would be to have a 90% deterministic way to
generate the imports.
We want to avoid meaningless choices a developer needs to make case by case.
I see some difficulties here, but there are probably ways to handle it.
So to what extent do we build the language based on how it can be used, and to what extent do we build it based on how it is used? What if that usage changes over time? What if we add more ways to do it, and those aren't used either? I don't think there's an easy answer.
It depends how disruptive the change is.
E.g. symfony has a Yaml class with only class constants and static methods.
https://github.com/symfony/symfony/blob/35890e1ca999cb92d7b20c81bc07cdc2f17b7bb4/src/Symfony/Component/Yaml/Yaml.php
We can argue whether this is good or bad.
Or we can just slap a "static" modifier on the class, so that
developers know what to expect.
This does not prevent future refactoring, where these methods might be
converted to functions.
Btw, even with only the class constants left, this would still be an
all-static class.
Here is a relevant example usage of static classes/polymorphism on my
personal site/app:
https://gist.github.com/oplanre/6894f56fb61134ee5ea93cf262ba3662
On Sun, Jun 16, 2024 at 10:38 AM Andreas Hennings andreas@dqxtech.net
wrote:
Regarding function autoloading:
A more interesting question to me is a convention where to put each
function.
With classes, PSR-4 tells us exactly what one can expect to find in a
class file with a given name.
Perhaps a separate directory tree per package, with one file per
sub-namespace?
Or just a package-wide functions.php?Regarding static methods vs functions.
From the older thread, I can see different levels of possible
objection to all-static classes:
- "Every static method should instead be either a non-static method,
or a regular procedural function."- "Static methods are acceptable, but no class should exist that has
only static methods."- "Static methods and all-static classes are acceptable, but no new
language feature is needed to specifically support all-static
classes."- "A new language feature for all-static classes could be acceptable,
but it should be very minimal and declarative."I see myself somewhere between 3 and 4.
The DX of static methods can be evaluated from two perspectives: The
calling side, and the declaration side.
On the calling code side, the class of a static method call is usually
visible in the place where the call is made,
For a function call, the namespace is usually only visible in the
imports list at the top of the file.
The class name that is part of a static method call hints at an object
type, whereas for a namespace fragment it may not be really clear what
it describes.
In a static factory call, likeColor::fromRgb($red, $green, $blue)
,
I would argue that having the class name as part of the call improves
DX.
On the declaration side, I like to have a static factory in the same
place as the class itself.For other cases, it can vary whether having a class name as part of
the call improves DX or not.
On the declaration side, static methods do bring some additional
features, that is, to split out some parts into private and protected
methods.
This said, in many cases such classes could be converted to non-static
instantiable classes.One benefit of static methods is how they can be part of incremental
refactoring.
- Split some functionality into a private method.
- Notice that this method does not depend on any object state, so we
make it static.- Now that it is static, it could as well be public, if we clean up
the signature a bit. This way it can be reused and tested
independently.- Now that it is public static, I rather want to move it out of the
class, into a utility class.- Now that we have this standalone method, we could convert it to a
procedural function.- OR, we may decide that the entire utility class should actually be
converted to a service, and the method to be not static.Or:
- Move some functionality out of the constructor into a static factory.
- Let the static factory return different implementations based on the
parameters. E.g. Color::fromRbg() could return a GrayscaleColor object
or a RainbowColor object, depending on parameter values.- Make
Color
an all-static class, because the specific
implementations have dedicated classes.- Convert the static methods on
Color
into object methods on
ColorFromRgbFactory
.- OR, we discover static polymorphism (*) and "abstract protected
static function".- Later we might decide that this was awkward, and convert everything
to object methods.In both of these scenarios we have steps with an all-static class.
There are possibilities to move away from that.
But in some cases the all-static class is actually the sweet spot
where we want to stay.Another case for an all-static class would be a class that only
contains class constants.
Yes, we can usedefine(...)
, but sometimes we want class constants,
for similar arguments as above.A third case is if the all-static class already exists, as part of a
legacy codebase, and we could not break it up even if we wanted to.If we do end up with an all-static class, even as an intermediate
state, it can be useful to declare this in some way.
Developers should immediately see whether a class is instantiable or not.A trait or a base class or an explicit private constructor can do the
job, but it won't be as universal as a language feature.On the other hand:
From the older proposal by Lanre Waju:All methods and variables in the class are implicitly static.
I would very much oppose this.
These implicit semantics would make it harder and error-prone to move
methods around from a regular class to an all-static class.
And it would be confusing to look at such a method, because typically
the class-level static declaration won't be visible when you look at
the method.
Also think of the noise in git commits caused by the conversion to an
all-static class.
(Btw the same problem applies to class-level readonly)To me, the best version of a class-level "static" keyword would be as
a contract, and nothing more:
- The class cannot be instantiated.
- It cannot have a constructor or destructor, or any magic methods
that only apply to objects.- It can only extend or be extended by other static classes.
(otherwise it should be declared "abstract" instead)The same could be achieved with a native base class or trait with
private constructor, or even the same thing from php-fig.
This would be less clear and less powerful than a static keyword, but
also less invasive to the language.(One such language feature could be a language-level base class or
trait, simply for the sake of unification)From this thread:
In particular: static classes are implied final and they cannot inherit
from any other class.With the version of the static keyword as above, I would be against
this limitation.
Static polymorphism (*) is possible today, and it should be perfectly
fine to slap a "static" keyword on these classes, if they are
all-static.
This is not about whether or not we like or dislike static
polymorphism, but about not attaching unrelated meaning to a keyword.
An explicit "static final class" is more obvious than "static class"
with implicit final.(*) The term "static polymorphism" is a bit questionable.
I obviously mean this:class B {
static function f() {
return static::g();
}
protected static function g() {
return null;
}
}class C extends B {
protected static function g() {
return 5;
}
}assert(C::f() === 5);
I have used this in the past, and I am now questioning my choices in
some of these cases.
Still I would not consider it an anti-pattern.--- Andreas
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: https://wiki.php.net/rfc/abstract_final_class
- Pre-vote discussion threads: https://externals.io/message/79211 and
https://externals.io/message/79338- Final vote thread: https://externals.io/message/79601
Searching my list archive, I find that it came up again a few months
ago, which I'd entirely forgotten: https://externals.io/message/121717Slightly 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/85779Regards,
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. :-)
--Larry Garfield
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
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: https://wiki.php.net/rfc/abstract_final_class
- Pre-vote discussion threads: https://externals.io/message/79211 and
https://externals.io/message/79338- Final vote thread: https://externals.io/message/79601
Searching my list archive, I find that it came up again a few months
ago, which I'd entirely forgotten: https://externals.io/message/121717Slightly 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/85779Regards,
Let's start getting specific so we can make some progress.
The goals of this RFC are fairly straightforward:
• Introduce thestatic
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:
- 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.
- Should
static
implyfinal
?
2a. If yes, shouldfinal
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.
- 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?
- 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).
- Should traits be permitted in a static class?
I hope so. Traits are some of the most abused super powers of PHP.
- 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:
- 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.- 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 replacingfinal
withstatic
in-place.- 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.
- 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.
- 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.
- 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,
BilgeI 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: https://wiki.php.net/rfc/abstract_final_class
- Pre-vote discussion threads: https://externals.io/message/79211 and https://externals.io/message/79338
- Final vote thread: https://externals.io/message/79601
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
Let's start getting specific so we can make some progress.
The goals of this RFC are fairly straightforward:
Introduce thestatic
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:
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...
- Since a "static class" implies all members are static, should we still allow explicit static method declarations with the
static
keyword?
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.
- Should
static
implyfinal
?
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.
- Should a "static class" prohibit inheritance?
Isn't #3 just a rewording of #2?
- Should a "static class" permit state?
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.
- Should traits be permitted in a static class?
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.
- Which magic methods (if any) should be valid in a static class?
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
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.
You wrote the perfect words. Even if I do not agree with all of your six
conclusions following, I agree with this preface 100%, to such an extent
that I think it should form the underpinning basis for all further
decisions on this RFC and be enshrined in the preamble of said RFC, if
indeed I ever acquire such permissions as are necessary to actually
write it.
The idea of "implicit" versus "explicit" static classes is a clear and
powerful one that I think we can all understand, such that a successful
RFC would collapse this distinction; any failure to do so would
undermine the value of the feature entirely. As a concrete example,
then, prohibiting state in a static class is now completely off the
table simply because regular classes already support this feature.
Whilst some people have talked about idealogical aspirations for this
feature which sound valid in a vacuum, we must primarily concern
ourselves with the fact that this feature is being introduced into an
existing language, not a brand new language where such ideals would have
merit. And that is why this statement is so powerful: most (but perhaps
not all) of the answers to these disputed questions follow naturally
from the clear and guiding principle of this preface.
Thank you so much, Mike, for this! I feel confident I can write this RFC
now, and with help from Lanre, we already have the beginnings of an
implementation, too!
Cheers,
Bilge
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.
You wrote the perfect words. Even if I do not agree with all of your
six conclusions following, I agree with this preface 100%, to such an
extent that I think it should form the underpinning basis for all
further decisions on this RFC and be enshrined in the preamble of said
RFC, if indeed I ever acquire such permissions as are necessary to
actually write it.The idea of "implicit" versus "explicit" static classes is a clear and
powerful one that I think we can all understand, such that a
successful RFC would collapse this distinction; any failure to do so
would undermine the value of the feature entirely. As a concrete
example, then, prohibiting state in a static class is now completely
off the table simply because regular classes already support this
feature. Whilst some people have talked about idealogical aspirations
for this feature which sound valid in a vacuum, we must primarily
concern ourselves with the fact that this feature is being introduced
into an existing language, not a brand new language where such ideals
would have merit. And that is why this statement is so powerful: most
(but perhaps not all) of the answers to these disputed questions
follow naturally from the clear and guiding principle of this preface.Thank you so much, Mike, for this! I feel confident I can write this
RFC now, and with help from Lanre, we already have the beginnings of
an implementation, too!Cheers,
Bilge
Furthermore, for absolute clarity and the avoidance of doubt, I believe
this guiding principle can be enshrined on the following single sentence:
"We cannot remove features from a static class that would otherwise
be present in a standard class."
I believe following this principle we will arrive at the best
implementation of static classes.
Cheers,
Bilge
Furthermore, for absolute clarity and the avoidance of doubt, I believe this guiding principle can be enshrined on the following single sentence:
"We cannot remove features from a static class that would otherwise be present in a standard class."
I believe following this principle we will arrive at the best implementation of static classes
A great principle, yes, but needing a small caveat:
"...excepting features that by-nature are not relevant to the static class use-case, such as instance-based magic methods like __get()
, __set()`` and
__construct()`."
That might have been implied, but good to make it explicit.
-Mike
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.
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
https://github.com/oplanre/php-src/tree/feature/static-class
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.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