adci/full-name-parser
A PHP package for parsing full names into first, middle, last, prefix, and suffix components. It handles common name formats, titles, and suffixes, making it easy to split user-provided names for storage, search, or display in Laravel and other PHP applications.
Install with Composer (no Laravel auto-discovery, no config file, no service provider):
composer require adci/full-name-parser
The entire public surface is two classes: ADCI\FullNameParser\Parser and the Name value object it returns. Instantiate the parser once and call parse():
use ADCI\FullNameParser\Parser;
$parser = new Parser();
$name = $parser->parse('Dr. Juan de la Cruz Jr.');
$name->getSalutation(); // "Dr."
$name->getFirstName(); // "Juan"
$name->getMiddleName(); // "de la"
$name->getLastName(); // "Cruz"
$name->getSuffix(); // "Jr."
Look first at src/Parser.php (the splitting heuristics) and the returned Name object's getters — that is the complete API you will build against. Then skim the package's test fixtures: they document exactly which name shapes the parser handles (compound surnames, quoted nicknames, suffixes), which is more useful than the README when deciding whether to trust it for your data.
First realistic use case: a CRM/contacts table with a single full_name column, where you need sortable last_name and personalized greetings (first_name). Parse on write, keep the raw string, and store the parts alongside it.
Register it as a singleton so you don't rebuild the parser per row:
// AppServiceProvider::register()
$this->app->singleton(\ADCI\FullNameParser\Parser::class, fn () => new \ADCI\FullNameParser\Parser());
Wrap it in your own service, never call it from controllers/models directly. The package has no interface and no injection points; a thin NameParser wrapper lets you swap it later and gives you one place for normalisation and fallback logic.
final class ParsedName
{
public function __construct(
public readonly ?string $salutation,
public readonly ?string $first,
public readonly ?string $middle,
public readonly ?string $last,
public readonly ?string $suffix,
) {}
}
final class NameParser
{
public function __construct(private \ADCI\FullNameParser\Parser $parser) {}
public function parse(string $full): ParsedName
{
$n = $this->parser->parse(trim($full));
return new ParsedName(
$n->getSalutation() ?: null,
$n->getFirstName() ?: null,
$n->getMiddleName() ?: null,
$n->getLastName() ?: null,
$n->getSuffix() ?: null,
);
}
}
Parse at the boundary, persist normalised. Do the parsing in a form request, an import job, or a model observer — not on every read. Persist full_name (source of truth) plus first_name, middle_name, last_name, salutation, suffix. That way re-parsing later (e.g. after a parser upgrade) is a backfill job, not a data loss event.
Use it as a triage signal, not a verdict. A good pattern for imports is to flag rows where the parser produced something suspicious:
$parsed = $this->nameParser->parse($row['name']);
$needsReview = $parsed->last === null || $parsed->first === null;
Batch imports: chunk the source (->chunkById(500, ...)), reuse the same singleton parser, and collect failures rather than aborting the whole import on one weird name.
Sorting and search: once last_name is populated, index it. For display, keep a formatted_name() accessor that prefers the original full_name and falls back to recombining the parts — the parser can "correct" a name into something the user never typed (e.g. moving "de la" into the middle name), so don't silently replace what they entered.
Testing: use a PHPUnit data provider with the real-world names you care about (your actual customer base, not just John Smith). This is the cheapest way to discover the package's failure modes before production does.
null when a part is absent; : string type hints and strlen() calls will blow up. Coalesce (?: null) at the boundary as shown above.parse('Cher') will put the token in one field and leave the rest null — decide what "last name" means for your UI before this becomes a bug report.parse().III, Jr., Ph.D. and a surname like "Young" are indistinguishable without a dictionary; verify getSuffix() before trusting the split."Bob") are recognised by dictionary — anything outside it (e.g. "Rev. Dr.", emoji, honorifics in other languages) will leak into the first-name field.mb_* handling is on you, not the package.Parser per row in a 100k-row import is wasted work; bind it as a singleton.composer.json, add your own tests around the behaviour you depend on, and verify it on your PHP version (8.x) before committing — the algorithm is simple enough to vendor or fork if a PHP upgrade ever breaks it.Parser if the internals allow it. Keep the wrapper in place so either is a one-file change.Name object rather than individual getters — it shows you which dictionary rule fired and makes the failure obvious.How can I help you explore Laravel packages today?