This is an old revision of the document!
PHP RFC: Static class
- Version: 1.2
- Date: 2024-06-23
- Author: Paul Morris bilge@scriptfusion.com
- Status: Under Discussion
- First Published at: https://wiki.php.net/rfc/static_class
Introduction
A static class is a class that cannot be instantiated, and whose members (properties and methods) are all static. Implicit static classes can be created today, by simply only including static members. However, explicitly marking a class as static has several advantages:
- Clear expression of developer intent.
- Language-level compile-time checks to verify no instance members are included anywhere in the inheritance chain.
- Language-level runtime checks to verify the class is never instantiated by any mechanism.
Of these advantages, the first can be somewhat mitigated with a userland trait, but as such traits are non-standard, there is no consistent way to identify them between projects. The language-level checks cannot be so easily emulated, and are better suited for inclusion within PHP.
Proposal
A static class is so marked with the existing static
keyword, adjacent to the class declaration.
static class Foo {}
We introduce the static
keyword at the class level to preclude the need to create a private constructor. That is, function __construct()
is a compile-time error in a static class. Furthermore, attempting to instantiate a static by any means, whether with the new
keyword, ReflectionClass::newInstance*
methods or unserialize()
hacks, is strictly forbidden and will result in a runtime error.
Whilst the goals of this RFC so expressed are fairly straightforward, we acknowledge there are many finer points to consider. In particular:
- Should a static class imply
final
(as in C#)? - Should a static class prohibit inheritance (as in C#)?
- Should a static class permit state?
- Should traits be permitted in a static class?
- Which magic methods should be supported?
To answer these questions, we consider the principal reason to mark a class static is to move from an implicit declaration to the explicit. Ergo, any decision precluding the developer from doing so is invalid. Thus we derive the following razor:
We cannot remove features from a static class that would otherwise be present in a standard class.
Following this razor we arrive at the following design.
- A static class may be marked
final
, but in the absence of such a mark, may still be extended. - A static class may extend other (static) classes, whether or not so marked, provided they contain no instance members.
- A static class may have static properties.
- A static class may use traits.
- All static magic methods must be supported, i.e. just
__callStatic()
.
Static keyword for class members
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.
There is also third position one might take, to treat static as forbidden for members of a static class. This would probably be an unpopular option because it requires changing every member declaration to migrate existing classes, rather than just changing the class definition. Nevertheless, it is an option.
Choosing whether static
should be required, optional or forbidden for members of a static class is still open for debate, and if it cannot be clearly settled on the mailing list, it may be posed as an additional voting option for this RFC (separately from the static class feature itself).
Mutually exclusive modifiers
When static
is applied to a class, the following other class-level modifiers become invalid and will raise compile-time errors.
abstract
— This modifier exists to denote a class cannot be instantiated, but thestatic
modifier already carries this meaning.readonly
— Read-only properties are only supported for instance properties, but instance properties are forbidden bystatic
.
Anonymous classes
Anonymous classes are always instantiated and thus at odds with the concept of a static class. That is, the notion of an anonymous static class is inherently invalid and not supported.
Dynamic properties
Undeclared object properties can be added dynamically to object instances, but since static classes cannot be instantiated, there is nothing to attach such properties to. Static properties must always be declared explicitly.
Reflection extensions
A new ReflectionClass::isStatic
method should be added, returning true
when the class is so marked, otherwise false
.
Open questions
- Should static classes only permit extending explicitly marked static classes?
Backward Incompatible Changes
None known.
Proposed PHP Version(s)
PHP 8.4
Future Scope
This feature is expected to be complete by itself. However, as noted in Readonly properties 2.0, read-only static properties are not supported due to a technical limitation. If that limitation should ever be lifted, we could revisit lifting the restriction on mutual exclusivity with the readonly
modifier.
Proposed Voting Choices
- A simple yes/no vote to include this feature.
- A choice between required, optional or forbidden to include
static
on member declarations within a static class (TBC).
Patches and Tests
The patch and tests will be created by myself and/or Lanre. A draft PR by Lanre is currently available at https://github.com/php/php-src/pull/14583.
References
Rejected Features
Forbidding static class type hints
It should be regarded as an error to type hint a static class, since they cannot be instantiated and thus the requirement can never be fulfilled by a matching instance. However, it is not technically possible to forbid such hints within PHP itself because type checking is done when an instance is passed at runtime (and we can never have such an instance). The engine does not support type checking at compile time for function/method signatures. Such a check would be a good candidate for third party static analysers instead.
Special thanks
I would feel remiss not to give thanks to the following list contributors whose high quality feedback made this RFC what it is.
- Mike Schinkel
- Alexandru Pătrănescu
- Claude Pache
- Everyone else who engaged with this proposal.
Thank you!