PHP is too verbose

One thing that's always struck me is how much unnecessary ceremony PHP devs add to their code, and how it's actually even recommended for the sake of being "Explicit".

Explicit is better than implicit. That principle applies when implicit behaviour is surprising. It does not apply when the implicit behaviour is the only possible behaviour, enforced by the spec. Doing so is just noise with a philosophical label stuck on after the fact.

Methods

Take the PHP documentation's own example for constructor promotion. Here it is in modern idiomatic PHP: final readonly, promoted properties, two methods, each body a single expression:

final readonly class Point
{
    public function __construct(
        public float $x,
        public float $y,
    ) {}

    public function translate(float $dx, float $dy): self
    {
        return new self($this->x + $dx, $this->y + $dy);
    }

    public function distanceTo(Point $other): float
    {
        return sqrt(($other->x - $this->x) ** 2 + ($other->y - $this->y) ** 2);
    }
}

You see the redundant use of public function everywhere? PHP already has implicit public visibility for methods, and the function keyword is identifiable from context. Neither adds useful information.

This is not even a worst-case example with many arguments, extra modifiers or dare I say when generics finally arrive, it's not gonna be pretty.

Let's start off easy: drop the function keyword. Many languages like Java, C# and JavaScript ES6+ don't require it inside a class body. The parser already knows it's a method from context, and so should you:

final readonly class Point
{
    public __construct(
        public float $x,
        public float $y,
    ) {}

    public translate(float $dx, float $dy): self
    {
        return new self($this->x + $dx, $this->y + $dy);
    }

    public distanceTo(Point $other): float
    {
        return sqrt(($other->x - $this->x) ** 2 + ($other->y - $this->y) ** 2);
    }
}

Then lean on the implicit default. Methods without a visibility modifier are already public in PHP, so spelling it out adds nothing the spec doesn't already guarantee. The same rules still apply, but the important information stands out more:

final readonly class Point
{
    __construct(
        public float $x,
        public float $y,
    ) {}

    translate(float $dx, float $dy): self
    {
        return new self($this->x + $dx, $this->y + $dy);
    }

    distanceTo(Point $other): float
    {
        return sqrt(($other->x - $this->x) ** 2 + ($other->y - $this->y) ** 2);
    }
}

Properties

Properties could do with some love too. Today every property must be prefixed with a visibility modifier. Typed properties without a visibility modifier should default to public, this is consistent with method behavior and encapsulation can honestly be quite annoying sometimes.

Defaulting to private only pays off for black-box APIs with strict versioning, where hiding internals is a genuine contract. For everyday code it just creates friction. You know a field has useful state, you can't touch it, and the restriction buys you nothing. Especially when debugging or writing tests.

The underscore convention is a neat trade-off. Prefix fields you consider private with it and the intent is communicated clearly, without the language enforcing access rules that get in your way.

Properties should also be allowed to be declared in a comma-separated list, just like variables. This is a common pattern in other languages, and it reduces boilerplate when you have multiple fields of the same type.

class Example
{
    // same type, shorthand names after the first
    float $x, y, z;

    // mixed types in one line
    float $width, int $count, string $label;

    string $name;
    DateTime $createdAt;

    // underscore prefix signals internal use, still accessible
    string $_cache;
    int $_retryCount;

    // private stated once for multiple properties
    private string $secret, string $token;

    // or as a block when there are many
    private {
        string $secret,
        string $token,
    }
}

Property hooks

Another minor inconvenience, property hooks on interfaces require you to declare them as public. In no world can an interface be anything other than public, so this is just redundant noise.

interface HasId
{
    public int $id { get; }
    public string $name { get; }
}

Generics

There is an RFC for generics in the works. Generic type parameters already make signatures longer. Stacking public function on top of that makes it worse.

Here is what idiomatic generic PHP would likely look like under the current rules:

final readonly class Pair<+L, +R>
{
    public function __construct(
        public L $left,
        public R $right,
    ) {}

    public function swap(): Pair<R, L>
    {
        return new Pair($this->right, $this->left);
    }
}

final readonly class Box<+T>
{
    public function __construct(
        public T $value,
    ) {}

    public function map<U>(callable $fn): Box<U>
    {
        return new Box(($fn)($this->value));
    }

    public function zip<O>(O $value): Box<Pair<T, O>>
    {
        return new Box(new Pair($this->value, $value));
    }
}

The type parameters are already doing a lot of visual work. With the redundant keywords removed, the structure breathes again and the types stand out:

final readonly class Pair<+L, +R>
{
    __construct(
        public L $left,
        public R $right,
    ) {}

    swap(): Pair<R, L>
    {
        return new Pair($this->right, $this->left);
    }
}

final readonly class Box<+T>
{
    __construct(
        public T $value,
    ) {}

    map<U>(callable $fn): Box<U>
    {
        return new Box(($fn)($this->value));
    }

    zip<O>(O $value): Box<Pair<T, O>>
    {
        return new Box(new Pair($this->value, $value));
    }
}

This is all achievable today

None of this requires a new language paradigm or a radical redesign. The generics examples aside, every change described in this article is a small, localised modification to PHP-src.

These are not proposals that should require years of RFC debate (but we know how it goes) about semantics. The only thing being removed is the requirement to write things the compiler already knows.