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

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