rfc:class-like_primitive_types

PHP 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 return FALSE for the other primitive types
  • gettype() will continue to report the other primitive types as non-objects
  • ArrayAccess will not be implemented by array, because it provides mutating methods
  • null has no shadow class (see the Open Issues section), but does support instanceof null

FIXME: 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

FIXME

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.

FIXME: Reflection.

To Opcache

FIXME.

Open Issues

FIXME: 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

FIXME

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

There is no language specification patch at present. FIXME

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged to
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

FIXME

Rejected Features

None yet.

rfc/class-like_primitive_types.txt · Last modified: 2017/09/22 13:28 (external edit)