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: Draft (or Under Discussion or Accepted or Declined)
- First Published at: http://wiki.php.net/rfc/short-and-inner-classes
Introduction
PHP has steadily evolved to enhance developer productivity and expressiveness, introducing features such as typed properties, constructor property promotion, and first-class callable syntax. However, defining simple data structures and organizing classes remains verbose.
This RFC proposes two related enhancements to PHP:
Short class syntax, allowing simple or data-oriented classes to be defined in a single line:
class Point(int $x, int $y);
This syntax acts as a shorthand for defining classes with constructor property promotion, reducing boilerplate while maintaining clarity.
Inner classes, enabling the definition of classes within other classes with visibility control:
class Foo { public class Bar(public string $message); }
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, 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, public int $y) {} }
Properties inside parentheses are automatically declared as class properties and default to public unless explicitly specified:
// declare $shapes as a private property class Geometry(private $shapes) use GeometryTrait;
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. The parent class constructor is overridden and not automatically called.
class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable;
Empty Classes
Short classes may be empty:
class Point() extends BasePoint use PointTrait;
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 compile into standard class definitions.
Inner Classes
Inner classes allow defining classes within other classes, following visibility rules:
class Outer { class Inner(public string $message); 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.abstract
is not allowed as an inner class cannot be parent classes.
Visibility Rules
Private and protected inner classes are only accessible within their outer class (or subclasses for protected):
class Outer { private class PrivateInner(string $message); public function getInner(): self::PrivateInner { return new self::PrivateInner('Hello, world!'); } } // using a private inner class from outside the outer class, as a type hint is forbidden function doSomething(Outer::PrivateInner $inner) { echo $inner->message; } // this is ok: $inner = new Outer()->getInner(); // but this is not: doSomething($inner); // Fatal error: Private inner class Outer::Inner cannot be used in the global scope
Just like with other languages that support inner classes, it is better to return an interface or a base class from a method instead of exposing a private/protected class.
You may also not instantiate a private/protected class from outside the outer class’s scope:
$x = new Outer::PrivateInner(); // Fatal error: Uncaught Error: Cannot access private inner class Outer::Inner
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. However, tooling utilizing 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
Most of the changes are in compilation and AST, so the impact to opcache is minimal.
Open Issues
Pending discussion.
Unaffected PHP Functionality
There should be no change to existing PHP functionality.
Future Scope
- inner enums
Proposed Voting Choices
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
Links to external references, discussions or RFCs
Rejected Features
Keep this updated with features that were discussed on the mail lists.