[PHP-DEV] [RFC] Static class

Hi Internals!

I am pleased to present my first RFC: Static class <PHP: rfc:static_class.

This work is based on the previous discussion thread on this list of the same name, and hopefully captured all the relevant details, notwithstanding anything unanticipated that may present itself during the implementation. Let me know if you feel anything important has been missed. I am aware it omits to mention specifics about messages so emitted when runtime or compile-time errors occur, but I anticipate this can be hashed out in the PR, unless consensus indicates otherwise.

I am aware this idea is not supported by everyone, but there seemed to be enough positive voices for it to be worth a shot. I'd like to get a better idea of where people might stand when it comes down to a vote, now there is a formal RFC, so we know whether it's worth completing the implementation, although any sentiments so proffered are of course not a commitment to vote any particular way and nobody should feel compelled to speak to that unless comfortable. Looking forward to feedback!

Cheers,
Bilge

On 24/06/2024 00:29, Larry Garfield wrote:

On the point of getting feedback on where people will vote now there's an RFC, I can only say with all honesty "Good luck." :slight_smile: This is by far the biggest challenge of making an RFC: the lack of a meaningful sense of how something will be received before the actual vote. Welcome to the club.

For the record, as previously stated, I will be voting No on this RFC.

--Larry Garfield

Thanks, Larry. I appreciate that, and on that point, I appreciate you being clear where you stand.

Cheers,
Bilge.

On Sun, Jun 23, 2024, at 6:10 PM, Bilge wrote:

Hi Internals!

I am pleased to present my first RFC: Static class
<PHP: rfc:static_class.

This work is based on the previous discussion thread on this list of the
same name, and hopefully captured all the relevant details,
notwithstanding anything unanticipated that may present itself during
the implementation. Let me know if you feel anything important has been
missed. I am aware it omits to mention specifics about messages so
emitted when runtime or compile-time errors occur, but I anticipate this
can be hashed out in the PR, unless consensus indicates otherwise.

I am aware this idea is not supported by everyone, but there seemed to
be enough positive voices for it to be worth a shot. I'd like to get a
better idea of where people might stand when it comes down to a vote,
now there is a formal RFC, so we know whether it's worth completing the
implementation, although any sentiments so proffered are of course not a
commitment to vote any particular way and nobody should feel compelled
to speak to that unless comfortable. Looking forward to feedback!

Cheers,
Bilge

On the point of getting feedback on where people will vote now there's an RFC, I can only say with all honesty "Good luck." :slight_smile: This is by far the biggest challenge of making an RFC: the lack of a meaningful sense of how something will be received before the actual vote. Welcome to the club.

For the record, as previously stated, I will be voting No on this RFC.

--Larry Garfield

On Mon, Jun 24, 2024, 02:15 Bilge <bilge@scriptfusion.com> wrote:

Hi Internals!

I am pleased to present my first RFC: Static class
<https://wiki.php.net/rfc/static_class>.

Hi! and good luck with the RFC.

While I don’t use static classes, I can see how this can be used by others.

I worked in big projects that used too many libraries, and when those libraries have functions.php file, they pile up and loading the composer autoloader can take a small dent on performance of each request.
Having functions grouped by a class that can autoloader instead of a file is an advantage that should be spelled out as an use case.

Feedback:

  1. I agree that static classes may extend other non-static classes (that might have some static behavior).
    But I think that static classes should be extended only by other static classes, or at least for the first iteration of these feature.
    This should be documented more in the RFC, no matter how it ends up being.
    1.1 I think using a static class as a type should be considered an error, as there will never be instances of it if we consider all child classes will also be static classes.
    Of course, this cannot be an error on php side given the class per file loading separation.

  2. __set_state() is strongly linked to exporting a class instance as a valid php string, and is used to reconstruct the class instance upon execution.
    Considering we forbid instances, we should not support __set_state(). If you think otherwise, please share an example how it can be used.

Regards,
Alex

To be clear, they must have static members, not just . This is an interesting debate, and something we may end up doing. I am currently undecided if it would be better to enforce full static class hierarchy, but I could definitely be so convinced. This is a very interesting point that I had not considered. I agree, it should be correct to forbid type hinting static classes. However, if I understood you correctly, we cannot actually implement that? My knowledge of engine internals is too weak to know the correct answer here. If it is possible, I think we should do it.

···

On 24/06/2024 07:59, Alexandru Pătrănescu wrote:

On Mon, Jun 24, 2024, 02:15 Bilge <bilge@scriptfusion.com> wrote:

Hi Internals!

I am pleased to present my first RFC: Static class
<https://wiki.php.net/rfc/static_class>.

Hi! and good luck with the RFC.

Hi Alex, and thanks for your excellent feedback!

While I don’t use static classes, I can see how this can be used by others.

It is my sincere hope that everyone will vote with similar empathy.

Feedback:

  1. I agree that static classes may extend other non-static classes (that might have some static behavior).

exclusively**some

But I think that static classes should be extended only by other static classes, or at least for the first iteration of these feature.
This should be documented more in the RFC, no matter how it ends up being.

1.1 I think using a static class as a type should be considered an error, as there will never be instances of it if we consider all child classes will also be static classes.
Of course, this cannot be an error on php side given the class per file loading separation.

  1. __set_state() is strongly linked to exporting a class instance as a valid php string, and is used to reconstruct the class instance upon execution.
    Considering we forbid instances, we should not support __set_state(). If you think otherwise, please share an example how it can be used.

Thank you for catching this. This is strictly a mistake on my part. I have never used, or even heard of, __set_state() in my life. I was merely perusing the magic methods to make sure I caught them all and it seemed, at a glance, this might apply to static classes, but as you point out, if this indeed requires an instance then it does not apply. I will remove this from the next version of the RFC, which I shall publish imminently.

Many thanks,
Bilge

This work is based on the previous discussion thread on this list of the
same name, and hopefully captured all the relevant details,
notwithstanding anything unanticipated that may present itself during
the implementation. Let me know if you feel anything important has been
missed. I am aware it omits to mention specifics about messages so
emitted when runtime or compile-time errors occur, but I anticipate this
can be hashed out in the PR, unless consensus indicates otherwise.

Hi Bilge,
I was reading the mailing list discussions, and I am glad you created the RFC.

Personally, I think this will be a useful feature. Just like we have
`readonly` properties and `readonly` classes, I don't see why we can't
have static methods. I also saw in the draft PR that it does not
require a lot of changes to the engine to have this.

I'd like to propose to add some more information and points addressed
in the RFC. This is of course merely a suggestion, but it's something
I was thinking when reading the RFC.

- Why is it a class-level flag and not an attribute (similar to the
`#[Override]` attribute in PHP 8.3) ?
- Can a subclass extend a static parent class without using the
`static` keyword in the subclass? This will avoid a lot of BC issues
if a library decides to declare classes as static.
- Are there any opt-out mechanisms for subclasses?
- Can you mark interfaces, abstract classes, and Enums as static?

Le 24 juin 2024 à 01:10, Bilge <bilge@scriptfusion.com> a écrit :

Hi Internals!

I am pleased to present my first RFC: Static class <PHP: rfc:static_class.

This work is based on the previous discussion thread on this list of the same name, and hopefully captured all the relevant details, notwithstanding anything unanticipated that may present itself during the implementation. Let me know if you feel anything important has been missed. I am aware it omits to mention specifics about messages so emitted when runtime or compile-time errors occur, but I anticipate this can be hashed out in the PR, unless consensus indicates otherwise.

I am aware this idea is not supported by everyone, but there seemed to be enough positive voices for it to be worth a shot. I'd like to get a better idea of where people might stand when it comes down to a vote, now there is a formal RFC, so we know whether it's worth completing the implementation, although any sentiments so proffered are of course not a commitment to vote any particular way and nobody should feel compelled to speak to that unless comfortable. Looking forward to feedback!

Cheers,
Bilge

Hi,

A general remark: I appreciate that a static class does not impose arbitrary restrictions (like: implicitly `final`) on the class beyond what is meaningful for staticness. On the other side, I don’t think that we should support markers that are technically possible, but semantically meaningless, like the `readonly` marker on class.

-----

Some more specific remarks:

* In the intro: “A static class is a class whose members (properties and methods) are all static”. One of the most important point is missing, namely: It is meaningless for a static class to have instances.

* In the “Proposal” section, it should be stated explicitly that any attempt to construct an instance, not only with `new`, but also with any of the `ReflectionClass::newInstance*()` methods, or with `unserialize()`, or with whatever other mean, shall fail.

-----

Should a static class be marked `readonly` or `abstract`? I think not, because those have no real semantic meaning for static class; their effects on static members are only consequences of their intended meaning on non-static class:

* Unless/until the `readonly` marker may be applied to static properties, the only effect of such a keyword, is that it would prevent the creation of static properties. I don’t know if that restriction is useful, but in case it would be used for that purpose, it would be hijacking the `readonly` marker for a something it wasn’t intended for.

* The main purpose of the `abstract` keyword is to prevent a class to be instantiated, which (in case of static class) is more semantically described by the `static` marker. Beyond that, it just allows to declare a method that, if implemented by a subclass, should have a compatible signature. Most notably, it does not prevent the other static members of the class to be used directly.

----

The RFC says that a static class may extend a class not explicitly marked as static, but with no instance member. This is not sound, because a class with no instance members is not necessarily static. The most obvious example is `stdClass` (which has no member at all, even if their instances may have properties).

—Claude

I am pleased to present my first RFC: Static class

To recap my opinion from the other thread:
I generally support the idea to have a label to slap on all-static
classes, to make that contract more explicit.
The goal is not to promote a specific use of the language, but to add
clarity to what already exists.

Since my last response on the other thread, I warmed up more to
namespaced floating functions, thanks Larry :slight_smile:
At the same time I don't see static methods and all-static classes
fully going away soon, so I still support them having a label.
But...

Why is it a class-level flag and not an attribute (similar to the `#[Override]` attribute in PHP 8.3) ?

I don't know that it needs to be a language keyword.
An attribute would be ok for me, or even just a doc tag with phpstan.
In fact maybe a feature request with phpstan could be a starting point.

One question our razor cannot answer is whether static member declarations still require the static keyword in a static class. Marking a class readonly makes marking properties with the same optional, thus one might argue static should follow suit. However, I opine it should not. Unlike readonly, which typically applies to properties declared near the head of the class (by convention) where the same mark is also applied, static members appear throughout the full length of the class body. That is, it should be apparent when jumping anywhere into a static class that its members are static, without having to check the class declaration.

I prefer that the static keyword be required on the members.
This supports the common use case of incremental refactoring, where
methods are moved from one class to another, or a git merge where one
branch adds the class-level static keyword, while another commit adds
another method.

We cannot remove features from a static class that would otherwise be present in a standard class.

I think I get the intent of this statement, but I don't really vibe
with the wording.
I would say we should limit the scope of a modifier keyword to its essence.
And we should not remove features that are already being used in
classes that would naturally qualify for the class-level static
modifier.

On 2024-06-24 01:10, Bilge wrote:

I am pleased to present my first RFC: Static class <PHP: rfc:static_class.

Just one point of feedback: for clarity I think it would be good if it mentions that creating dynamic properties i.e. $this->dynamicProp = 'bar' would also throw at runtime (or even compile time if doable?).

As for voting intention, it sounds totally reasonable to me, and while I don't see myself using it much I don't see a reason to be against it, so :+1:t2:

Best

Jordi Boggiano
@seldaek - https://seld.be

Hi Claude! I really appreciate your feedback. Everything you highlighted is an important point that will be included in the RFC! Great point, readonly and static should be mutually exclusive and generate a compile-time error, since readonly static properties are not supported. I thought that was covered off, but I’ll see if I can word it better. Great point, I’ll include that detail. Agree, as above, we’ll make them mutually exclusive. I tend to find inheritance is not a very useful concept in static contexts, but others have already pointed out that some have found uses for it. Due to my lack of experience I cannot confidently say that abstract static has no value, but you make a compelling argument. Happy to add a similar mutual exclusivity prohibition for this keyword too, unless and until someone protests it with an equally compelling argument to the contrary.

···

On 24/06/2024 09:27, Claude Pache wrote:

Hi,
A general remark: I appreciate that a static class does not impose arbitrary restrictions (like: implicitly `final`) on the class beyond what is meaningful for staticness. On the other side, I don’t think that we should support markers that are technically possible, but semantically meaningless, like the `readonly` marker on class.
Some more specific remarks:

* In the intro: “A static class is a class whose members (properties and methods) are all static”. One of the most important point is missing, namely: It is meaningless for a static class to have instances.
* In the “Proposal” section, it should be stated explicitly that any attempt to construct an instance, not only with `new`, but also with any of the `ReflectionClass::newInstance*()` methods, or with `unserialize()`, or with whatever other mean, shall fail.

I think we’re also missing a similar detail with respect to dynamic properties, which should of course also be forbidden.

Should a static class be marked `readonly` or `abstract`? I think not, because those have no real semantic meaning for static class; their effects on static members are only consequences of their intended meaning on non-static class:

* Unless/until the `readonly` marker may be applied to static properties, the only effect of such a keyword, is that it would prevent the creation of static properties. I don’t know if that restriction is useful, but in case it would be used for that purpose, it would be hijacking the `readonly` marker for a something it wasn’t intended for.
* The main purpose of the `abstract` keyword is to prevent a class to be instantiated, which (in case of static class) is more semantically described by the `static` marker. Beyond that, it just allows to declare a method that, if implemented by a subclass, should have a compatible signature. Most notably, it does not prevent the other static members of the class to be used directly.
The RFC says that a static class may extend a class not explicitly marked as static, but with no instance member. This is not sound, because a class with no instance members is not necessarily static. The most obvious example is `stdClass` (which has no member at all, even if their instances may have properties).

Do you mean it is not simply sufficient for a class to be regarded as implicitly static by virtue of the fact that it has no instance members? I’m not sure I agree, but I may be missing something. If we extend stdClass then we gain nothing, and our class so extending it can still be safely marked static, can it not? Please elaborate so I might understand better.

Kind regards,
Bilge

Hi Jordi,

On 24/06/2024 13:22, Jordi Boggiano wrote:

On 2024-06-24 01:10, Bilge wrote:

I am pleased to present my first RFC: Static class <PHP: rfc:static_class.

Just one point of feedback: for clarity I think it would be good if it mentions that creating dynamic properties i.e. $this->dynamicProp = 'bar' would also throw at runtime (or even compile time if doable?).

Absolutely. I'll include a note about this in the RFC.

As for voting intention, it sounds totally reasonable to me, and while I don't see myself using it much I don't see a reason to be against it, so :+1:t2:

Thank you! :pray:t2:

Kind regards,
Bilge

P.S. Thanks for Composer :slightly_smiling_face:

Hi again Jordi,

On 24/06/2024 21:21, Bilge wrote:

Hi Jordi,

On 24/06/2024 13:22, Jordi Boggiano wrote:

On 2024-06-24 01:10, Bilge wrote:

I am pleased to present my first RFC: Static class <PHP: rfc:static_class.

Just one point of feedback: for clarity I think it would be good if it mentions that creating dynamic properties i.e. $this->dynamicProp = 'bar' would also throw at runtime (or even compile time if doable?).

Absolutely. I'll include a note about this in the RFC.

On second thoughts, I don't think dynamic properties can apply at all. The notion of `$this` is a reference to an object instance; static classes cannot be instantiated, so the concept of dynamic properties simply cannot exist, and that's really all there is to it :sweat_smile: (unless I'm missing something).

Cheers,
Bilge

On Mon, Jun 24, 2024 at 10:22 PM Bilge <bilge@scriptfusion.com> wrote:

On 24/06/2024 09:27, Claude Pache wrote:

Hi,

Hi Claude! I really appreciate your feedback. Everything you highlighted is an important point that will be included in the RFC!

A general remark: I appreciate that a static class does not impose arbitrary restrictions (like: implicitly `final`) on the class beyond what is meaningful for staticness. On the other side, I don’t think that we should support markers that are technically possible, but semantically meaningless, like the `readonly` marker on class.

Great point, `readonly` and `static` should be mutually exclusive and generate a compile-time error, since `readonly static` properties are not supported.

Some more specific remarks:

* In the intro: “A static class is a class whose members (properties and methods) are all static”. One of the most important point is missing, namely: It is meaningless for a static class to have instances.

I thought that was covered off, but I'll see if I can word it better.

* In the “Proposal” section, it should be stated explicitly that any attempt to construct an instance, not only with `new`, but also with any of the `ReflectionClass::newInstance*()` methods, or with `unserialize()`, or with whatever other mean, shall fail.

Great point, I'll include that detail. I think we're also missing a similar detail with respect to dynamic properties, which should of course also be forbidden.

Should a static class be marked `readonly` or `abstract`? I think not, because those have no real semantic meaning for static class; their effects on static members are only consequences of their intended meaning on non-static class:

* Unless/until the `readonly` marker may be applied to static properties, the only effect of such a keyword, is that it would prevent the creation of static properties. I don’t know if that restriction is useful, but in case it would be used for that purpose, it would be hijacking the `readonly` marker for a something it wasn’t intended for.

Agree, as above, we'll make them mutually exclusive.

* The main purpose of the `abstract` keyword is to prevent a class to be instantiated, which (in case of static class) is more semantically described by the `static` marker. Beyond that, it just allows to declare a method that, if implemented by a subclass, should have a compatible signature. Most notably, it does not prevent the other static members of the class to be used directly.

I tend to find inheritance is not a very useful concept in static contexts, but others have already pointed out that some have found uses for it. Due to my lack of experience I cannot confidently say that `abstract static` has no value, but you make a compelling argument. Happy to add a similar mutual exclusivity prohibition for this keyword too, unless and until someone protests it with an equally compelling argument to the contrary.

The RFC says that a static class may extend a class not explicitly marked as static, but with no instance member. This is not sound, because a class with no instance members is not necessarily static. The most obvious example is `stdClass` (which has no member at all, even if their instances may have properties).

Do you mean it is not simply sufficient for a class to be regarded as implicitly static by virtue of the fact that it has no instance members? I'm not sure I agree, but I may be missing something. If we extend `stdClass` then we gain nothing, and our class so extending it can still be safely marked static, can it not? Please elaborate so I might understand better.

Kind regards,
Bilge

Hey Bilge,

Do you mean it is not simply sufficient for a class to be regarded as implicitly static by virtue of the fact that it has no instance members? I'm not sure I agree, but I may be missing something. If we extend `stdClass` then we gain nothing, and our class so extending it can still be safely marked static, can it not? Please elaborate so I might understand better.

I think it is sound in theory, but in practice, it will be a foot gun.
Imagine if you were to take my HTTP code library and make something
static from it. Later, I added an instance member. Now, when you
upgrade, everything starts crashing because of one little change
unrelated to your code.

On 24/06/2024 22:56, Alex Wells wrote:

Hey Bilge,

I'm not usually a resident of these discussions, but it seems like this RFC is heading into a wrong direction. Let me explain: as it currently stands, static properties and methods live separately from instance properties and methods. That means a couple of limitations, namely: a static class member can never implement an interface's contract, and hence a static class could never be used as an instance; static class members have a separate reflection API; static class members cannot have expression initializers; there's no static constructor; and so on. Adding `static classes` would not solve any of the above issues, and they would still be barely useful.

To counter these issues, Kotlin has a concept of `data objects`: <Object expressions and declarations | Kotlin Documentation.

Hi Alex,

If you wish to implement data objects, what part of the current proposal precludes you from doing so?

Cheers,
Bilge

Hey Bilge,

I’m not usually a resident of these discussions, but it seems like this RFC is heading into a wrong direction. Let me explain: as it currently stands, static properties and methods live separately from instance properties and methods. That means a couple of limitations, namely: a static class member can never implement an interface’s contract, and hence a static class could never be used as an instance; static class members have a separate reflection API; static class members cannot have expression initializers; there’s no static constructor; and so on. Adding static classes would not solve any of the above issues, and they would still be barely useful.

To counter these issues, Kotlin has a concept of data objects: <https://kotlinlang.org/docs/object-declarations.html#data-objects>. They look like regular classes, but only one instance of a data object is created - when it’s first accessed by other code. The closest alternative in PHP would be an enum with a single case. Unlike static classes, data objects don’t have any of the problems associated with static members.

They can be passed around as an instance of a class. This allows implementing interfaces, and replacing a data object with a regular class instance without having to change all member access from static to instance. They would also be able to inherit the syntax and semantics of regular class constructors:

object Converter {
private array $map = new Map();

public function __construct() {
$this->map['miles to km'] = 1.6;
}

public function convert(string $type, float $value): float {
return $value * $this->map[$type];
}
}

doSomeConversion(Converter::object);

// If `Converter` is ever to become a regular class, none of the related code would require changes.
function doSomeConversion(Converter $converter) {
$result = $someUtils->convert('miles to km', 123);
}

That would be functionally equivalent to:

class Converter {
private static self $instance;

public static function instance(): self {
return self::$instance ??= new self();
}

public function convert(float $something): float {}
}

doSomeConversion(Converter::instance());

Of course, this is already possible, as shown in the second example. But so is this RFC. Implementing data objects would actually bring them on-par with regular classes, and likely cause less drama overall.

On Mon, Jun 24, 2024 at 2:14 AM Bilge <bilge@scriptfusion.com> wrote:

Hi Internals!

I am pleased to present my first RFC: Static class
<https://wiki.php.net/rfc/static_class>.

This work is based on the previous discussion thread on this list of the
same name, and hopefully captured all the relevant details,
notwithstanding anything unanticipated that may present itself during
the implementation. Let me know if you feel anything important has been
missed. I am aware it omits to mention specifics about messages so
emitted when runtime or compile-time errors occur, but I anticipate this
can be hashed out in the PR, unless consensus indicates otherwise.

I am aware this idea is not supported by everyone, but there seemed to
be enough positive voices for it to be worth a shot. I’d like to get a
better idea of where people might stand when it comes down to a vote,
now there is a formal RFC, so we know whether it’s worth completing the
implementation, although any sentiments so proffered are of course not a
commitment to vote any particular way and nobody should feel compelled
to speak to that unless comfortable. Looking forward to feedback!

Cheers,
Bilge

On Tue, Jun 25, 2024 at 1:56 AM Bilge <bilge@scriptfusion.com> wrote:

Hi Alex,

If you wish to implement data objects, what part of the current proposal
precludes you from doing so?

None, but they both try to solve the same problem, so it’s highly unlikely that data objects would ever be considered if this RFC is accepted. And while static classes fit better with lots of existing PHP code, data classes (or a similar proposal) would bring more benefit to the table in the long run, which is why I was hoping for an alternative solution to be considered and/or discussed. I’m not planning to pursue the data objects proposal further, but it was still worth mentioning so the internals are aware of the alternatives to this RFC.

On Jun 24, 2024, at 3:53 AM, Ayesh Karunaratne <ayesh@php.watch> wrote:

  • Why is it a class-level flag and not an attribute (similar to the
    #[Override] attribute in PHP 8.3) ?

From my perspective that would create much confusion among every PHP developer who has not committed to memory when to use static as a class keyword and when to use it as an attribute.

Given the concept already exists in keywords I would strongly argue that it makes no sense to introduce as an attributes in core PHP.

Attributes are great for concepts new to PHP, IMO, but not for existing concepts already part of PHP implemented with keywords.

On Jun 24, 2024, at 4:27 AM, Claude Pache <claude.pache@gmail.com> wrote:

  • The main purpose of the abstract keyword is to prevent a class to be instantiated, which (in case of static class) is more semantically described by the static marker. Beyond that, it just allows to declare a method that, if implemented by a subclass, should have a compatible signature. Most notably, it does not prevent the other static members of the class to be used directly.

Given a primary purpose for being able to declare a class static is to signal intent, disallowing abstract static classes seems at odds with that goal.

Of course it is currently true that static methods of abstract classes can be called from outside a class and its class hierarchy, so if we allow declaring abstract static classes then it would never in the future be possible to lock down calls of static methods to those abstract classes.

So IMO it would be better to disallow calling static methods from outside a declared abstract static class and its inheritance hierarchy as part of this RFC. That would be backward compatible since there are currently no classes that are declared in that manner. Doing otherwise would force those who want to declare a class as both static and abstract to have to make a choice rather than being able to signal their full intent. Which brings us back to the “implied” vs. “explicitly declared” bifurcation I mentioned in a prior email.

BTW, I am assuming it is technically possible to disallow calling methods for classes declared both abstract and static without considerable difficulty in implementation and without creating a significant drain on performance.

-Mike

P.S. I would argue the same for readonly static properties, but as that seems those would require an involved discussion about the exact rules to implement I am demurring on that topic.

On 25 Jun 2024, at 09:22, Mike Schinkel mike@newclarity.net wrote:

On Jun 24, 2024, at 3:53 AM, Ayesh Karunaratne <ayesh@php.watch> wrote:

  • Why is it a class-level flag and not an attribute (similar to the
    #[Override] attribute in PHP 8.3) ?

From my perspective that would create much confusion among every PHP developer who has not committed to memory when to use static as a class keyword and when to use it as an attribute.

Given the concept already exists in keywords I would strongly argue that it makes no sense to introduce as an attributes in core PHP.

Attributes are great for concepts new to PHP, IMO, but not for existing concepts already part of PHP implemented with keywords.

On Jun 24, 2024, at 4:27 AM, Claude Pache <claude.pache@gmail.com> wrote:

  • The main purpose of the abstract keyword is to prevent a class to be instantiated, which (in case of static class) is more semantically described by the static marker. Beyond that, it just allows to declare a method that, if implemented by a subclass, should have a compatible signature. Most notably, it does not prevent the other static members of the class to be used directly.

Given a primary purpose for being able to declare a class static is to signal intent, disallowing abstract static classes seems at odds with that goal.

Of course it is currently true that static methods of abstract classes can be called from outside a class and its class hierarchy, so if we allow declaring abstract static classes then it would never in the future be possible to lock down calls of static methods to those abstract classes.

So IMO it would be better to disallow calling static methods from outside a declared abstract static class and its inheritance hierarchy as part of this RFC. That would be backward compatible since there are currently no classes that are declared in that manner. Doing otherwise would force those who want to declare a class as both static and abstract to have to make a choice rather than being able to signal their full intent. Which brings us back to the “implied” vs. “explicitly declared” bifurcation I mentioned in a prior email.

BTW, I am assuming it is technically possible to disallow calling methods for classes declared both abstract and static without considerable difficulty in implementation and without creating a significant drain on performance.

-Mike

P.S. I would argue the same for readonly static properties, but as that seems those would require an involved discussion about the exact rules to implement I am demurring on that topic.

Hi Mike,

So IMO it would be better to disallow calling static methods from outside a declared abstract static class

Can you explain what you mean by this, or more specifically the “why”. What (implied wrong/bad) scenario are you trying to prevent, by disallowing a static method on an abstract class to be called?

As you point out, it’s already possible to call (public) static methods on abstract classes, the same as it’s possible to call (public) static methods without instantiating a regular class.

So why should static abstract classes be different? If the static method shouldn’t be called publicly, it shouldn’t be public. Are you suggesting that public static methods shouldn’t be callable on any abstract class? If so, this sounds like a huge BC break, and if not, it sounds like a confusing “if-then-but-why” scenario.

I agree that the static keyword is a much better fit here, however there is one other aspect here that may come into it (as much as I prefer the keyword approach): the Attribute approach is backwards compatible (with a polyfill) to let code using this feature also run on previous PHP releases. Given that this is mostly intended as a way to signal intent about a pattern that’s already in use, this may be significant for library authors.

Personally (as a library author) I don’t think that ability is worth the weirdness of an attribute vs a keyword, but it may be more important for others who are voting, and I’d rather have the feature with slightly odd syntax rather than not at all.

Cheers

Stephen

On Tue, Jun 25, 2024 at 1:00 AM Alex Wells <autaut03@gmail.com> wrote:

a static class member can never implement an interface’s contract

Well, PHP is a weird one here compared to other languages

Interfaces can contain contracts for static methods and they are checked just like usual non-static methods.
Example: https://3v4l.org/8cuXW

With late static binding, you can define static abstract methods and basically do code reuse through inheritance.
Example: https://3v4l.org/pid7W

I’m not saying I would ever use such a thing, just that they exist and others might find them useful so why build limits around it?
A limit is just extra code to maintain and make sure you use it consistently.

I believe that a static class should have the principle of no allowed instance and only static methods, without any impact on inheritance:

  • There can be a static interface and a static abstract class
  • A static class can extend a non-static class, or implement an interface or abstract class as long as it does not require to implement a non-static method.
  • A static class can be extended only by another static class
  • As static interface can be extended only by a static class

Basically, when a static class gets into the inheritance chain, all the extending classes will be static and there is a guarantee that there will never be an instance of that class.

Making sure a static class does just that, limiting the methods and properties in it to only be static, will keep things simpler going forward when thinking about interactions with other features.
Like readonly static class would not work right now, but it will work if readonly will be implemented for static properties, and that should be doable effortless.

Alex

What is the of abstract static? How is such intent different from just static? When I first saw the proposal to use an attribute instead of the keyword, I thought it absurd, but this idea has now been entertained by three people, and for the first time I have seen a compelling argument in favour of (early adoption). I must admit, I was too quick to judge this one. I had not considered that libraries will still not be able to use the static modifier with classes unless and until they drop support for PHP < 8.4, which may take a while! Of course, it is still of real benefit to first-party proprietary projects whom have upgraded. Nevertheless, the allure of early adoption is curious, and made me wonder whether we could do both, just to support early adoption in a backwards-compatible manner. However, this would be unprecedented and most likely not accepted; never before has the same feature been implemented two ways just to appease early adopters. I think the best compromise would be, for anyone so eager, to implement such support in community tools, e.g. PHPStan. That is, it should be perfectly possible to enforce the core semantics of the static class feature with a userland attribute and the necessary logic in static analysis tools, provided the library is well behaved and doesn’t do anything too weird with variable variables, references, reflection or unserialize() to deliberately circumvent the restriction.

···

Hi guys,

On 25/06/2024 04:17, Stephen Reay wrote:

Given a primary purpose for being able to declare a class static is to signal intent, disallowing abstract static classes seems at odds with that goal.

intent

I agree that the static keyword is a much better fit here, however there is one other aspect here that may come into it (as much as I prefer the keyword approach): the Attribute approach is backwards compatible (with a polyfill) to let code using this feature also run on previous PHP releases. Given that this is mostly intended as a way to signal intent about a pattern that’s already in use, this may be significant for library authors.

Personally (as a library author) I don’t think that ability is worth the weirdness of an attribute vs a keyword, but it may be more important for others who are voting, and I’d rather have the feature with slightly odd syntax rather than not at all.

Cheers,
Bilge