PHP only allows a class to extend one parent class — traits exist specifically to work around that limitation, letting a chunk of reusable method code be mixed into any number of otherwise unrelated classes, each of which can still only have one real parent class if it needs one.
1. Defining and using a trait
trait Loggable
{
public function log(string $message): void
{
Log::info(static::class . ': ' . $message);
}
}
class OrderService
{
use Loggable;
public function process(Order $order): void
{
$this->log("Processing order #{$order->id}");
// ...
}
}
use Loggable; inside the class body (not a namespace use import) copies the trait's methods directly into the class as if they'd been written there — the class gains a working log() method without inheriting from any particular parent class at all.
2. Using multiple traits in one class
class OrderService
{
use Loggable, HasCache, ValidatesInput;
}
This is exactly the flexibility a single-parent class hierarchy can't offer — a class can pull in behavior from several independent traits at once, none of which need to be related to each other or fit into a single inheritance chain.
3. Traits can have abstract methods
trait Loggable
{
public function log(string $message): void
{
Log::info($this->logPrefix() . ': ' . $message);
}
abstract protected function logPrefix(): string;
}
class OrderService
{
use Loggable;
protected function logPrefix(): string
{
return 'OrderService';
}
}
A trait can declare a method as abstract, requiring any class using it to provide its own implementation — this lets a trait depend on class-specific behavior while still providing shared, common logic itself, similar in spirit to how an abstract class works, just without the single-inheritance constraint.
4. Resolving a naming conflict between two traits
trait A { public function hello() { echo 'A'; } }
trait B { public function hello() { echo 'B'; } }
class C
{
use A, B {
A::hello insteadof B;
B::hello as helloFromB;
}
}
If two traits used in the same class define a method with the same name, PHP requires this explicit conflict resolution — insteadof picks which trait's version wins for the original method name, and as gives the other one an alias so it's still accessible under a different name, rather than being silently lost.
5. Where Laravel itself uses traits extensively
class Post extends Model
{
use HasFactory, SoftDeletes;
}
HasFactory and SoftDeletes are both traits, not base classes — this is exactly why a model can use both simultaneously while still extending only Model as its single parent class. Laravel's framework code relies on this pattern heavily precisely because a model needing several independent pieces of optional, reusable functionality couldn't get all of them through single inheritance alone.
6. Traits vs. interfaces vs. inheritance — when to reach for which
An interface defines a contract (what methods a class must have) with no actual implementation. Inheritance (extends) is for a genuine "is-a" relationship with one specific parent. A trait is for sharing actual, reusable implementation code across classes that aren't related by inheritance at all — reaching for a trait when the real relationship is closer to "is-a" (better expressed with inheritance) is a common misuse; traits work best specifically for cross-cutting, optional behavior like logging, caching, or soft-delete handling that many otherwise-unrelated classes might want to opt into.