Originally published on decentphp.dev — part of the Writing decent PHP course. View this lesson on the original site.
A class name should describe a single concept
A class name is a noun. The reader should be able to say "an instance of this is a ___" and have a clear answer.
When a class is named Manager, Helper, Util, or Processor, that sentence becomes "an instance of this is a manager". A manager of what? Doing what? With which dependencies?
Vague class names are usually a symptom of a class that does too many things. Better names force you to name the responsibility, and that often reveals the class should be split.
Before
Each class compiles, but the names hide what they actually do.
<?php
class UserManager
{
public function createUser(array $input): void { /* validates + saves + sends welcome email */ }
public function suspendUser(int $userId): void { /* updates status + logs audit + revokes sessions */ }
public function check(int $userId): bool { /* checks if subscription is active */ }
}
class OrderHelper
{
public function getTotal(array $items): int { /* calculates total in pence */ }
public function format(array $order): string { /* renders order as HTML */ }
public function send(array $order): void { /* posts to fulfilment API */ }
}
class DataProcessor
{
public function run(array $records): array { /* transforms and writes to database */ }
}
Reading the class names alone, you have no idea:
- what
UserManageractually owns — auth? billing? profile? - whether
OrderHelperreads, writes, or talks to a remote system - what
DataProcessoreven does — "process" and "data" together carry zero information
After
The names describe a single, specific concept. Once they do, the methods that do not fit become obvious — and tend to move out into their own class.
<?php
final class UserRegistration
{
public function register(array $input): void { /* validates + saves + sends welcome email */ }
}
final class UserSuspension
{
public function suspend(int $userId): void { /* updates status + logs audit + revokes sessions */ }
}
final class SubscriptionStatus
{
public function isActive(int $userId): bool { /* checks if subscription is active */ }
}
final class OrderTotalCalculator
{
public function calculateInPence(array $items): int { /* calculates total in pence */ }
}
final class OrderHtmlRenderer
{
public function render(array $order): string { /* renders order as HTML */ }
}
final class FulfilmentApiClient
{
public function submitOrder(array $order): void { /* posts to fulfilment API */ }
}
The class names now act as section headers in the codebase. You can scan a directory and predict what lives where without opening a single file.
Why this version is better
- the class name describes one concept, so the responsibility is obvious
- "where do I add billing logic?" stops being a guess
- splitting
UserManagerinto three smaller classes makes each one easier to test - generic suffixes (
Manager,Helper) are replaced with words that say what the class does
Class naming patterns that pull their weight
| Suffix | Use it when | Example |
|---|---|---|
Repository |
reads/writes a single aggregate to storage | InvoiceRepository, UserRepository |
Factory |
builds instances of a value object or entity | OrderFactory, JwtTokenFactory |
Builder |
step-by-step construction of a complex object | EmailMessageBuilder |
Validator |
checks input and reports errors | SignupInputValidator |
Calculator |
derives a value from inputs | OrderTotalCalculator, ShippingCostCalculator |
Renderer / Formatter |
turns data into a presentation form | OrderHtmlRenderer, MoneyFormatter |
Client |
talks to a specific external system | StripeClient, FulfilmentApiClient |
Policy |
makes a yes/no business decision | RefundPolicy, AccessPolicy |
Mapper |
converts between two shapes | OrderToInvoiceMapper |
Listener / Subscriber |
reacts to events | OrderShippedListener |
These suffixes earn their keep because they tell you both the responsibility and the role in the system.
Suffixes to be careful with
Service— fine in moderation, but tends to become a bucket. If you cannot describe what the service does in one short sentence, the name is hiding something.Manager— almost always vague. Replace with a verb-noun describing the actual responsibility.Helper,Util,Utility,Common,Misc— bins for unrelated functions. If a class is named after a bin, it will be filled like one.Handler,Processor— see lesson 2.4:processandhandleare vague verbs and the same applies to class names built on them.Data,Info— these are nouns of nothing.UserDataandUsershould not both exist.
A small class smell test
Try to finish this sentence about your class in one short, concrete phrase:
"An instance of this class is a __________ that __________."
UserRegistration→ "a user registration that turns input into a saved user with a welcome email" — clearUserManager→ "a user manager that... manages users" — uselessDataProcessor→ "a data processor that processes data" — definitionally empty
If you cannot finish the sentence with one specific noun and one specific verb, the class is doing too much, the name is too vague, or both.
From Writing decent PHP — Naming variables, functions, classes, and files well