skip to content

In PHP 8.4 and later, when would you declare a property on an interface instead of a getter method, and what can implementing classes satisfy it with?

level: seniorimportance: should knowfreq 18%

answer

  1. added in PHP 8.4
  2. { get; } / { set; } / { get; set; }
  3. only public access is constrained
  4. readonly satisfies get-only
  5. Interfaces may only include hooked properties

basics

~20 s

Since PHP 8.4 an interface can require a public readable, writable or read-write property with { get; }, { set; } or both. Implementers may use a plain public property, a hooked or virtual property, or, for get-only, a readonly one.

solid answer

~40 s

PHP 8.4 lets an interface declare a property requirement such as `public string $displayName { get; }`. The hooks listed say what **public** access the implementer must allow: `{ get; }` means publicly readable, `{ set; }` publicly writable, `{ get; set; }` both. An implementer can satisfy it with an ordinary `public` property, with a property that has hooks, with a **virtual** property that computes the value in a `get` hook, or, for a get-only requirement, with a `readonly` property. The declaration must carry hooks (`Interfaces may only include hooked properties`) and must be public, non-final and non-static. Prefer it over a `getDisplayName()` method when the contract is genuinely a value, so callers write `$gateway->displayName` whether it is stored or computed. Keep methods for anything with parameters, side effects or real cost.

code

php · 37 lines
php
<?php
declare(strict_types=1);

interface PaymentGateway
{
    public string $displayName { get; }

    public function charge(int $amountCents, string $orderId): string;
}

final class CardGateway implements PaymentGateway
{
    public function __construct(public readonly string $displayName = 'Card') {}

    public function charge(int $amountCents, string $orderId): string
    {
        return 'card-' . $orderId;
    }
}

final class VoucherGateway implements PaymentGateway
{
    public function __construct(private string $issuer) {}

    public string $displayName {
        get => 'Voucher from ' . $this->issuer;
    }

    public function charge(int $amountCents, string $orderId): string
    {
        return 'voucher-' . $orderId;
    }
}

foreach ([new CardGateway(), new VoucherGateway('City Arena')] as $g) {
    echo $g->displayName, "\n";
}

go deeper

for a junior

Recall that PHP 8.4 interfaces can require public properties using { get; }, { set; } or { get; set; }, and that a plain public property satisfies them.

for a middle

Explain which implementations satisfy which forms, including readonly for get-only and virtual get hooks, and the declaration errors for plain, non-public or final interface properties.

for a senior

Argue when a property requirement beats a getter method, where it misleads (I/O, failure, cost), and how PHP-version support constrains a shared library's contracts.

for a principal

Weigh standardising on property requirements across a codebase against consistency with older libraries and teams, and how you would migrate existing getter-based contracts.

## What changed in PHP 8.4 Before PHP 8.4, an interface could only require **methods**, so exposing a value through a contract meant a getter such as `getDisplayName()`. PHP 8.4 added **property hooks** to classes, and with them interfaces gained the ability to require **properties**. The interface does not store anything; it states which public operations must be possible on a property of that name and type. ## The three forms | Declaration in the interface | What every implementer must allow publicly | |---|---| | `public string $displayName { get; }` | reading `$obj->displayName` | | `public string $webhookUrl { set; }` | writing `$obj->webhookUrl = ...` | | `public int $timeoutSeconds { get; set; }` | both reading and writing | Only **public** access is constrained. A `{ get; }` requirement does not forbid writing; the implementer decides whether writes are allowed at all. The engine enforces the declaration's shape: - It must list hooks; a plain `public string $name;` fails with `Interfaces may only include hooked properties`. - It must be `public`: `Property in interface cannot be protected or private`. - It cannot be `final` or explicitly `abstract` (every interface member is already abstract). - Hooks cannot be declared on static properties, so an interface property is always an instance property. ## How an implementer can satisfy it 1. **A plain public property.** `public string $displayName;` satisfies `{ get; }`, `{ set; }` and `{ get; set; }`, because it is both readable and writable. 2. **A readonly property**, for a `{ get; }` requirement only. A promoted `public readonly string $displayName` works. It does **not** satisfy `{ get; set; }`; the class then fails to compile because readonly restricts public writes. 3. **A virtual property with only a `get` hook**, computing the value: `public string $displayName { get => 'Card (' . $this->region . ')'; }`. It satisfies `{ get; }` and cannot be written. 4. **A backed property with hooks**, for example a `set` hook that validates before storing, which satisfies `{ set; }` or `{ get; set; }`. A class that implements none of these fails like any unimplemented interface member: `Class X contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set)`. ## Property requirement or getter method? The trade-off a senior engineer should be able to argue: - **Use a property requirement** when the contract is a **value**: a gateway's display name, a currency code, a configured timeout. Callers read `$gateway->displayName` whether an implementer stores it, computes it or loads it lazily, and the interface no longer forces a pile of trivial getters. - **Keep a method** when the operation takes **parameters**, may **fail**, performs **I/O**, or is expensive. A property read that makes a network call surprises every reader. - **Mind the audience.** Code that must run on PHP 8.3 or earlier cannot parse hook syntax in the interface, so a shared library supporting older versions still needs methods. - **Readonly is a good fit for get-only requirements.** A value object implementing `{ get; }` with a `readonly` promoted property gets immutability for free. ## Reading the error messages The engine reports interface property mistakes at declaration time, and each message points at one rule: - `Interfaces may only include hooked properties`: the interface wrote a plain property; add `{ get; }`, `{ set; }` or both. - `Property in interface cannot be protected or private`: interface requirements are about public access only. - `Property in interface cannot be final`, or `cannot be explicitly abstract`: interface members are implicitly abstract and cannot be closed. - `Class X contains N abstract methods ... (I::$prop::get, ...)`: the implementer is missing the property, and the missing hooks are listed like methods. - `Set access level of C::$prop must be omitted (as in class I)`: a readonly property was offered where the interface requires public writes. ## Scenario: payment gateways A ticketing site shows the chosen payment method at checkout. `interface PaymentGateway { public string $displayName { get; } public function charge(int $amountCents, string $orderId): string; }` lets the card gateway store a fixed name in a readonly property while the voucher gateway computes its name from the voucher's issuer, without either one adding a getter the checkout template must remember to call.

  • Why does a readonly property satisfy { get; } but not { get; set; }?
    A `{ get; set; }` requirement promises that any caller holding the interface type may write the property. A `readonly` property can only be initialised from inside the declaring class, so public writes are impossible and the promise would be broken. PHP rejects the class at declaration time instead of failing later on a write.
  • Does a { get; } requirement stop an implementer from making the property writable?
    No. The requirement only guarantees public reads. An implementer may use a plain public property that is also writable. Callers typed against the interface should treat it as read-only, but the engine does not forbid writes on classes that allow them.

An interface property is like a sign on a service counter that promises "ask here for opening hours". The promise is that a question gets answered, not that the answer is printed on a card: one branch reads it off a poster, another checks the calendar each time, and the customer asks the same way at both.

saying these in an interview costs you the question

  • An interface property stores a default value shared by all implementers
  • A { get; } requirement forbids implementers from allowing writes
  • Only hooked properties can satisfy an interface property requirement
  • Interface properties may be protected so only implementers see them
  • A readonly property satisfies a { get; set; } requirement