Inheritance says "is a", composition (Composition) says "has a", and a trait says "contains a copy of". A trait is not a type, so pair it with an interface: class Product implements HasSlug { use SlugFromTitle; } passes every HasSlug check, and the trait is merely a default implementation that another class may replace.
| Question | Inheritance | Trait | Composition |
|---|---|---|---|
| Creates a type? | Yes | No (pair it with an interface) | Via the collaborator's interface |
| Swap at run time? | No | No | Yes, inject another object |
| Sees private state? | Only protected | Yes, all of it | Only the public API |
| Test in isolation? | Through the child | Through a host class | Yes, with a fake |
Use a trait for a small, stateless helper (slugs, timestamps, toArray()) that recurs in unrelated classes. Code with dependencies or state of its own, such as a mailer or a cache, belongs in a collaborator passed to the constructor.