---
title: "PHP Property Resolution Order: Class · Trait · Parent · Interface"
url: https://www.exakat.io/php-property-resolution-order-class-%c2%b7-trait-%c2%b7-parent-%c2%b7-interface/
date: 2026-09-14
modified: 2026-09-14
lang: en
author: "dams"
description: "PHP Property Resolution Order: Class · Trait · Parent · Interface PHP offers the same four places to define a property that it offers for constants: directly in a class,..."
categories:
  - "Code auditing"
tags:
  - "class"
  - "code"
  - "php"
  - "property"
image: https://www.exakat.io/wp-content/uploads/2026/09/pumpkin.320.jpg
word_count: 1941
---

# PHP Property Resolution Order: Class · Trait · Parent · Interface

# PHP Property Resolution Order: Class · Trait · Parent · Interface

PHP offers the same four places to define a property that it offers for constants: directly in a class, in a trait the class uses, in a parent class it extends, and, as of PHP 8.4, in an interface it implements. Since these different places may all happen at the same time, they are in competition. So, thre will be only one winner in the PHP property resolution order: Class, Trait, Parent, Interface

That last one used to be a flat "no". For the entire history of the language before 8.4, interfaces could not declare properties at all; they were the one member kind interfaces had nothing to say about. Property hooks changed that, but not by making interfaces work like they do for constants. They introduced a third model, distinct from both.

There's also a dimension constants never had to deal with: storage. A constant is a value: read it from anywhere in the hierarchy and you get one thing back, copied conceptually into whichever class resolves it. A property is a slot in an object, or a shared cell on a class, and where that slot lives is not always where you'd guess. Two of the surprises below come directly from that difference.

## The priority stack

| Priority | Layer | Description |
| -------- | ----- | ----------- |
| **1 highest** | **Class own property** | Always wins. Defined directly in the class body, or as a promoted property. |
| **2** | **Trait property** | Used when the class has no own definition. Copied in at compile time: value-checked against the class's own definition, but not against the parent's. |
| **3** | **Parent class property** | Inherited when nothing more local claims the name. A "inherited" doesn't always mean "copied". See the static-property section below. |
| **4 new in 8.4** | **Interface hooked property only** | A plain property on an interface is a compile error. A hooked property is a contract, exactly like an interface method: the interface says the property must exist and be readable or writable or both; the implementing class supplies the actual logic. |

Tier 4 isn't really competing with tiers 1–3 the way an interface constant competes with a parent constant. A hooked property in an interface has no storage and no default value of its own. It's a shape, not a value. So there's no version of the constants article's "ambiguous, parent and interface both supply a value" fatal error here. Properties sidestep that entire category of conflict, at the cost of interfaces being locked out of the storage-based kind of property for good.

## A concrete example

This code actually fails, as the compatibility between the class and the trait definition MUST be complete: down to the default value. If the definition is not the same, it yields a "[App and HasGreeting define the same property ($greeting) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed](https://php-errors.readthedocs.io/en/latest/messages/ps-and-ps-define-the-same-property-q$psr-in-the-composition-of-ps.-however,-the-definition-differs-and-is-considered-incompatible.-class-was-composed.html)" compilation error.

Now, remove `App`'s own property and the trait takes over: `"Hello from trait"`, with no complaint that it disagrees with the parent's value: class-vs-parent and trait-vs-parent are silent overrides, not a checked ones. Remove the trait too, and `App` simply inherits `BaseGreeter`'s property, no conflict, because nothing else at that level is competing for the name.

## Interfaces couldn't join this club, until PHP 8.4

```
Fatal error: Interfaces may only include hooked properties
?>
[/php]

```

That's the whole story, for every PHP version before 8.4: an interface demanding a property was simply not expressible. Property hooks reopen the door, but as a contract for behavior, not storage:

This is structurally closer to how interfaces handle methods than how they handle constants: the interface declares that reading or writing `$greeting` must be possible, and leaves the actual value, computed, stored, whatever the implementer wants, entirely up to the class. Two interfaces can declare the same hooked property name without any risk of the "ambiguous" fatal error the constants article ran into, because neither interface is supplying a competing value to be ambiguous about.

## The rules that catch people out

### Trait beats parent, no questions asked

Same rule as constants: a trait property is compiled directly into the class body, so it's treated as the class's own definition the moment there's nothing more local to contest it.

### Class beats trait — but only if they agree, or the class overrides

```
Fatal error: C and T define the same property ($foo) in the
composition of C. However, the definition differs and is
considered incompatible.
?>
[/php]

```

Give the class the same default the trait has and it compiles fine. This value-sensitivity is specific to the class-vs-its-own-traits relationship: it doesn't apply to trait-vs-parent, which never checks at all.

### Two traits, same property: fatal only if the defaults differ

Identical defaults compile without complaint. As with constants, there's no `insteadof`/`as` for properties: that syntax only resolves method conflicts. The only fix is declaring the property explicitly on the class.

### Shadowing a private property doesn't override it — it duplicates it

This is the one with no equivalent anywhere in the constants article, because constants aren't storage and can't be duplicated. Properties can:

```
object(ChildF)#1 (2) {
["x":"ParentF":private]=>
string(8) "parent-x"
["x":"ChildF":private]=>
string(7) "child-x"
}
?>
[/php]

```

Both `$x` properties are alive in the same object, in two separate storage slots, each visible only from methods declared in its own class. `dumpParent()` reads `ParentF`'s slot, `dumpChild()` reads `ChildF`'s slot — from the outside this looks like one property, but it's genuinely two. This only happens with `private`; `protected` and `public` properties with the same name really do get overridden, one slot, like you'd expect.

### Static properties: shared storage unless you redeclare

Skip redeclaring a static property in the child and it isn't "inherited" in the copy sense at all: it's the literal same storage cell, and writes through either class name are visible through the other. Redeclare it, even with the same value, and that link is severed:

Nothing in the constants model prepares you for this, because constants have no concept of a write happening after declaration: this is a live, mutable, shareable cell, not a value baked in at compile time.

### readonly blocks a subclass from re-touching an already-set property

```
Error: Cannot modify readonly property ParentI::$foo
?>
[/php]

```

The lock applies per-property, not per-class: once `parent::__construct()` has set it, no code anywhere, including a subclass constructor, gets a second write.

### Uninitialized typed properties throw exceptions, they don't default to null

```
Error: Typed property C::$x must not be accessed before initialization
?>
[/php]

```

This isn't strictly a resolution-order rule, but it's a failure mode constants simply cannot have, so it is worth mentioning here. A constant always has a value the moment the class is compiled. A typed property without a default can exist, structurally, in a state where reading it is an error.

## Calling up the chain: self:: vs static::

Same late-static-binding split as constants, but with real consequences now that static properties are shared storage rather than fixed values:

`self::$foo`, written inside `ParentH`, always reads `ParentH`'s cell, no matter which subclass calls the method. `static::$foo` reads whichever class's cell is actually in play. Combined with the storage-sharing rule above, this means `self::` and `static::` can end up pointing at two different cells entirely, not just two different values.

## Side note reminder: enums

This is the sharpest three-way contrast of the whole series. Enums can declare their own constants freely, and they can `implement` interfaces just like classes: but they cannot declare properties at all, of any kind:

Not instance properties, not static ones, not hooked ones: the baillon is total. An enum case is meant to be a fixed, singleton value; giving it mutable storage would undermine that guarantee, so PHP forbids the whole category rather than picking rules for it. Where the constants article found enums following the same rules as classes, properties are where enums diverge completely.

## The mental model

For class constants, "closest wins" was nearly the whole story, with one wrinkle: same-distance conflicts, like two interfaces or two traits, need an explicit tiebreaker because there's no storage to fall back on: only values, and values don't merge. Properties keep the "closest wins" rule for which definition is visible, but add a second, independent question the constants model never had to ask: is it the same storage, or two different storages wearing the same name?

Public and protected inheritance answer that question the boring, expected way: one property, always redefined identically. Private inheritance answers it the surprising way: two properties, coexisting, as dizigot twins. Static properties answer it based on whether the child bothered to redeclare, one shared cell, or two independent ones. Interfaces, which fully participated in the constants story, sit almost entirely outside the properties story, contributing shape through hooks but never storage. Knowing which value resolves is only half of understanding a property; the other half is knowing which piece of memory it actually lives in.

In the end, it is a zoo with very diverse animals.