- Version: 0.3
- Last Substantial Updates: 2022-05-04
- Target Version: PHP 8.next
There are many scenarios in which userland code might need to pass parameters to a method or assign a value to a property that is intended to represent a class
. Currently the only way to type-hint that a provided value represents a fully-qualified class-name (FQCN) is through userland code. This RFC proposes adding a new internal type that would verify provided values are FQCN.
There are several motivations for wanting to include this type and not rely on userland to provide this functionailty:
Like all types one of the primary purposes of this is to convey more meaningful information to people reading a given codebase. While it is possible to type-hint string
and declare your property or parameter name in such a way that it conveys what is being requested this feels subpar at best. This RFC takes the opionin that information about types should be conveyed through the type system and not the constructs around it.
Generally speaking, throwing errors and failing fast is preferred. Throwing an error where an invalid type is used could prevent unneccesary instructions from being executed and causes the failure point to be closer to the calling code.
There are currently libraries1 that exist in userland for declaring a type that represents a PHP class. Having a consistent type for representing a class could, albeit slightly, improve the interoperability between codebases. Still, the userland libraries that provide additional features for representing a PHP type could make use of this new type inside their own code.
It is also worth pointing out that unlike other features that could have the same principle applied, for example HTTP abstractions being in userland vs internal, the domain for this RFC is the PHP language itself. As evidenced in the "Use Case" section there's a need for this type currently and this RFC takes the opinion that PHP should provide a suitable construct for describing itself.
Other languages, such as Java2, have the concept of a Class
class. Adding such a concept to PHP would be very complex, heavy-handed, and would be hard to introduce in ways that provides backwards compatibility with existing codebases. Additionally, existing static analysis tools, such as vimeo/psalm
and phpstan/phpstan
, have the concept of a class-string
which works well with a common way of providing class names, through the Object::class
or $instance::class
constructs. For these reasons, this RFC proposes adding a new type that will accept string
values that are FQCN. With the hyphen present in class-string
it likely could not be used for our purposes. With the hyphen removed the type classstring
becaomes difficult to read and prone to typos with the 3 sss
characters in a row. Other alternatives, in no specific order, could be:
classtype
classname
fqcn
classable
For the sake of clarity the rest of this RFC will refer to the type as
classsstring
until a primary alternative is chosen. At the time of this draft this RFC does not have a strong opinion on which name to choose.
The classstring
could be type-hinted anywhere the string
type is currently allowed. This would include:
- Class properties
- Method and function parameters
- Method and function return types
It is important to note that the classstring
type would be available in type unions but not type intersects.
At runtime any strings passed to the classstring
type that is not a loadable class or interface a TypeError
, or other valid Throwable, would be thrown. This check would cause autoloading of the class or interface to trigger if required. This check would occur:
- When a typed property is assigned a value.
- When a method or function is invoked with typed parameters.
- When a method or function with a return type has its return value checked.
I have 2 use cases in personal projects that include the loading of a Plugin architecture3 and the static analysis of a codebase to create a Container4. A highly cursory search for class-string
on GitHub returned 1.3m+ results for PHP5. This many uses indicates that there's a large amount of existing code that could potentially utilize this new type.
<?php
namespace Acme;
class GoodPropertyDemo {
private classstring $type;
private classstring $withDefault = PropertyDemo::class;
// The type can be nullable
private ?classstring $nullableType = null;
// The type can be in a union of other types
private classstring|false|int $someUnion = false;
}
class BadPropertyDemo {
// Throws an error when this class is instantiated and the property is assigned the default value
private classstring $type = 'baz';
// Throws an error that classstring can't be used in an intersect type.
// This should occur whenever type intersect validity is checked
private classstring&GoodPropertyDemo $foo;
}
<?php
namespace Acme;
class GoodMethodParameter {
public function register(classstring $service) {
}
public function withDefault(classstring $service = GoodMethodParameter::class) {
}
public function withNullable(?classstring $service = null) {
}
public function inTypeUnion(classstring|int $service = 42) {
}
}
class BadMethodParameter {
// An exception would be thrown if this method is invoked with the default parameter values
// because there is no class or interface 'foobar'
public function withNotClass(classstring $type = 'foobar') {
}
// Throws an error that classstring can't be used in an intersect type.
// This should occur whenever type intersect validity is checked
public function inTypeIntersect(classstring&GoodMethodParameter $param) {
}
}
<?php
class GoodReturnType {
public function getType() : classstring {
return $this::class;
}
public function getPossibleType() : ?classstring {
return null;
}
public function getTypeOrSomething() : classstring|int {
return 13;
}
}
class BadReturnType {
// Throws an exception when this method is invoked and the return type is checked because it isn't a FQCN
public function getType() : classstring {
return '';
}
// Throws an error that classstring can't be used in an intersect type.
// This should occur whenever type intersect validity is checked
public function getIntersect() : classstring&GoodReturnType {
// ...
}
}
- Should
classstring|string
be allowed? - What are potential performance implications?
- 1 https://github.com/cspray/typiphy, https://github.com/sebastianbergmann/type
- 2 https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html
- 3 labrador-kennel/core includes a Plugin architecture that would take advantage of this.
- 4 cspray/annotated-container includes many places where an
ObjectType
hint is currently being used that could be replaced. - 5 https://github.com/search?l=PHP&q=class-string&type=Code
Ideas are always made better by multiple people providing feedback. This one is no different. Special thanks to the following individuals who provided important guidance in fleshing this RFC out.