This is an old revision of the document!
PHP RFC: Inner Classes with Short Syntax
- Version: 0.1
- Date: 2025-02-08
- Author: Rob Landers, rob@bottled.codes
- Status: Under Discussion (Accepted or Declined)
- First Published at: http://wiki.php.net/rfc/short-and-inner-classes
Introduction
This RFC proposes two significant enhancements to the language: Short Class Syntax and Inner Classes. These features aim to streamline class definitions, reduce boilerplate, and introduce a new level of encapsulation and organization within PHP applications.
Short Class Syntax allows developers to define simple or data-oriented classes in a single line, leveraging constructor property promotion to enhance readability and maintainability. Inner Classes, on the other hand, enable the definition of within other classes, providing fine-grained visibility control and fostering better modularization.
By adopting these enhancements, PHP can offer developers more powerful tools to write clean, efficient, and well-structured code, ultimately improving the overall developer experience and code quality.
Proposal
Short Class Syntax
The proposed syntax for defining a short class consists of the class keyword, followed by the class name, and a list of properties in parentheses. Optionally, traits, interfaces, and a parent class can be specified.
// a simple class with two public properties class Point(int $x, int $y); // A readonly class with a parent class, interface, and traits readonly class Vector(int $x, private int $y) extends BaseVector implements JsonSerializable use PointTrait, Evolvable;
This is equivalent to the following full class definition:
class Point { public function __construct(public int $x, public int $y) {} } readonly public class Vector extends BaseVector implements JsonSerializable { use PointTrait, Evolvable; public function __construct(public int $x, private int $y) {} }
Properties inside parentheses are automatically declared as class properties and default to public unless explicitly specified.
Default Values
Properties with type hints may have default values:
class Point(int $x = 0, int $y = 0);
Inheritance and Behavior
Short classes can extend other classes, implement interfaces, and use traits, but they cannot define additional methods except by using traits. If the parent class has a constructor, the constructor will be overridden and not called.
class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable;
Empty Classes
Short classes may be empty, in which case, the constructor is not overridden. This can be useful in some cases, such as when you want to define a simple exception:
class HttpError() extends Exception; throw new HttpError('Not Found', 404);
Attributes
Attributes can be used with short classes:
#[MyAttribute] class Password(#[SensitiveParameter] string $password);
Modifiers
Short classes support readonly, final, and abstract:
readonly class User(int $id, string $name); final class Config(string $key, mixed $value); abstract class Shape(float $area);
How it works
Short classes are purely syntactic sugar and compiled into standard class definitions during compile time.
Inner Classes
Inner classes allow defining classes within other classes, following standard visibility rules. This allows developers to declare a class as private
or protected
and restrict its usage to the outer class. Inner classes may only be nested one level deep, may not be a parent class, and may not be declared abstract
:
class Outer { class Inner(public string $message); // using short syntax // using traditional syntax private class PrivateInner { public function __construct(public string $message) {} } } $foo = new Outer::Inner('Hello, world!'); echo $foo->message; // outputs: Hello, world! $baz = new Outer::PrivateInner('Hello, world!'); // Fatal error: Uncaught Error: Cannot access private inner class Outer::PrivateInner
Modifiers
Inner classes support modifiers such as public
, protected
, private
, final
and readonly
. When using these as modifiers on an inner class, there are some intuitive rules:
public
,private
, andprotected
apply to the visibility of the inner class.final
, andreadonly
apply to the class itself.static
is not allowed as a modifier since PHP does not support static classes and isn’t a property.abstract
is not allowed as an inner class cannot be parent classes.
Visibility Rules
Private and protected inner classes are only instantiatable within their outer class (or subclasses for protected) and may not be used as type hints outside their outer class.
For example, you may return a private inner class from any method inside that same inner class or the outer class, but you may not use it as a type hint in a function outside the outer class:
class Box { private class Point(int $x, int $y); private self::Point $center; public function __construct() { $this->center = new self::Point(0, 0); } public function getCenter(): self::Point { return $this->center; } public function setCenter(self::Point $center) { $this->center = $center; } } $box = new Box(); $center = $box->getCenter(); $center->x = 10; $box->setCenter($center); var_dump($box);
Outputs:
object(Box)#1 (1) { ["center":"Box":private]=> object(Box::Point)#2 (2) { ["x"]=> int(10) ["y"]=> int(0) } }
However, if we try to use it outside the outer class as a type hint:
function mutateBox(Box::Point $point): Box::Point { $point->x = 10; } $center = mutateBox($center);
We receive the following error:
PHP Fatal error: Private inner class Box::Point cannot be used in the global scope
This gives a great deal of control to developers, preventing accidental misuse of inner classes. Developers may have the inner class implement an interface so that the programmer must code to the interface, allowing large projects to enforce a consistent API.
Inheritance
Inner classes have inheritance similar to static properties; this allows you to redefine an inner class in a subclass, allowing rich hierarchies.
readonly class Point(int $x, int $y); class Geometry { public array $points; protected function __construct(Point ...$points) { $this->points = $points; } public class FromPoints extends Geometry { public function __construct(Point ...$points) { parent::__construct(...$points); } } public class FromCoordinates extends Geometry { public function __construct(int ...$coordinates) { $points = []; for ($i = 0; $i < count($coordinates); $i += 2) { $points[] = new Point($coordinates[$i], $coordinates[$i + 1]); } parent::__construct(...$points); } } } class Triangle extends Geometry { protected function __construct(public Point $p1, public Point $p2, public Point $p3) { parent::__construct($p1, $p2, $p3); } public class FromPoints extends Triangle { public function __construct(Point $p1, Point $p2, Point $p3) { parent::__construct($p1, $p2, $p3); } } public class FromCoordinates extends Triangle { public function __construct(int $x1, int $y1, int $x2, int $y2, int $x3, int $y3) { parent::__construct(new Point($x1, $y1), new Point($x2, $y2), new Point($x3, $y3)); } } } $t = new Triangle::FromCoordinates(0, 0, 1, 1, 2, 2); var_dump($t instanceof Triangle); // true var_dump($t instanceof Geometry); // true var_dump($t instanceof Triangle::FromCoordinates); // true
However, no classes may not inherit from inner classes, but inner classes may inherit from other classes, including the outer class.
Names
Inner classes may not have any name that conflicts with a constant or static property of the same name.
class Foo { const Bar = 'bar'; class Bar(public string $message); // Fatal error: Uncaught Error: Cannot redeclare Foo::Bar } class Foo { static $Bar = 'bar'; class Bar(public string $message); // Fatal error: Uncaught Error: Cannot redeclare Foo::$Bar }
Backward Incompatible Changes
- This RFC introduces new syntax and behavior to PHP, which does not conflict with existing syntax.
- Some error messages will be updated to reflect inner classes, and tests that depend on these error messages are likely to fail.
- Tooling using AST or tokenization may need to be updated to support the new syntax.
Proposed PHP Version(s)
This RFC targets the next version of PHP.
RFC Impact
To SAPIs
None.
To Existing Extensions
Extensions accepting class names may need to be updated to support ::
in class names. None were discovered during testing, but it is possible there are extensions that may be affected.
To Opcache
This change introduces a new opcode, AST, and other changes that affect opcache. These changes are included as part of the PR that implements this feature.
Open Issues
Pending discussion.
Unaffected PHP Functionality
There should be no change to any existing PHP syntax.
Future Scope
- inner enums
Proposed Voting Choices
As this is a significant change to the language, a 2/3 majority is required.
Patches and Tests
A complete implementation is available on GitHub.
Implementation
After the project is implemented, this section should contain - the version(s) it was merged into - 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
- “evolvable” syntax: A ->with() function built into short classes.