[RFC] Static classes (Was Abstract final classes)

php.internals

Robert Stoll

11 years ago
Hi Guilherme, I read your updated RFC: https://wiki.php.net/rfc/abstract_final_class IMO the RFC as such makes sense now (abstract final confusion is eliminated. In addition to turn methods implicitly into static methods, the __construct of a static class should be implicitly private as well. I am not sure what makes more sense for PHP, but just as thought-provoking impulse, wouldn't it be better just to check that all methods are static and __construct private instead of turning methods magically into static methods and __construct private? And then there is one bit which I do not understand. What do you mean with: "There's no way of adding static variables to a namespace. This would address this issue too." How does this RFC address this problem? Cheers, Robert

Andrew Faulds

11 years ago
Hi all!
> On 1 Dec 2014, at 13:58, Robert Stoll <php@tutteli.ch> wrote: > > I read your updated RFC: > https://wiki.php.net/rfc/abstract_final_class
Hmm, I don’t like this new RFC either. Static classes in languages like C# exist because the language designers made the dogmatic decision to require everything to be a class. Thus, to make collections of functions, your only choice is to make a “utility class" with a bunch of static methods. To make this easier, the `static` keyword was added to make this easier in C# - Java doesn’t have this keyword, but it has the same pattern of static method-only classes. But PHP is not one of these dogmatic “everything must be a class” languages: it has true global functions which can be namespaced. So, why, then, does PHP need static classes? As I see it, there are two reasons: (1) Functions currently cannot be autoloaded (2) Encapsulation of state (private/protected static properties) I think both of these can be solved without perpetuating what I feel is an anti-pattern, the “utility class”. If they are solved, the need for utility classes goes away. To implement (1), someone just needs to go and finally implement function autoloading. I think Joe Watkins (krakjoe) might’ve said he’d write a patch for that, but I may be wrong. I’d certainly like to help with this effort. Implementing (2) is more difficult. We currently don’t have file-local variables like C has, we don’t even have namespaced variables. We do have static variables within functions, however. I can think of a few possible approaches to this: (1) Say that global state is evil and refuse to implement it. Some people (myself included, to an extent) would argue that global state is “spooky action at a distance” and we shouldn’t be encouraging it. In this case we encourage users to simply make normal, non-static/abstract classes and to pass an instance around. This is generally good programming practise anyway. (2) Add lexically-scoped variables, and allow normal global functions to be closures. By this I mean we add something like JavaScript’s `let` keyword that would make a variable that is unset when it falls out of scope (end of a {} block, end of a file etc.). Then, we allow normal functions to use the `use ($var)` syntax and close over variables. That’d look something like this: <?php let $x = 0; function getCounter() use(&$x) { return $x; } function incrementCounter() use(&$x) { return $x++; } // since this is the end of the file, $x falls out of scope I’d quite like this, as the introduction of lexically-scoped variables would have other advantages, too. They would make foreach() by-reference much less error-prone, for example: <?php foreach ($array as let &$item) { // do stuff with $item } // $item is unset here, since it fell out of scope when we left the {} block (3) Add file-local variables using the `static` keyword (à la C). That’d work something like this: <?php static $x = 0; function getCounter() { global $x; return $x; } // etc. This would work similarly to `let`, I suppose, it’d just only be useful for files. (4) Add namespace-local variables. No idea quite how this’d work. I’m not sure which of these is the best, though I’d leans towards (1) or (2). But I definitely feel that adding a `static` modifier for classes is solving the wrong problem. We don’t need to make utility classes easier to create. Rather, we need to solve the long-standing problems which force people to create utility classes in the first place. Thanks!
-- Andrea Faulds http://ajf.me/

Guilherme Blanco

11 years ago
Hi Andrea, Thanks a lot for putting efforts thinking through this problem. Indeed we have 3 problems that leads to "static classes". You detailed 2 of them. The third is around encapsulation. I may want functions that are reused by other functions at the namespace level, but that shouldn't be used outside of it. By purely supporting (1), you still didn't address this issue. That would be easily addressed by having private static functions inside of the static class. So, let me recap the 3 motivations: (1) Function/Namespaced function autoloading (2) State encapsulation (3) Function scoping Heading back to your drill down in (2), I feel that we're adding more complexity to something that can and it is at some extent be easily supported through classes. I actually thought about having "let" originally and using as "global", but I gave up on the idea due to patch's complexity and also because it didn't matter how far I was thinking through this, something was popping up my head telling it was wrong. I decided to share my idea with a friend which pointed me to HHVM's support and then I decided to work on that original patch. So, as you walk through your options, didn't it feel wrong to you? Static classes are an easy addiction to the language and would have a negligent performance impact, since it just adds 2 conditions in zend_vm and zend_api. Again, I wouldn't necessarily use this concept I'm proposing right out the bet, but I do see there are use cases. I plan to redo my patch and get back for review once I finalize. Cheers, On Mon, Dec 1, 2014 at 9:32 AM, Andrea Faulds <ajf@ajf.me> wrote:
> Hi all! > > > On 1 Dec 2014, at 13:58, Robert Stoll <php@tutteli.ch> wrote: > > > > I read your updated RFC: > > https://wiki.php.net/rfc/abstract_final_class > > Hmm, I don’t like this new RFC either. Static classes in languages like C# > exist because the language designers made the dogmatic decision to require > everything to be a class. Thus, to make collections of functions, your only > choice is to make a “utility class" with a bunch of static methods. To make > this easier, the `static` keyword was added to make this easier in C# - > Java doesn’t have this keyword, but it has the same pattern of static > method-only classes. But PHP is not one of these dogmatic “everything must > be a class” languages: it has true global functions which can be namespaced. > > So, why, then, does PHP need static classes? As I see it, there are two > reasons: > > (1) Functions currently cannot be autoloaded > > (2) Encapsulation of state (private/protected static properties) > > I think both of these can be solved without perpetuating what I feel is an > anti-pattern, the “utility class”. If they are solved, the need for utility > classes goes away. > > To implement (1), someone just needs to go and finally implement function > autoloading. I think Joe Watkins (krakjoe) might’ve said he’d write a patch > for that, but I may be wrong. I’d certainly like to help with this effort. > > Implementing (2) is more difficult. We currently don’t have file-local > variables like C has, we don’t even have namespaced variables. We do have > static variables within functions, however. I can think of a few possible > approaches to this: > > (1) Say that global state is evil and refuse to implement it. Some > people (myself included, to an extent) would argue that global state is > “spooky action at a distance” and we shouldn’t be encouraging it. In this > case we encourage users to simply make normal, non-static/abstract classes > and to pass an instance around. This is generally good programming practise > anyway. > > (2) Add lexically-scoped variables, and allow normal global functions to > be closures. By this I mean we add something like JavaScript’s `let` > keyword that would make a variable that is unset when it falls out of scope > (end of a {} block, end of a file etc.). Then, we allow normal functions to > use the `use ($var)` syntax and close over variables. That’d look something > like this: > > <?php > let $x = 0; > function getCounter() use(&$x) { > return $x; > } > function incrementCounter() use(&$x) { > return $x++; > } > // since this is the end of the file, $x falls out of scope > > I’d quite like this, as the introduction of lexically-scoped > variables would have other advantages, too. They would make foreach() > by-reference much less error-prone, for example: > > <?php > foreach ($array as let &$item) { > // do stuff with $item > } > // $item is unset here, since it fell out of scope when we left > the {} block > > (3) Add file-local variables using the `static` keyword (à la C). That’d > work something like this: > > <?php > static $x = 0; > function getCounter() { > global $x; > return $x; > } > // etc. > > This would work similarly to `let`, I suppose, it’d just only be > useful for files. > > (4) Add namespace-local variables. No idea quite how this’d work. > > I’m not sure which of these is the best, though I’d leans towards (1) or > (2). But I definitely feel that adding a `static` modifier for classes is > solving the wrong problem. We don’t need to make utility classes easier to > create. Rather, we need to solve the long-standing problems which force > people to create utility classes in the first place. > > Thanks! > -- > Andrea Faulds > http://ajf.me/ > > > > >
-- Guilherme Blanco MSN: guilhermeblanco@hotmail.com GTalk: guilhermeblanco Toronto - ON/Canada

Andrew Faulds

11 years ago
Hi!
> On 1 Dec 2014, at 15:27, guilhermeblanco@gmail.com wrote: > > Hi Andrea, > > Thanks a lot for putting efforts thinking through this problem. > Indeed we have 3 problems that leads to "static classes". You detailed 2 of them. > The third is around encapsulation. I may want functions that are reused by other functions at the namespace level, but that shouldn't be used outside of it. By purely supporting (1), you still didn't address this issue. That would be easily addressed by having private static functions inside of the static class.
Hmm. Well, having functions not to be used outside of the namespace/file isn’t a problem unique to classes. That would be useful for people writing normal functions. Having something like static functions would be great.
> So, as you walk through your options, didn't it feel wrong to you? Static classes are an easy addiction to the language and would have a negligent performance impact, since it just adds 2 conditions in zend_vm and zend_api.
Yes, they’re a simple addition, but I still think they’re the wrong solution to the problem. We shouldn’t force people to use classes for simple collections of functions. People have wanted function autoloading for years. I say it’s high time we add that. Thanks!
-- Andrea Faulds http://ajf.me/

Christoph Becker

11 years ago
Andrea Faulds wrote:
> Yes, they’re a simple addition, but I still think they’re the wrong > solution to the problem. We shouldn’t force people to use classes for > simple collections of functions. People have wanted function > autoloading for years. I say it’s high time we add that.
Maybe <https://wiki.php.net/rfc/function_autoloading> should be reopened?
-- Christoph M. Becker

Andrew Faulds

11 years ago
> On 1 Dec 2014, at 16:12, Christoph Becker <cmbecker69@gmx.de> wrote: > > Andrea Faulds wrote: > >> Yes, they’re a simple addition, but I still think they’re the wrong >> solution to the problem. We shouldn’t force people to use classes for >> simple collections of functions. People have wanted function >> autoloading for years. I say it’s high time we add that. > > Maybe <https://wiki.php.net/rfc/function_autoloading> should be reopened?
Perhaps. I did want to revive that RFC, but I disliked Anthony’s approach to implementing it (new set of functions, namespaced) and the patch wasn’t for phpng.
-- Andrea Faulds http://ajf.me/

Rowan Collins

11 years ago
guilhermeblanco@gmail.com wrote on 01/12/2014 15:27:
> (1) Function/Namespaced function autoloading > (2) State encapsulation > (3) Function scoping
I would add (4) static polymorphism, which Late Static Binding explicitly supports.
> But PHP is not one of these dogmatic “everything must be a class” languages: it has true global functions which can be namespaced.
By the same token, PHP is not one of those dogmatic languages which says that every class must have at least one instance. There may be advantages to giving your Factory class a constructor, but there is nothing in the language itself to stop you making it entirely out of static methods, and even sub-classing it from an abstract base using LSB. The only thing missing is a standardised, enforced, annotation on the class to declare that you are using this ability and instances will never be created. Regards,
-- Rowan Collins [IMSoP]

Levi Morrison

11 years ago
> The only thing missing is a standardised, enforced, annotation on the class > to declare that you are using this ability and instances will never be > created.
Let's say a class has no members: what's the harm in instantiating it? Sure, it logically doesn't do anything useful, but is this user error such a big deal that we alter the language to disallow it?

Rowan Collins

11 years ago
Levi Morrison wrote on 01/12/2014 17:58:
>> The only thing missing is a standardised, enforced, annotation on the class >> to declare that you are using this ability and instances will never be >> created. > Let's say a class has no members: what's the harm in instantiating it? > Sure, it logically doesn't do anything useful, but is this user error > such a big deal that we alter the language to disallow it?
The same could be said of many function and class modifiers - they exist as self-documenting, mostly compile-time, assertions. There may not be a compelling reason to add one, but "the user's self-discipline should be good enough" isn't a very compelling reason not to. I do think the keyword should trigger simple enforcement, though, not automagically change the behaviour. static class Foo { function bar() { echo do_something_with($this); } } Much nicer (and just as easy) for that to give a compile-time error of "Static class Foo cannot contain non-static function bar" than for it to autoload fine, and then give a less obvious error when Foo::bar() is run (probably to do with a non-existent variable, since $this is always undefined inside a static function). Programmers, and automated tools, won't know about the "static class" annotation immediately, but hopefully already know what "static function" means. Note that this is the same as with "abstract function" vs "abstract class", only the other way around - adding an abstract function doesn't implicitly convert a class to be abstract, it refuses to compile if you don't explicitly do so. Regards,
-- Rowan Collins [IMSoP]

Stas Malyshev

11 years ago
Hi!
> static class Foo { > function bar() { > echo do_something_with($this); > } > } > > Much nicer (and just as easy) for that to give a compile-time error of > "Static class Foo cannot contain non-static function bar" than for it to > autoload fine, and then give a less obvious error when Foo::bar() is run > (probably to do with a non-existent variable, since $this is always > undefined inside a static function).
This example is fit the narrow purpose of being the example, but you could have as easily written "static function bar" and be back to square one. I'm not sure how language-enforced "all methods must be static" is really useful - if you don't want to instantiate it, put a private ctor or make it abstract. If you want static methods, just make them static. It won't help you anyway if you use $this in static methods.
-- Stas Malyshev smalyshev@gmail.com

Patrick Schaaf

11 years ago
Am 01.12.2014 17:37 schrieb "Rowan Collins" <rowan.collins@gmail.com>:
> > guilhermeblanco@gmail.com wrote on 01/12/2014 15:27: > >> (1) Function/Namespaced function autoloading >> (2) State encapsulation >> (3) Function scoping
> I would add (4) static polymorphism, which Late Static Binding explicitly
supports. Amen! In several places in our codebase, I make use of per-request constants by having an autoloaded baseclass file which uses class_alias for specialization, like this: // in some/class.inc class some_class__base { ... common methods and default implementation } if (class_exists('some_class_'.SOME_CONSTANT)) { class_alias('some_class', 'some_class_'.SOME_CONSTANT); } else { class_alias('some_class', 'some_class__base'); } // in some/class/special.inc class some_class_special extends some_class_base { .... specialization ... } Calling code simply uses some_class::foo() without caring whether the concrete script run then uses the specialized class, or the base class.
> The only thing missing is a standardised, enforced, annotation on the
class to declare that you are using this ability and instances will never be created. I that regard, I'm quite happy with private function __construct, where desired. best regards Patrick

Stas Malyshev

11 years ago
Hi!
> (2) Add lexically-scoped variables, and allow normal global functions > to be closures. By this I mean we add something like JavaScript’s > `let` keyword that would make a variable that is unset when it falls > out of scope (end of a {} block, end of a file etc.). Then, we allow > normal functions to use the `use ($var)` syntax and close over > variables. That’d look something like this: > > <?php > let $x = 0; > function getCounter() use(&$x) { > return $x; > } > function incrementCounter() use(&$x) { > return $x++; > } > // since this is the end of the file, $x falls out of scope >
This looks like you're trying to create a class without calling it a class. Now, I agree that not everything should be a class. But what you're doing - set of functions linked by common behavior and sharing a common state - is a perfect fit for something classes and objects are used in PHP. So why not just use them instead of inventing creative ways of doing the same but in more complicated way? Moreover, since your example is clearly not a singleton - it is hard to imagine any program that would have use for just one counter but never any more - it is a classic example of something that should be an object. It might be a wrong example, but I have hard time seeing any example of something that has a group of functions, bound by common functionality, has a shared state and still should not and can not be expressed as a class/object. That's literally what they are made for. Now, utility classes do suck, but they suck not because they do what you described - on the contrary, they suck exactly because they are usually a bag of random, unrelated functions bound by nothing but the common trait of "we don't have other places to put them in". But for that, plain old functions work just fine in PHP.
-- Stas Malyshev smalyshev@gmail.com

Andrew Faulds

11 years ago
> On 1 Dec 2014, at 17:46, Stanislav Malyshev <smalyshev@gmail.com> wrote: > > Hi! > >> (2) Add lexically-scoped variables, and allow normal global functions >> to be closures. By this I mean we add something like JavaScript’s >> `let` keyword that would make a variable that is unset when it falls >> out of scope (end of a {} block, end of a file etc.). Then, we allow >> normal functions to use the `use ($var)` syntax and close over >> variables. That’d look something like this: >> >> <?php >> let $x = 0; >> function getCounter() use(&$x) { >> return $x; >> } >> function incrementCounter() use(&$x) { >> return $x++; >> } >> // since this is the end of the file, $x falls out of scope >>
> Moreover, since your example is clearly not a singleton - it is hard to > imagine any program that would have use for just one counter but never > any more - it is a classic example of something that should be an > object.
That was just a frivolous example to show how the syntax works, it’s not a sensible example of why you’d want this functionality.
> This looks like you're trying to create a class without calling it a > class. Now, I agree that not everything should be a class. But what > you're doing - set of functions linked by common behavior and sharing a > common state - is a perfect fit for something classes and objects are > used in PHP. So why not just use them instead of inventing creative ways > of doing the same but in more complicated way?
> It might be a wrong example, but I have hard time seeing any > example of something that has a group of functions, bound by common > functionality, has a shared state and still should not and can not be > expressed as a class/object. That's literally what they are made for.
> Now, utility classes do suck, but they suck not because they do what you > described - on the contrary, they suck exactly because they are usually > a bag of random, unrelated functions bound by nothing but the common > trait of "we don't have other places to put them in". But for that, > plain old functions work just fine in PHP.
Yeah, I see your point. For encapsulated state with associated functions to work with that state, a class is the way to go. For miscellaneous functions with no shared state, they should just be functions, not in a class. However, I still don’t think `static` is a good idea. Adding function autoloading eliminates one use case - a bag of random, unrelated functions. But it leaves the other: a set of functions to operate on global state. The problem is that, well, global state is rarely a good thing, I don’t think we should be encouraging it. If people really want to make a singleton or a final class with a private constructor, be my guest, but I don’t think we should make it any easier for them.
-- Andrea Faulds http://ajf.me/

Rowan Collins

11 years ago
On 1 December 2014 20:30:46 GMT, Andrea Faulds <ajf@ajf.me> wrote:
> > The problem is that, well, global state is rarely a good thing, I don’t think we should be encouraging it.
To get a bit philosophical: Is it really the language's job to make that decision? Is there a difference between "encouraging", "allowing", and "enabling"? Purely static classes are a reality whether this feature is added or not. They do things namespaces can't (and probably shouldn't), but are simpler to work with than singletons (which they closely resemble) or full-blown Dependency Injection. Declaring a static class is just a way of acknowledging that this is a deliberate decision, and instance members should not be added. Regards,
-- Rowan Collins [IMSoP]

Guilherme Blanco

11 years ago
PR with this feature request: https://github.com/php/php-src/pull/929 On Mon, Dec 1, 2014 at 4:56 PM, Rowan Collins <rowan.collins@gmail.com> wrote:
> On 1 December 2014 20:30:46 GMT, Andrea Faulds <ajf@ajf.me> wrote: > > > > The problem is that, well, global state is rarely a good thing, I don’t > think we should be encouraging it. > > To get a bit philosophical: Is it really the language's job to make that > decision? Is there a difference between "encouraging", "allowing", and > "enabling"? > > Purely static classes are a reality whether this feature is added or not. > They do things namespaces can't (and probably shouldn't), but are simpler > to work with than singletons (which they closely resemble) or full-blown > Dependency Injection. Declaring a static class is just a way of > acknowledging that this is a deliberate decision, and instance members > should not be added. > > Regards, > -- > Rowan Collins > [IMSoP] > > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
-- Guilherme Blanco MSN: guilhermeblanco@hotmail.com GTalk: guilhermeblanco Toronto - ON/Canada

Levi Morrison

11 years ago
Perhaps I have missed it in the noise, but have any other mainstream languages (or new hipster ones; I don't care) have something equivalent to the proposed static classes?

Guilherme Blanco

11 years ago
Yes, C# documented here: http://msdn.microsoft.com/en-us/library/79b3xss3.aspx On Mon, Dec 1, 2014 at 5:41 PM, Levi Morrison <levim@php.net> wrote:
> Perhaps I have missed it in the noise, but have any other mainstream > languages (or new hipster ones; I don't care) have something > equivalent to the proposed static classes? > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
-- Guilherme Blanco MSN: guilhermeblanco@hotmail.com GTalk: guilhermeblanco Toronto - ON/Canada

Stas Malyshev

11 years ago
Hi!
> To get a bit philosophical: Is it really the language's job to make > that decision? Is there a difference between "encouraging", > "allowing", and "enabling"?
I think, yes on both. The PHP tradition is to allow doing stuff that may look weird (most languages won't allow you to do $$foo) but still there are practices that we encourage, especially in OO space, and some even that we enforce quite strictly (such as inheritance rules for methods).
> Purely static classes are a reality whether this feature is added or > not. They do things namespaces can't (and probably shouldn't), but
Of course, you can create class containing of only static members. The question is whether we need a language construct for ensuring that certain class has only static members. I'm not sure this requires a language-level construct.
-- Stas Malyshev smalyshev@gmail.com

Lars Strojny

11 years ago
Hi Guilherme, hi Robert.
> On 01 Dec 2014, at 14:58, Robert Stoll <php@tutteli.ch> wrote: > > I am not sure what makes more sense for PHP, but just as thought-provoking impulse, wouldn't it be better just to check that all methods are static and __construct private instead of turning methods magically into static methods and __construct private?
I very much like the RFC in its current state except for the implicit static. While we can preserve the behaviour of automatically turning methods into static methods, this should indeed trigger a warning. cu, Lars

Guilherme Blanco

11 years ago
I'm working on a patch right now and I removed the implicit configuration of static to methods. It's only missing reflection magic. Should be out of the oven in one hour or less. On Mon, Dec 1, 2014 at 2:44 PM, Lars Strojny <lars@strojny.net> wrote:
> Hi Guilherme, hi Robert. > > > On 01 Dec 2014, at 14:58, Robert Stoll <php@tutteli.ch> wrote: > > > > I am not sure what makes more sense for PHP, but just as > thought-provoking impulse, wouldn't it be better just to check that all > methods are static and __construct private instead of turning methods > magically into static methods and __construct private? > > I very much like the RFC in its current state except for the implicit > static. While we can preserve the behaviour of automatically turning > methods into static methods, this should indeed trigger a warning. > > cu, > Lars
-- Guilherme Blanco MSN: guilhermeblanco@hotmail.com GTalk: guilhermeblanco Toronto - ON/Canada