rfc:short-and-inner-classes

This is an old revision of the document!


PHP RFC: Inner Classes with Short Syntax

Introduction

PHP has steadily evolved to enhance developer productivity and expressiveness, introducing new 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, which allows for defining simple or data-oriented classes in a single line:

class Point(int $x, int $y);

This syntax serves as a shorthand for defining classes with constructor property promotion, reducing boilerplate while maintaining clarity.

Inner classes, which allows for the ability to define classes within other classes and control the use of inner classes through visibility:

class Foo {
    public class Bar(public string $message);
}

Proposal

Short Class Syntax

The proposed syntax for a short class definition is as follows: a keyword class, followed by the class name, then a list of properties enclosed in parentheses. Optionally, a list of traits, interfaces, and a parent class may be defined.

// a simple class with two public properties: x and y
class Point(int $x, int $y);
 
// a more complex 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) {}
}

Any properties defined within the parenthesis are defined as a property on the class and are automatically public unless specified otherwise.

// declare $shapes as a private property
class Geometry(private $shapes) use GeometryTrait;

Default Values

Default values may be provided for properties but only for properties with type hints:

class Point(int $x = 0, int $y = 0);

Inheritance and Behavior

With class short syntax, no behavior may be defined, yet it can still utilize traits, interfaces, and other classes.

class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable;

Note that the original constructor from any parent class is overridden and not called by the short syntax.

Empty Classes

Short classes may also be empty:

class Point() extends BasePoint use PointTrait;

Attributes

Attributes may also be used with short classes:

#[MyAttribute]
class Password(#[SensitiveParameter] string $password);

Modifiers

Short classes support modifiers such as 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 implemented as pure syntax sugar and are compiled as full class definitions.

Inner Classes

Inner classes are classes that are defined within another class.

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 

Inner classes have inheritance similar to static properties, allowing you to define rich class 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

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, and protected apply to the visibility of the inner class.
  • final, and readonly apply to the class itself.

Thus, an inner class with the modifier private readonly is only accessible within the class and any instances are readonly.

static is not allowed as a modifier on an inner class because there is currently no such thing as a static class in PHP.

Visibility

A private or protected inner class type is only accessible within the class it is defined in (or its subclasses in the case of protected classes). This is similar to C#, so you may return a private type from a public method, but not use it as a type hint from outside the outer class:

class Outer {
    private class Inner(string $message);
 
    public function getInner(): self::Inner {
        return new self::Inner('Hello, world!');
    }
}
 
function doSomething(Outer::Inner $inner) {
    echo $inner->message;
}
 
doSomething(new Outer()->getInner());
// Fatal error: Private inner class Outer::Inner cannot be used in the global scope

You may also not instantiate a private/protected class from outside the outer class’s scope:

$x = new Outer::Inner();
// Fatal error: Uncaught Error: Cannot access private inner class Outer::Inner

Inheritance

Classes may not inherit from inner classes. 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.

rfc/short-and-inner-classes.1740254954.txt.gz · Last modified: 2025/02/22 20:09 by withinboredom