PHP RFC: Class-like Primitive Types
- Version: 0.1
- Date: 2016-12-07
- Author: Andrea Faulds, ajf@ajf.me
- Status: Draft
- First Published at: http://wiki.php.net/rfc/class-like_primitive_types
Background
PHP has a small set of built-in types of value: null, Boolean, integer, float, string, array, object and resource. These types are known as “primitive” types, because they are built-in to the language. Of these, objects are special. Objects, themselves instances of the primitive type object, are also instances of PHP's form of non-primitive, user-defined types: classes.
Since their introduction in PHP 3.0, classes and objects have gained several features that are exclusive to them, which the other primitive types consequently cannot make use of. These features include instance and static methods, properties, class constants, interface implementation, inheritance, and instanceof
.
This set of exclusive features creates an awkward divide between PHP's objects and its other primitive types. Various tasks must be done in different ways for objects versus other types, creating the need for workarounds to bridge the gap. For example, the iterable pseudo-type was introduced because objects can implement Traversable
, yet the array type, not being a class, cannot.
By contrast, some of PHP's contemporaries, like JavaScript and Python, make all their primitive types be classes. This brings uniformity, and also means primitive types can benefit from the convenience of method calls as compared to function calls.
There could be benefits to adopting the same approach in PHP. However, as of PHP 5, PHP objects behave differently from the other primitive types, in that they are not value types, but rather reference types. This prevents making the other primitive types into objects, unless objects were changed to also support value type semantics. Even surmounting that hurdle, the PHP interpreter's internal representation of objects versus other primitive values is much less efficient, so replacing the primitive types with classes could significantly reduce performance. Moreover, changing the internal representation of the other primitive types in the PHP interpreter would be a massive undertaking.
Hence, this RFC suggests a different approach.
Proposal
This RFC proposes to extend PHP's object-oriented features to the other primitive types. They would not become objects, and their internal representation would be unchanged. However, they would now benefit from most of the features of objects.
Features extended to primitives
The following features of objects would now be extended to the other primitive types.
Interfaces and inheritance
These types would now implement PHP's pre-defined interfaces as appropriate (see the class hierarchy further down for a list of which).
These types would not be extendible by user classes, but could in future potentially extend new built-in abstract classes (e.g. int
and float
could extend \Number
).
Type declarations and instanceof
would now accept values of these types as implementing these interfaces and extending these classes, as appropriate.
Instance methods and properties
These types will provide instance methods as necessary to conform to PHP's pre-defined interfaces (see the class hierarchy further down for a list of which).
In future, they could provide other methods, or magic instance properties (see the future scope section).
instanceof
The instanceof
operator will now accept primitive values, rather than producing a fatal error. In addition, it will now support testing for membership of primitive types:
$x instanceof null
$x instanceof bool
$x instanceof int
$x instanceof float
$x instanceof string
$x instanceof array
$x instanceof object
$x instanceof resource
Features not extended to primitives
The following features would not now be extended to the other primitive types:
is_object()
will continue to returnFALSE
for the other primitive typesgettype()
will continue to report the other primitive types as non-objectsArrayAccess
will not be implemented byarray
, because it provides mutating methodsnull
has no shadow class (see the Open Issues section), but does supportinstanceof null
: What do I do about reflection?
Serializable and resources
The resource type will not implement the Serializable
interface. Though the serialize()
function accepts resources, it is broken: they serialise to an integer of the resource ID, and this integer, when deserialised, neither become a resource nor is usable as one. The ->deserialize()
method would therefore be redundant given resources cannot be deserialised, and the type would not be fulfilling the contract of the interface since resources do not meaningfully/ serialise.
Primitive type class hierarchy
Behind each other primitive type, there would now be a hidden internal class, or shadow class. Attempts to call methods, look up properties, use instanceof
, etc. on values of these types would consult these hidden internal classes. The following is an outline of these new classes. Note that the implementations of JsonSerializable
would only be present when the JSON extension is loaded.
/* There is no shadow class for null */ final class bool implements Serializable, JsonSerializable { public function __toString() { /* ... */ } public function serialize() { /* ... */ } public function unserialize($serialized) { /* ... */ } public function jsonSerialize() { /* ... */ } } final class int implements Serializable, JsonSerializable { public function __toString() { /* ... */ } public function serialize() { /* ... */ } public function unserialize($serialized) { /* ... */ } public function jsonSerialize() { /* ... */ } } final class float implements Serializable, JsonSerializable { public function __toString() { /* ... */ } public function serialize() { /* ... */ } public function unserialize($serialized) { /* ... */ } public function jsonSerialize() { /* ... */ } } final class string implements Serializable, JsonSerializable { public function __toString() { /* ... */ } public function serialize() { /* ... */ } public function unserialize($serialized) { /* ... */ } public function jsonSerialize() { /* ... */ } } final class array implements Serializable, JsonSerializable, Countable, IteratorAggregate { public function __toString() { /* ... */ } public function serialize() { /* ... */ } public function unserialize($serialized) { /* ... */ } public function jsonSerialize() { /* ... */ } public function count() { /* ... */ } public function getIterator() { return new ArrayIterator($this); } } final class resource { public function __toString() { /* ... */ } }
Backward Incompatible Changes
Proposed PHP Version(s)
This is proposed for the next PHP 7.x. Currently, that would be PHP 7.2.
RFC Impact
To SAPIs
This has no particular special impact on the SAPIs.
To Existing Extensions
This does not impact existing extensions: their view of the world is unchanged and the primitive types other than objects are still not objects, internally.
: Reflection.
To Opcache
.
Open Issues
: Reflection, Opcache.
Should null have a shadow class?
Or in other words, should these features be extended to null
?
null
is a value and type representing the absence of a value. It is a special case among the scalar types, lacking its own type declaration and not being coerced in weak type checking.
Would methods like __toString()
on null
values be more likely to be called in error than intentionally?
In JavaScript, null
does not have any properties or methods, but true
and false
do. In contrast, Python's None
(its equivalent to null) does have properties and methods, albeit only magic methods.
This RFC currently chooses to omit null from the extension of most features of objects to the other types, but excepts instanceof null
because it lacks the same potential to create errors.
Should resource be supported?
Resource is a legacy type that could be wholly replaced by objects in future. Extending these features to this type would be further entrenching it and contrary to the goal of its eventual removal.
In particular, instanceof resource
could create future backwards-compatibility problems if code uses it to check for values being resources and they later become objects.
Unaffected PHP Functionality
Future Scope
The extension of these features to the other primitive types opens up a number of future possibilities.
One of these would be introducing new methods on the other primitive types (and also properties). This could lend string and array manipulation the convenience of method calls, and would provide an opportunity for a fresh start versus PHP's existing string and array functions, which have notoriously inconsistent naming and parameter orders.
It also means we can easily introduce new superclasses of our other primitive types. For example, a new \Number
type superclassing int
and float
, or a new \Scalar
type superclassing everything except objects and arrays. Such superclasses could potentially be extended also by user-defined classes.
Likewise, we could introduce new interfaces implemented by our other primitive types. One potential use for this is operator overloading. PHP could add an interface for number-like classes, which when implemented, would allow use of the number operators (+ - * /
etc.) with objects of that class. That interface could itself be implemented by PHP's own int
and float
types.
instanceof
could potentially support pseudo-types like callable
.
Proposed Voting Choices
This is a major language change, so it would require a 2/3 majority.
It would be a Yes/No vote on whether to accept the RFC.
Patches and Tests
There is no interpreter patch at present. .
There is no language specification patch at present.
Implementation
After the project is implemented, this section should contain
- the version(s) it was merged to
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
- a link to the language specification section (if any)
References
Rejected Features
None yet.