Traits
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints list phpstan and phpcpd, but automated detection is explicitly marked 'no'. phpstan can flag some trait-related issues (property conflicts cause fatal errors, method conflicts are caught), but the deeper misuse — hiding business logic in traits, hidden coupling, overuse as a substitute for proper abstraction — requires careful code review. The structural smell is not reliably surfaced by tooling alone.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix points to preferring composition/interfaces instead of traits, which means extracting trait behaviour into injected services or interfaces. This affects every class using the trait, potentially spanning multiple files. It is not a single-line swap, but it is also not a full architectural rework — landing at e5.
Closest to 'persistent productivity tax' (b5). Traits apply across web, cli, and queue-worker contexts (applies_to is broad). Once a trait carrying state or business logic is adopted by many classes, every future maintainer must understand the implicit dependencies it introduces. The misconception notes hidden coupling as the core hazard. This is a persistent tax on many work streams but does not fully define the system's shape — b5 is the right anchor.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception is explicit: developers treat traits as a clean multiple-inheritance substitute, missing the hidden coupling and fragility of stateful traits. The common_mistakes list property conflicts (fatal error) and unresolved method name conflicts as concrete gotchas. These are documented, learnable traps rather than catastrophic always-wrong behaviour — t5 fits well.
Also Known As
TL;DR
Explanation
PHP traits are a mechanism for code reuse in single-inheritance languages. A trait can contain methods (including abstract ones) and properties, and is included in a class with use TraitName. Conflicts between multiple traits are resolved explicitly. Traits are useful for cross-cutting concerns like logging, timestamping, or soft deletion. However, heavy trait usage can obscure where methods come from, increase coupling, and complicate testing — prefer composition (injected objects) for behaviour that varies, reserving traits for truly horizontal concerns.
Diagram
flowchart TD
subgraph Without_Traits
BASE2[Base class] -->|only one parent| CHILD[Child class<br/>cannot inherit from two classes]
end
subgraph With_Traits
T1[trait Timestampable<br/>created_at updated_at]
T2[trait SoftDeletable<br/>deleted_at restore]
T3[trait Auditable<br/>log changes]
MODEL2[Model uses T1 T2 T3<br/>all three behaviours]
T1 & T2 & T3 --> MODEL2
end
subgraph Conflict_Resolution
CONFLICT[Two traits same method] --> INSTEADOF[insteadof keyword<br/>choose which wins]
CONFLICT --> ALIAS[alias keyword<br/>rename one method]
end
style MODEL2 fill:#238636,color:#fff
style T1 fill:#1f6feb,color:#fff
style T2 fill:#6e40c9,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Using traits as a substitute for proper abstraction — if the behaviour belongs in a service or base class, use that instead.
- Defining properties in traits that conflict with the using class — PHP raises a fatal error.
- Importing multiple traits with conflicting method names without resolving the conflict with insteadof.
- Hiding business logic in traits where it is hard to discover — traits are best for cross-cutting technical concerns.
Avoid When
- Do not use traits to work around poor class hierarchy design — if two classes share a trait's entire interface, they probably need a shared base class or interface.
- Avoid traits with more than one clear responsibility — they become hidden coupling between unrelated classes.
When To Use
- Use traits to share behaviour across classes that cannot share a common parent — horizontal code reuse.
- Use traits for cross-cutting concerns like logging, serialisation helpers, or timestamp management.
Code Examples
// Code duplication across unrelated classes:
class User {
public function getCreatedAt(): DateTime { return $this->createdAt; }
public function getUpdatedAt(): DateTime { return $this->updatedAt; }
}
class Product {
public function getCreatedAt(): DateTime { return $this->createdAt; } // Duplicated
public function getUpdatedAt(): DateTime { return $this->updatedAt; } // Duplicated
}
// With trait:
trait HasTimestamps {
public function getCreatedAt(): DateTime { return $this->createdAt; }
public function getUpdatedAt(): DateTime { return $this->updatedAt; }
}
class User { use HasTimestamps; }
class Product { use HasTimestamps; }
// Trait for shared behaviour across unrelated classes
trait Timestamps {
public ?\DateTimeImmutable $createdAt = null;
public ?\DateTimeImmutable $updatedAt = null;
public function touch(): void {
$this->updatedAt = new \DateTimeImmutable();
}
}
trait SoftDeletable {
public ?\DateTimeImmutable $deletedAt = null;
public function delete(): void { $this->deletedAt = new \DateTimeImmutable(); }
public function restore(): void { $this->deletedAt = null; }
public function isDeleted(): bool { return $this->deletedAt !== null; }
}
class Order { use Timestamps, SoftDeletable; }
class Product { use Timestamps; }