[RFC] [Draft] Final constants

php.internals

Máté Kocsis

5 years ago
Hi Internals, I've recently realized that class constant values cannot always be trusted when late static binding is involved (e.g. static::FOO or $this::FOO), since they can be freely overridden in child classes. That's why the engine knows neither the type and the value before run-time. Doctrine coding standards has a specific rule which disallows referencing a possibly overridden class constant ( https://github.com/doctrine/coding-standard/blob/8d75920bb04a9be4c73c2a775ee5766262cf2a0d/lib/Doctrine/ruleset.xml#L123 ), but I'm wondering if it would be better to simply make it possible to allow declaring class constants final (thus disallowing overriding) instead? In my opinion, final constants would come with two advantages: - make class constant declarations paranoidly safe - allow the engine to optimize class constants references in LSB context Although I admit that the win is not extra huge, I do think that it would still be useful to have this feature, especially considering that the implementation is very straightforward ( https://github.com/php/php-src/pull/6878). So that's why I'm proposing this as an RFC. Recently, a similar attempt to make class constant declarations safer was the Typed Constants RFC (https://externals.io/message/110755#110755), but unfortunately it has halted since then. I think final and typed constants would be a nice little addition, and would complement each other well. Regards, Máté

Nikita Popov

5 years ago
On Sun, Apr 18, 2021 at 4:20 PM Máté Kocsis <kocsismate90@gmail.com> wrote:
> Hi Internals, > > I've recently realized that class constant values cannot always be trusted > when late static binding is involved (e.g. static::FOO or $this::FOO), > since they can be freely overridden in child classes. That's why the engine > knows neither the type and the value before run-time. > > Doctrine coding standards has a specific rule which disallows referencing a > possibly overridden class constant ( > > https://github.com/doctrine/coding-standard/blob/8d75920bb04a9be4c73c2a775ee5766262cf2a0d/lib/Doctrine/ruleset.xml#L123 > ), > but I'm wondering if it would be better to simply make it possible to allow > declaring class constants final (thus disallowing overriding) instead? > > In my opinion, final constants would come with two advantages: > - make class constant declarations paranoidly safe > - allow the engine to optimize class constants references in LSB context > > Although I admit that the win is not extra huge, I do think that it would > still be useful to have this feature, especially considering that the > implementation is very straightforward ( > https://github.com/php/php-src/pull/6878). So that's why I'm proposing > this > as an RFC. > > Recently, a similar attempt to make class constant declarations safer was > the Typed Constants RFC (https://externals.io/message/110755#110755), but > unfortunately it has halted since then. I think final and typed constants > would be a nice little addition, and would complement each other well. > > Regards, > Máté >
I think if we add final class constants, we should also clean up the current semantics for class constant overriding at the same time. The problem is that interface constants currently cannot be overridden directly (https://3v4l.org/jsDRO), but they can be overridden indirectly ( https://3v4l.org/IkvJe). If we have final class constants, then we should say that class constants are always overridable by default (regardless of whether they are declared in an interface or class), as we now have an explicit way to opt into forbidding the override. Regards, Nikita

=?utf-8?B?5rGf5rmW5aSn6Jm+5LuB?=

5 years ago
Hi Mate, Can I ask why you use late static binding while you don't want your constant value to be changed/overridden by child classes? IMHO, `self::FOO` is all you need. Regards, CHU Zhaowei -----Original Message----- From: Máté Kocsis <kocsismate90@gmail.com> Sent: Sunday, April 18, 2021 10:20 PM To: PHP Internals List <internals@lists.php.net> Subject: [PHP-DEV] [RFC] [Draft] Final constants Hi Internals, I've recently realized that class constant values cannot always be trusted when late static binding is involved (e.g. static::FOO or $this::FOO), since they can be freely overridden in child classes. That's why the engine knows neither the type and the value before run-time. Doctrine coding standards has a specific rule which disallows referencing a possibly overridden class constant ( https://github.com/doctrine/coding-standard/blob/8d75920bb04a9be4c73c2a775ee5766262cf2a0d/lib/Doctrine/ruleset.xml#L123 ), but I'm wondering if it would be better to simply make it possible to allow declaring class constants final (thus disallowing overriding) instead? In my opinion, final constants would come with two advantages: - make class constant declarations paranoidly safe - allow the engine to optimize class constants references in LSB context Although I admit that the win is not extra huge, I do think that it would still be useful to have this feature, especially considering that the implementation is very straightforward ( https://github.com/php/php-src/pull/6878). So that's why I'm proposing this as an RFC. Recently, a similar attempt to make class constant declarations safer was the Typed Constants RFC (https://externals.io/message/110755#110755), but unfortunately it has halted since then. I think final and typed constants would be a nice little addition, and would complement each other well. Regards, Máté

Máté Kocsis

5 years ago
> > Can I ask why you use late static binding while you don't want your > constant value to be changed/overridden by child classes? IMHO, `self::FOO` > is all you need. >
Personally, I don't use late static binding with class constants since the coding standard rule I previously referenced forbids this, but I don't think a coding style rule should be our only possibility to disallow constant overriding. After learning about the strange behavior Nikita pointed out, final constants would be even more useful than what I initially thought.

=?utf-8?B?5rGf5rmW5aSn6Jm+5LuB?=

5 years ago
Can I ask why you use late static binding while you don't want your constant value to be changed/overridden by child classes? IMHO, `self::FOO` is all you need. Personally, I don't use late static binding with class constants since the coding standard rule I previously referenced forbids this, but I don't think a coding style rule should be our only possibility to disallow constant overriding. After learning about the strange behavior Nikita pointed out, final constants would be even more useful than what I initially thought. I agree we should fix the weird behavior but that’s another topic. My point is actually what’s your scenario for a final constant? If you want to make sure the value you access are not changed by child class, `self::` can serve the purpose already. If you want to make sure child classes can access the original value, `parent::` or `ParentClassName::` can do the job as well. Regards, CHU Zhaowei

Máté Kocsis

5 years ago
> > My point is actually what’s your scenario for a final constant? If you > want to make sure the value you access are not changed by child class, > `self::` can serve the purpose already. If you want to make sure
Yes, my intention is to actually make class constant overriding impossible when it is desired. You can't really achieve this with only using self::, since you can only control your own code. Yes, I know that child classes could still declare a new class constant to overcome this limitation, but at least there will be no doubt what they should expect if they try to override the constant value. Otherwise, they have to check whether the parent class uses self:: or static:: calls. I agree we should fix the weird behavior but that’s another topic. No, I believe it isn't. Currently, only interface constants are not (directly) overridable. If we simply made all class/interface constans overridable, then one could not retain the original behavior in case of interfaces. While the solution offered by my proposal wouldn't be compatible with older PHP versions, it's still better to offer the possibility than not doing anything. Regards: Máté

Christian Schneider

5 years ago
Am 21.04.2021 um 14:25 schrieb Máté Kocsis <kocsismate90@gmail.com>:
>> My point is actually what’s your scenario for a final constant? If you >> want to make sure the value you access are not changed by child class, >> `self::` can serve the purpose already. If you want to make sure > > > Yes, my intention is to actually make class constant overriding impossible > when it is desired. You can't really achieve this with only using self::, > since you can only control your own code. Yes, I know that child classes > could still declare a new class constant to overcome this limitation, but > at least there will be no doubt what they should expect if they try to > override the constant value. Otherwise, they have to check whether the > parent class uses self:: or static:: calls.
I never really understood the desire to restrict how people can use your code. If there is no good reason to override the value of a class constant people won't do it. If there might be a good reason (even one you as the original designer didn't predict) then why not leave that door open. While I understand the theoretical benefit of being able to specify this behavior I do think it is almost always counter productive and not a pattern I would encourage. Especially not in a dynamic language like PHP. - Chris

Andreas Leathley

5 years ago
On 21.04.21 15:14, Christian Schneider wrote:
> I never really understood the desire to restrict how people can use > your code. > If there is no good reason to override the value of a class constant people won't do it. > If there might be a good reason (even one you as the original designer didn't predict) then why not leave that door open. > > While I understand the theoretical benefit of being able to specify this behavior I do think it is almost always counter productive and not a pattern I would encourage. Especially not in a dynamic language like PHP.
Such restrictions also convey information and intent. One could argue defining a specific return type to a method is restricting its use, but it also clarifies how something should work and avoids unintended changes. final constants might be more niche, but it gives you a choice on how you want a class constant to behave if child classes are necessary and possibly done by someone else. Having "final" for constants seems a good addition to me, especially if the inconsistent behavior with interface constants can be solved at the same time.

Christian Schneider

5 years ago
Am 21.04.2021 um 17:08 schrieb Andreas Leathley <a.leathley@gmx.net>:
> On 21.04.21 15:14, Christian Schneider wrote: >> I never really understood the desire to restrict how people can use >> your code. >> If there is no good reason to override the value of a class constant people won't do it. >> If there might be a good reason (even one you as the original designer didn't predict) then why not leave that door open. >> >> While I understand the theoretical benefit of being able to specify this behavior I do think it is almost always counter productive and not a pattern I would encourage. Especially not in a dynamic language like PHP. > > Such restrictions also convey information and intent. One could argue > defining a specific return type to a method is restricting its use, but > it also clarifies how something should work and avoids unintended > changes.
To me, the term 'constant' is good enough for that...
> final constants might be more niche, but it gives you a choice > on how you want a class constant to behave if child classes are > necessary and possibly done by someone else.
... but if child classes are necessary then how can you say that the constant's value should never be different for children? Why not leave that to the user of your class? I've seen my share of damage done by assumptions about how code will be used later.
> Having "final" for constants seems a good addition to me, especially if > the inconsistent behavior with interface constants can be solved at the > same time.
Fixing this inconsistency should be discussed separately IMHO. Both because it is a side-effect and should not be used as a reason to add 'final' as well as not fixing the problem for code *not* using 'final' with constants. Side-note: Back in my Java days people argued with performance to add 'final' but that only lead to it being sprinkled all over the place making code less flexible than it needed to be without real performance benefit. That's what I mean by promoting harmful patterns. - Chris