watson/nameable
A Laravel package for giving Eloquent models human-readable names through a polymorphic relationship. Add the trait to any model to store and retrieve names without repeating name columns across tables.
composer require watson/nameable
User, but it works on Customer, Contact, Member, etc.):
use Watson\Nameable\Nameable;
class User extends Model
{
use Nameable;
}
first_name and last_name. These are read from the model's attributes, so they can come from a base table, a joined table, or an accessor.$user->name_full; // "John Smith"
$user->name_familiar; // "John"
$user->name_abbreviated; // "J. Smith"
Where to look first: the package is tiny — read the trait source (src/Nameable.php) before anything else. The whole surface area is a handful of accessors, so the README plus the trait file is the entire mental model. There is no config file, service provider magic, or migration to run.
First use case: replace hand-rolled $user->first_name . ' ' . $user->last_name in Blade templates with $user->name_full. The immediate win is consistency — every view, mail, and notification renders names the same way, and you have one place to change formatting later.
Blade templates. Use the accessor at the point of display rather than pre-formatting in controllers:
{{-- Profile header --}}
<h2>{{ $user->name_full }}</h2>
{{-- Greeting in an email --}}
<p>Hi {{ $user->name_familiar }},</p>
{{-- Comment thread / avatar alt text --}}
<img src="{{ $user->avatar_url }}" alt="{{ $user->name_abbreviated }}">
API resources. Wrap the accessors in a resource so the contract is explicit and you can rename the JSON keys independently of the database:
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => [
'full' => $this->name_full,
'familiar' => $this->name_familiar,
'abbreviated' => $this->name_abbreviated,
],
];
}
}
Greetings and notifications. Pick the accessor based on tone. Transactional mail ("Hi Jane") is a good fit for name_familiar; formal documents and invoices read better with name_full; anything space-constrained (nav bars, mention chips, avatars) wants name_abbreviated.
Fallbacks for sparse profiles. Not every user will have a last name (mononyms, incomplete onboarding). Wrap reads in a small helper on the model or a presenter so views stay clean:
public function displayName(): string
{
return $this->name_full ?: $this->email;
}
Keep queries on the real columns. Sorting and filtering still happen against first_name and last_name — the accessors are pure presentation and never touch the query builder:
User::orderBy('last_name')->orderBy('first_name')->paginate();
Compose with other name logic. If you have a "display name" preference (e.g. a prefers_full_name setting), branch on it in a view component rather than overriding the trait:
$display = $user->prefers_full_name
? $user->name_full
: $user->name_familiar;
first_name and last_name. If your schema uses given_name / family_name, either add accessors mapping to the expected names or don't use the trait — there's little here to justify fighting the convention.User::where('name_full', 'John Smith') will fail. Any filter or sort must be expressed against the underlying columns, or you need a generated column / full-text index.name_full won't appear in toArray() / toJson() unless you add it to $appends. If your app relies on implicit serialization, either append the properties or (better) expose them through an API resource.first_name = 'Cher' and last_name = null can yield "Cher " or "C." depending on the method. Normalize with trim() in a wrapper, and decide deliberately what name_abbreviated should mean for a single-token name."Dr. John Q. Smith Jr." requires extending the model with your own accessors — treat the package as a starting point for the simple first + last case.name_full, name_familiar, or name_abbreviated on the model, the trait's method is shadowed by the model's own. That's useful for customization but easy to forget when debugging "why isn't the package's format used here?"HasName trait if you want to guarantee future maintenance or extend the formatting rules; the surface area is small enough that the migration cost is negligible.first_name / last_name explicitly in factories rather than relying on fake()->name(), which may produce a single string that won't split cleanly into the two columns.How can I help you explore Laravel packages today?