[PHP-DEV] [RFC] Static class

Hi,

What is the of abstract static? How is such intent different from just static?

Sorry I should have been clearer. It isn’t that abstract represents an intent, it’s that adding static is mostly about conveying intent - and thus there’s no technical reason that any existing class with only static members can’t be marked as a static class, so why introduce an artificial barrier if that class happens to already be abstract?

It’s already possible to have a class with only static members, that is abstract, and call the public static methods of it. It’s also possible to have a class that partially implements an interface that has static methods on it. Unless your RFC is going to propose preventing calling static methods on all abstract classes it’s a needless complication when there’s no technical reason to do so.

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.

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.

Yes it should be possible to use a user land attribute or phpdoc tag or what have, but that’s already true. You’ve essentially just made a case against your own RFC - that it can mostly be done already in userland.

The entire appeal of a builtin attribute would be that it’s something you could apply now (as in today, June 2024) and know that it won’t break anything, while having some {compile,run} time benefits in later versions (e.g. enforcing static-only, and inability to instantiate).

Like I said, I much prefer the keyword syntax - but I also recognise that others may have different priorities in terms of supporting older language versions, which is why I think that aspect is worth consideration (perhaps a secondary vote, or an informal vote to gauge consensus?), because too many good RFCs get rejected over small details.

Cheers,
Bilge

Cheers

Stephen

···

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

On Sun, 23 Jun 2024, Larry Garfield wrote:

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!

<snip>

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

Having read this thread, and the previous one from half a year ago, I
will do so too. In short, we shouldn't be encouraging static classes as
a bag of static functions, that ought to be just namespaced functions.

cheers,
Derick

On Jun 25, 2024, at 9:17 AM, Derick Rethans derick@php.net wrote:

Having read this thread, and the previous one from half a year ago, I
will do so too. In short, we shouldn’t be encouraging static classes as
a bag of static functions, that ought to be just namespaced functions.

Which brings us back to the age-old problem that functions can’t be autoloaded. Me, I want first-class modules, but until we have those, I have to settle for classes in the meantime. Scala/Kotlin-like “companion objects” might be a good all-round substitute though.

Cheers,
chuck

On Jun 24, 2024, at 11:17 PM, Stephen Reay <php-lists@koalephant.com> wrote:

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.

Thank you for the question.

I was not specifically advocating static methods to be disallowed to be called on an abstract class. I was instead addressing the desire by another list member to have classes declared static not also be able to be declared abstract. That person made the argument that if 'abstract static` did not actually have any effect then it should not be allowed by the RFC.

What affect are we talking about? If it is both abstract and static then it would seem to me the only effect such a declaration could have would be to disallow the calling of static methods using the named abstract static class, e.g.:

abstract static class Base {
static public foo() {
echo “foo() called\n”
}
}

static class Concrete extends Base {}

Concrete::foo(); // works
Base::foo(); // fails

My only real argument for disallowing static method calls on abstract static classes is that if we didn’t do so initially we would never be able to disallow because of BC.

In summary, my argument was really only about ensuring we allow “abstract static” declaration so developers can signal intent and also to address the objections of the other list member. But if given the option, I think it would be good to disallow calling static methods on abstract static classes if only to reserve the right to allow in the future vs. closing that door immediately. Still, that latter is not the hill for me to die on.

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.

Well, as previously stated, I think that would be confusing.

Though I do see your reasoning for wanting it to be an attribute. However…

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.

I too am not convinced that the ongoing confusion would be better than the temporary need to support older versions of PHP with the same source file.

Given that a build step could address the syntax there are certainly other new language features that require developers to bifurcate their libraries to support different versions, why make this one unique?

That said, it could be a voting option for the RFC?

-Mike

On Jun 25, 2024, at 4:03 AM, Bilge <bilge@scriptfusion.com> wrote:
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.

And that is probably the best solution I've heard regarding this quandary.

It could potentially apply with other keywords in the future as well.

#fwiw

-Mike

On 26 Jun 2024, at 12:51, Mike Schinkel mike@newclarity.net wrote:

On Jun 24, 2024, at 11:17 PM, Stephen Reay <php-lists@koalephant.com> wrote:

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.

Thank you for the question.

I was not specifically advocating static methods to be disallowed to be called on an abstract class. I was instead addressing the desire by another list member to have classes declared static not also be able to be declared abstract. That person made the argument that if 'abstract static` did not actually have any effect then it should not be allowed by the RFC.

What affect are we talking about? If it is both abstract and static then it would seem to me the only effect such a declaration could have would be to disallow the calling of static methods using the named abstract static class, e.g.:

abstract static class Base {
static public foo() {
echo “foo() called\n”
}
}

static class Concrete extends Base {}

Concrete::foo(); // works
Base::foo(); // fails

My only real argument for disallowing static method calls on abstract static classes is that if we didn’t do so initially we would never be able to disallow because of BC.

In summary, my argument was really only about ensuring we allow “abstract static” declaration so developers can signal intent and also to address the objections of the other list member. But if given the option, I think it would be good to disallow calling static methods on abstract static classes if only to reserve the right to allow in the future vs. closing that door immediately. Still, that latter is not the hill for me to die on.

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.

Well, as previously stated, I think that would be confusing.

Though I do see your reasoning for wanting it to be an attribute. However…

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.

I too am not convinced that the ongoing confusion would be better than the temporary need to support older versions of PHP with the same source file.

Given that a build step could address the syntax there are certainly other new language features that require developers to bifurcate their libraries to support different versions, why make this one unique?

That said, it could be a voting option for the RFC?

-Mike

Hi Mike,

This is an example of code that works today (and all the way back to 5.0): https://3v4l.org/4EKo2

The class hierarchy embody the type of classes this RFC is about: only static members, no instantiation.

The implemented methods can be called statically, regardless of whether the class they’re implemented in is abstract or not. The abstract methods cannot be called directly.

So these classes would be a candidate for the static class keyword (or Attribute) - except they can’t, if calls to implemented methods on abstract classes are disallowed. Because the Base::a() method has been publicly callable, for potentially as long as 20 years next month.

My point here is that if someone wants to prohibit calling public static methods on abstract classes with the static keyword, that’s going be inconsistent with how it’s worked for the last 20 years (i.e. on classes that were ‘static’ in intent but not syntactically), or if it applies the change everywhere it’s going to be a BC break.

Re: attribute vs keyword,

Like I said - I’m not personally in favour of the attribute approach; I’m simply making the point (playing devils advocate if you will) that by the very nature of how attributes work, they’re more friendly to supporting older versions of the language with a single code base. If the general consensus is that it isn’t a concern, great. But this wouldn’t be the first time we’ve seen a relatively minor syntax choice derail an otherwise useful RFC.

Cheers

Stephen

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).

Yeah sorry that is indeed not needed as it already works that way Online PHP editor | output for TYIiV

It is not compile time though, but that's a separate concern.

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

In JavaScript/Typescript, we can declare functions that are local to the current file by not exporting them. In C/C++ this is achieved by simply writing the function in the implementation file as opposed to the header. However, achieving a similar level of privacy in PHP requires using private static methods, or else you incur the overhead of creating an object for stateless functions. How do you propose handling such cases using namespaced functions? Can you think of any scenarios where hiding non-API functions might be necessary? I’m curious why you keep returning to namespaced functions when they don’t fulfill the same role.

Cheers,
Lanre

On Tue, Jun 25, 2024 at 9:20 AM Derick Rethans <derick@php.net> wrote:

On Sun, 23 Jun 2024, Larry Garfield wrote:

On Sun, Jun 23, 2024, at 6:10 PM, Bilge 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!

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

Having read this thread, and the previous one from half a year ago, I
will do so too. In short, we shouldn’t be encouraging static classes as
a bag of static functions, that ought to be just namespaced functions.

cheers,
Derick

On Wed, Jun 26, 2024, at 6:05 PM, Lanre wrote:

In JavaScript/Typescript, we can declare functions that are local to
the current file by not exporting them. In C/C++ this is achieved by
simply writing the function in the implementation file as opposed to
the header. However, achieving a similar level of privacy in PHP
requires using private static methods, or else you incur the overhead
of creating an object for stateless functions. How do you propose
handling such cases using namespaced functions? Can you think of any
scenarios where hiding non-API functions might be necessary? I'm
curious why you keep returning to namespaced functions when they don't
fulfill the same role.

Cheers,
Lanre

Please don't top-post.

PHP has no concept of packages at present, and thus no concept of package-level scope. A private static method is private not to a file or package, but to that class. (The class and file usually correlate in practice, but there's nothing in the language that mandates that.) I would love to have a concept of packages that we could use for scope, as most more recent languages have concluded that is the correct level at which to enforce visibility, NOT objects/classes. That's an entirely separate question, however.

For marking a function as "package private", the same way you would for a package-private class today: @internal docblock. It's not ideal (the ideal would require packages), but it still communicates the intent, has worked for classes for 15 years at least, and signals "if you use this class/function/thing and it breaks later, that's on you, don't come crying to me."

(ab)using static classes to emulate a pseudo-single-class-package is... abusing static.

To be blunt, 90% of uses of static methods in PHP are a sign the developer doesn't actually understand OOP and is just writing procedural code with more steps. The other 10% are either named constructors or cases that can now be replaced with attributes.

--Larry Garfield

On 25/06/2024 16:17, Derick Rethans wrote:

we shouldn't be encouraging static classes as
a bag of static functions, that ought to be just namespaced functions.

I understand this is the prevailing preference of a certain few, including (but not limited to) yourself and Larry. Nevertheless, classes as a bag of static functions is a phenomenon that exists and will continue to exist in PHP until such a time as `static` is completely removed from the language, so I don't really understand the avoidance of adding, in the interim, what seems like just a missing part of an existing feature.

However, I still want to understand why a file of functions is strictly better than a class of functions. What are the benefits of a file over a class?

For me, I dislike importing individual functions. It is often the case that, when such logic is so grouped in a static class, where we use one we may use several such functions. In such a case, we only need a single import statement instead of one for each function. This seems like a clear benefit. Even in the case that I only use one, I still prefer to import a class over a function, because it has better support in my editor and doesn't stick out like a sore thumb in the list of imports (why should it?).

Cheers, Bilge

That isn’t a top post, look again.And I am not referring to packages,rather to the ability to define functions that aren’t exposed to the users of a library. C doesn’t have packages either yet this is possible in it. And marking it as internal is not the same thing at all. I am not going to argue about why a developer would use static methods and whether that is actually a sign that the developer doesn’t understand OOP. But I do want to get this straight. Instead of actually hiding the function, your idea is to mark it with @internal (via PHPDoc)? To you, that solves the problem?

On Wed, Jun 26, 2024 at 1:46 PM Larry Garfield <larry@garfieldtech.com> wrote:

On Wed, Jun 26, 2024, at 6:05 PM, Lanre wrote:

In JavaScript/Typescript, we can declare functions that are local to
the current file by not exporting them. In C/C++ this is achieved by
simply writing the function in the implementation file as opposed to
the header. However, achieving a similar level of privacy in PHP
requires using private static methods, or else you incur the overhead
of creating an object for stateless functions. How do you propose
handling such cases using namespaced functions? Can you think of any
scenarios where hiding non-API functions might be necessary? I’m
curious why you keep returning to namespaced functions when they don’t
fulfill the same role.

Cheers,
Lanre

Please don’t top-post.

PHP has no concept of packages at present, and thus no concept of package-level scope. A private static method is private not to a file or package, but to that class. (The class and file usually correlate in practice, but there’s nothing in the language that mandates that.) I would love to have a concept of packages that we could use for scope, as most more recent languages have concluded that is the correct level at which to enforce visibility, NOT objects/classes. That’s an entirely separate question, however.

For marking a function as “package private”, the same way you would for a package-private class today: @internal docblock. It’s not ideal (the ideal would require packages), but it still communicates the intent, has worked for classes for 15 years at least, and signals “if you use this class/function/thing and it breaks later, that’s on you, don’t come crying to me.”

(ab)using static classes to emulate a pseudo-single-class-package is… abusing static.

To be blunt, 90% of uses of static methods in PHP are a sign the developer doesn’t actually understand OOP and is just writing procedural code with more steps. The other 10% are either named constructors or cases that can now be replaced with attributes.

–Larry Garfield

Also I specifically said “we can declare functions that are local to the current file by not exporting them.”, Where did you get “package private” from?

On Wed, Jun 26, 2024 at 1:46 PM Larry Garfield <larry@garfieldtech.com> wrote:

On Wed, Jun 26, 2024, at 6:05 PM, Lanre wrote:

In JavaScript/Typescript, we can declare functions that are local to
the current file by not exporting them. In C/C++ this is achieved by
simply writing the function in the implementation file as opposed to
the header. However, achieving a similar level of privacy in PHP
requires using private static methods, or else you incur the overhead
of creating an object for stateless functions. How do you propose
handling such cases using namespaced functions? Can you think of any
scenarios where hiding non-API functions might be necessary? I’m
curious why you keep returning to namespaced functions when they don’t
fulfill the same role.

Cheers,
Lanre

Please don’t top-post.

PHP has no concept of packages at present, and thus no concept of package-level scope. A private static method is private not to a file or package, but to that class. (The class and file usually correlate in practice, but there’s nothing in the language that mandates that.) I would love to have a concept of packages that we could use for scope, as most more recent languages have concluded that is the correct level at which to enforce visibility, NOT objects/classes. That’s an entirely separate question, however.

For marking a function as “package private”, the same way you would for a package-private class today: @internal docblock. It’s not ideal (the ideal would require packages), but it still communicates the intent, has worked for classes for 15 years at least, and signals “if you use this class/function/thing and it breaks later, that’s on you, don’t come crying to me.”

(ab)using static classes to emulate a pseudo-single-class-package is… abusing static.

To be blunt, 90% of uses of static methods in PHP are a sign the developer doesn’t actually understand OOP and is just writing procedural code with more steps. The other 10% are either named constructors or cases that can now be replaced with attributes.

–Larry Garfield

On Wed, 26 Jun 2024, Lanre wrote:

That isn't a top post, look again.

It was.

If people point out you're doing something against this list's
guidelines, don't reply with "it wasn't" in a snarky way.

cheers,
Derick

On Tue, 25 Jun 2024, Chuck Adams wrote:

> On Jun 25, 2024, at 9:17 AM, Derick Rethans <derick@php.net> wrote:
>
> Having read this thread, and the previous one from half a year ago,
> I will do so too. In short, we shouldn't be encouraging static
> classes as a bag of static functions, that ought to be just
> namespaced functions.

Which brings us back to the age-old problem that functions can’t be
autoloaded. Me, I want first-class modules, but until we have those, I
have to settle for classes in the meantime. Scala/Kotlin-like
“companion objects” might be a good all-round substitute though.

There has been some work done on function autoloading too:
https://wiki.php.net/rfc/core-autoloading

cheers,
Derick

On Wed, Jun 26, 2024 at 5:52 PM Derick Rethans <derick@php.net> wrote:

On Tue, 25 Jun 2024, Chuck Adams wrote:

On Jun 25, 2024, at 9:17 AM, Derick Rethans <derick@php.net> wrote:

Having read this thread, and the previous one from half a year ago,
I will do so too. In short, we shouldn’t be encouraging static
classes as a bag of static functions, that ought to be just
namespaced functions.

Which brings us back to the age-old problem that functions can’t be
autoloaded. Me, I want first-class modules, but until we have those, I
have to settle for classes in the meantime. Scala/Kotlin-like
“companion objects” might be a good all-round substitute though.

There has been some work done on function autoloading too:
https://wiki.php.net/rfc/core-autoloading

cheers,
Derick

My bad, I thought “top post” meant the same thing in mailing lists as it does in forums. I am now aware of my mistake and it won’t happen again. Can we address my actual points now?

Lanre

Hi Stephen,

On Jun 26, 2024, at 4:26 AM, Stephen Reay <php-lists@koalephant.com> wrote:

This is an example of code that works today (and all the way back to 5.0): https://3v4l.org/4EKo2

The class hierarchy embody the type of classes this RFC is about: only static members, no instantiation.

The implemented methods can be called statically, regardless of whether the class they’re implemented in is abstract or not. The abstract methods cannot be called directly.

So these classes would be a candidate for the static class keyword (or Attribute) - except they can’t, if calls to implemented methods on abstract classes are disallowed. Because the Base::a() method has been publicly callable, for potentially as long as 20 years next month.

My point here is that if someone wants to prohibit calling public static methods on abstract classes with the static keyword, that’s going be inconsistent with how it’s worked for the last 20 years (i.e. on classes that were ‘static’ in intent but not syntactically), or if it applies the change everywhere it’s going to be a BC break.

It seems what you are asking about is downstream from the reason I stated that I made the argument in the first place, which AFAICT you did not acknowledge. That leaves me unsure of your position on a precursor topic to your above stated objections.

As stated, my primary (initial) reason for arguing that abstract static should disallow calling static methods using the class name of the static class marked abstract — as my earlier example of calling Base::foo() illustrated— was in response to the argument against allowingabstractwithstatic` because — in Claude’s words — it would “have no real semantic meaning for static class; their effects on static members are only consequences of their intended meaning on non-static class.” I have copied his complete words on that topic below:

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

Should a static class be marked 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:

  • 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.

Thus my argument supporting abstract static was addressing his objections by suggesting that we could have an abstract static class declaration disallow calling any of the static methods of the class using the name of the class declared abstract given that it would in fact not cause any BC breaks with untouched code.

Before you repeat your objections to disallowing, permit me to ask you which of the following upstream arguments you are making? Is your position that we should:

  1. Disallow abstract on static as Claude argued?
  2. Allow abstract static in spite of Claude’s argument against it?

Also, if you answer “2. Allow abstract static” then please how do you address Claude’s objections?

Once I am clear of your position on two above I will address your objections stated in your most recent response to me, which I quoted at the beginning of this email. Thank you in advance for the clarity.

-Mike

On Jun 26, 2024, at 5:49 PM, Derick Rethans <derick@php.net> wrote:

There has been some work done on function autoloading too:
PHP: rfc:core-autoloading

Fantastic! I still want modules and/or companion objects of course... I wonder whether the autoloading machinery could be bent to such a purpose, or am I smoking the bad stuff again?

—c

Hi Larry,

On Jun 26, 2024, at 2:21 PM, Larry Garfield <larry@garfieldtech.com> wrote:

PHP has no concept of packages at present, and thus no concept of package-level scope.

Not entirely true.

PHP has static classes, which are stand-ins for packages for as long as PHP does not inherently support the concept of packages.

Arguing “that is abusing static” without offering an alternative is just Puritanistic logic that ignores pragmatic real world needs.

For marking a function as “package private”, the same way you would for a package-private class today: @internal docblock. It’s not ideal (the ideal would require packages), but it still communicates the intent, has worked for classes for 15 years at least, and signals “if you use this class/function/thing and it breaks later, that’s on you, don’t come crying to me.”

Not only is @internal docblock not ideal, it sets up those internal properties to be infected with usage as explained by Hyrum’s Law[1]. Thus providing @internal docblock as if it were a viable alternative to package private is essentially a non-serious argument.

PHP has no concept of packages at present, and thus no concept of package-level scope. A private static method is private not to a file or package, but to that class. (The class and file usually correlate in practice, but there’s nothing in the language that mandates that.) I would love to have a concept of packages that we could use for scope, as most more recent languages have concluded that is the correct level at which to enforce visibility, NOT objects/classes. That’s an entirely separate question, however.

If you really want people to stop “abusing static” then drive to address that entirely separate question. Otherwise let others who are more pragmatic give the language features that they need.

As a quick aside, I would argue that PHP should look at its biggest warts — which to me include but are not limited to autoloading and namespaces — and deprecate them to be replaced with a proper package system that leverages a symbol compiler instead of autoloading and that ejects the god-awful choice of using the escape character for package name separation.

But I digress.

To be blunt, 90% of uses of static methods in PHP are a sign the developer doesn’t actually understand OOP and is just writing procedural code with more steps. The other 10% are either named constructors or cases that can now be replaced with attributes.

Speaking of best practices, there is a growing contingent of software developers who feel that OOP should not be the future of software[2], e.g. that it was “a good idea at the time” but now no longer.

My point here is that PHP should not let entrenched dogma drive language design but instead pragmatically evolve as other languages are also evolving.

-Mike

[1] https://www.laws-of-software.com/laws/hyrum/
[2] https://news.ycombinator.com/item?id=8676872

On 27 Jun 2024, at 10:11, Mike Schinkel mike@newclarity.net wrote:

Hi Stephen,

On Jun 26, 2024, at 4:26 AM, Stephen Reay <php-lists@koalephant.com> wrote:

This is an example of code that works today (and all the way back to 5.0): https://3v4l.org/4EKo2

The class hierarchy embody the type of classes this RFC is about: only static members, no instantiation.

The implemented methods can be called statically, regardless of whether the class they’re implemented in is abstract or not. The abstract methods cannot be called directly.

So these classes would be a candidate for the static class keyword (or Attribute) - except they can’t, if calls to implemented methods on abstract classes are disallowed. Because the Base::a() method has been publicly callable, for potentially as long as 20 years next month.

My point here is that if someone wants to prohibit calling public static methods on abstract classes with the static keyword, that’s going be inconsistent with how it’s worked for the last 20 years (i.e. on classes that were ‘static’ in intent but not syntactically), or if it applies the change everywhere it’s going to be a BC break.

It seems what you are asking about is downstream from the reason I stated that I made the argument in the first place, which AFAICT you did not acknowledge. That leaves me unsure of your position on a precursor topic to your above stated objections.

As stated, my primary (initial) reason for arguing that abstract static should disallow calling static methods using the class name of the static class marked abstract — as my earlier example of calling Base::foo() illustrated— was in response to the argument against allowingabstractwithstatic` because — in Claude’s words — it would “have no real semantic meaning for static class; their effects on static members are only consequences of their intended meaning on non-static class.” I have copied his complete words on that topic below:

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

Should a static class be marked 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:

  • 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.

Thus my argument supporting abstract static was addressing his objections by suggesting that we could have an abstract static class declaration disallow calling any of the static methods of the class using the name of the class declared abstract given that it would in fact not cause any BC breaks with untouched code.

Before you repeat your objections to disallowing, permit me to ask you which of the following upstream arguments you are making? Is your position that we should:

  1. Disallow abstract on static as Claude argued?
  2. Allow abstract static in spite of Claude’s argument against it?

Also, if you answer “2. Allow abstract static” then please how do you address Claude’s objections?

Once I am clear of your position on two above I will address your objections stated in your most recent response to me, which I quoted at the beginning of this email. Thank you in advance for the clarity.

-Mike

Hi Mike,

To answer your question: I believe abstract static should be allowed, because the “objection” mis-characterises a particular aspect of them as an unintended consequence, when there’s evidence to show that’s not that case.

Claude essentially dismisses the use of abstract static methods:

only consequences of their intended meaning on non-static class

In v5.2 a strict standards notice was added regarding the use of abstract static methods in classes. No notice was ever shown when they’re used in interfaces. In v7 this notice was removed (via https://wiki.php.net/rfc/reclassify_e_strict#abstract_static_methods) because, as Nikita noted at the time:

We currently allow the use of abstract static functions in interfaces, as such it is inconsistent to not allow them as abstract methods. By using late static binding a method in the abstract class can reasonably depend on the existence of a static method in a superclass.

That to me says this is an intended feature, and not an unintended consequence.

Cheers

Stephen

On Jun 27, 2024, at 12:09 AM, Stephen Reay <php-lists@koalephant.com> wrote:

Hi Mike,

To answer your question: I believe abstract static should be allowed, because the “objection” mis-characterises a particular aspect of them as an unintended consequence, when there’s evidence to show that’s not that case.

Claude essentially dismisses the use of abstract static methods:

only consequences of their intended meaning on non-static class

In v5.2 a strict standards notice was added regarding the use of abstract static methods in classes. No notice was ever shown when they’re used in interfaces. In v7 this notice was removed (via https://wiki.php.net/rfc/reclassify_e_strict#abstract_static_methods) because, as Nikita noted at the time:

Thank you for elaborating.

We are on the same page here as I too think abstract static should be allowed when declaring a class

We currently allow the use of abstract static functions in interfaces, as such it is inconsistent to not allow them as abstract methods. By using late static binding a method in the abstract class can reasonably depend on the existence of a static method in a superclass.

That to me says this is an intended feature, and not an unintended consequence.

Before I address this and your other comments, please understand that I do not see this as a huge issue either way, but I do want my argument to be understood. If my argument on this does not win the day, I will not lament it in any way.

As an aside, I had never seen interfaces used that way and found it surprising. Obviously I missed when Nikita made that claim.

As for “an intended feature, and not an unintended consequence,” when I was in university they drilled the idea of a sunken cost into me to the point I am a true believer. IOW, make the best decision moving forward vs. doubling down on bad decisions.

Note that I am not saying Nikita’s was a bad decision (or a good one) just that the existence of a prior decision should not color making the best decision moving forward.

I am curious, do you know of real-world userland code that is actually configured as your example that would be negatively affected by disallowing static methods calls?

On Jun 26, 2024, at 4:26 AM, Stephen Reay <php-lists@koalephant.com> wrote:

This is an example of code that works today (and all the way back to 5.0): https://3v4l.org/4EKo2

The class hierarchy embody the type of classes this RFC is about: only static members, no instantiation.

The implemented methods can be called statically, regardless of whether the class they’re implemented in is abstract or not. The abstract methods cannot be called directly.

Understood, but I do not see how interfaces or instantiation or abstract method are relevant to the discussion. They all seem orthogonal to me.

So these classes would be a candidate for the static class keyword (or Attribute) - except they can’t, if calls to implemented methods on abstract classes are disallowed. Because the Base::a() method has been publicly callable, for potentially as long as 20 years next month.

While it may be true that it has been that way, disallowing static methods from being called using abstract static classes would not be a BC break because it was previously impossible to declare a class as static let alone abstract static.

Further, there are often new features with constraints that result in developer not being “to do what they have been able to do for 20 years” because, looking forward, those constraints make more sense than not having those constraints.

Take a look at readonly properties. They must be typed, but I could have used the same argument against that saying “We’ve always been able to have untyped properties so readonly should not have to be typed.”[1] I’d say requiring them to be typed was a win, though, regardless of past property capabilities.

I’m not claiming necessarily that disallowing calling static methods on a abstract static class is a best practice, but I am saying that “we’ve been able to do it with similar syntax for a long time” is not a particularly compelling argument if disallowing is a better approach.

As another aside, assuming everyone agrees on what a best practice is for a given case, I doubt anyone would object to a constraint that forced developers to follow that best practice vs. allowing them to write less ideal code.

My point here is that if someone wants to prohibit calling public static methods on abstract classes with the static keyword, that’s going be inconsistent with how it’s worked for the last 20 years (i.e. on classes that were ‘static’ in intent but not syntactically),

Back to sunken cost, does it make more sense to only give developers who want to disallow using static methods for their abstract static classes access to @internal docblock — which I argued against elsewhere in this thread — and let Hyrum’s Law take over, OR do we enable them to disallow calling those static methods from the abstract class itself?

More importantly, IF we do not immediately disallow calling static methods on abstract static classes then we will never be able to disallow in the future, because of BC. But on the flip side we could later open them up for calling if we found the limitation to be problematic.

or if it applies the change everywhere it’s going to be a BC break.

And to be clear, I am against BC breaks in almost all cases. So bringing that up between us is moot.

All that said, if you tell me "I don’t care about closing the door on being able to disallow developers from calling static methods on abstract static methods because prior to abstract static classes static methods could be called on abstract classes in the past" I will respect that, and as I said above, it won’t bother me.

My only reason for persisting is to ensure that the argument I was making was understood before it was dismissed.

-Mike

[1] Ignoring there were technical issues with allowing untyped to be readonly, which could have been gotten around somehow if the consensus was “They must be able to untyped.”