Pop PHP
The Toolkit

Name & Address Parsing

pop-parser splits a string a person typed into fields. NameParser turns Dr. John Michael Smith Jr. into a salutation, first, middle and last name and a suffix; AddressParser turns 123 Main St Apt 4B, Springfield, IL 62704 into street number, name, route type, unit, city, state and postal code. Both are native — no third-party parsing library.

BASH
composer require popphp/pop-parser

Parsing a Name#

Construct, parse, read the parts off:

PHP
use Pop\Parser\Name\NameParser;

$parser = new NameParser();
$parser->parse('Dr. John Michael Smith Jr.');

$parser->getSalutation();  // 'Dr.'
$parser->getFirstname();   // 'John'
$parser->getMiddlename();  // 'Michael'
$parser->getLastname();    // 'Smith'
$parser->getSuffix();      // 'Jr'

The string can go to the constructor instead, in which case parse() takes no argument. Both forms are equivalent, and the constructor form is the one to reach for when the name arrives with the object:

PHP
use Pop\Parser\Name\NameParser;

$parser = new NameParser('John Smith');
$parser->parse();

A comma anywhere in the input switches the parser to Last, First Middle[, Suffix] — the shape a database export or a sorted list hands you — with no flag to set:

PHP
use Pop\Parser\Name\NameParser;

$parser = new NameParser();
$parser->parse('Smith, John Michael, Jr');

$parser->getFirstname();  // 'John'
$parser->getLastname();   // 'Smith'
$parser->getSuffix();     // 'Jr'

Salutations are normalized rather than echoed back. mister, master and a bare lowercase mr all come out as Mr., and MRS comes out as Mrs., so the value is safe to compare against or store without a second pass of your own.

Three things that look like name parts come out separately: a recognized lastname prefix (van, von, de), a single letter as an initial, and a parenthetical or quoted nickname.

PHP
use Pop\Parser\Name\NameParser;

$parser = new NameParser();
$parser->parse('Ludwig van Beethoven');

$parser->getLastnamePrefix();  // 'van'
$parser->getLastname();        // 'Beethoven'
$parser->getFullName();        // 'Ludwig van Beethoven'

$parser->parse('J.R. "Bob" Smith');

$parser->getFirstname();       // 'J'
$parser->getInitials();        // 'R'
$parser->getNickname();        // 'Bob'
$parser->getNickname(true);    // '(Bob)'

getFullName() reassembles the given name, the prefix and the last name; getGivenName() stops after the first name, initials and middle name. Casting to a string gives the whole thing back including the salutation and suffix, and toArray() gives every field at once with null where nothing was found:

PHP
use Pop\Parser\Name\NameParser;

$parser = new NameParser('John Michael Smith Jr');
$parser->parse();

$parser->getGivenName();  // 'John Michael'
$parser->getFullName();   // 'John Michael Smith'
(string)$parser;          // 'John Michael Smith Jr'

$parser->toArray();
// ['salutation' => null, 'firstname' => 'John', 'initials' => null, 'middlename' => 'Michael',
//  'nickname' => null, 'lastnamePrefix' => null, 'lastname' => 'Smith', 'suffix' => 'Jr']

Every getter has a matching has*()hasSuffix(), hasNickname(), hasMiddlename() — which returns true only when that part was actually found. Use those rather than testing the getter against null, because they say what you mean.

A single-word input becomes a first name, so check hasLastname() and fall back to getFirstname() when you are normalizing a surname column.

The only input that raises is one with nothing in it. parse() with no data set anywhere, or with a string that normalizes to empty, throws Pop\Parser\Exception with "Error: You must pass a name string to the parser object."

Parsing an Address#

The same shape, with more fields:

PHP
use Pop\Parser\Address\AddressParser;

$parser = new AddressParser();
$parser->parse('123 Main St Apt 4B, Springfield, IL 62704');

$parser->getStreetNumber();  // '123'
$parser->getStreetName();    // 'Main'
$parser->getRouteType();     // 'St'
$parser->getUnit();          // 'Apt 4B'
$parser->getCity();          // 'Springfield'
$parser->getStateCode();     // 'IL'
$parser->getStateName();     // 'Illinois'
$parser->getPostalCode();    // '62704'
$parser->getCountry();       // 'US'

Separate the parts with a comma, semicolon, tab or newline — all four delimit, so a one-line address and a mailing-label one parse the same way. The unit can sit on the street segment or stand on its own, so 123 Main St Apt 4B, Springfield, IL 62704 and 123 Main St, Apt 4B, Springfield, IL 62704 give the same result.

A leading or trailing directional is recognized and kept apart from the street name. getStreetName() includes it by default; pass false for the bare name:

PHP
use Pop\Parser\Address\AddressParser;

$parser = new AddressParser();
$parser->parse('456 N Elm Street, Chicago, IL 60601');

$parser->getDirection();        // 'N'
$parser->getStreetName();       // 'N Elm'
$parser->getStreetName(false);  // 'Elm'
$parser->getRouteType();        // 'Street'

PO boxes are matched in all the forms people write them — PO Box 1234, P.O. Box 1234, POB 1234, Box 1234. The box lands in getStreetName(), getStreetNumber() stays null, and isPoBox() says which kind of address you have:

PHP
use Pop\Parser\Address\AddressParser;

$parser = new AddressParser();
$parser->parse('PO Box 1234, Springfield, IL 62704');

$parser->isPoBox();        // true
$parser->getStreetName();  // 'PO Box 1234'
$parser->getStreetNumber();// null
$parser->getCity();        // 'Springfield'

Canadian addresses parse without a flag — the province is resolved the same way a state is, and getCountry() reports 'CA'. The postal code comes back with its space removed, so H3Z 2Y7 is stored as H3Z2Y7 and needs reformatting before it goes on a label:

PHP
use Pop\Parser\Address\AddressParser;

$parser = new AddressParser('55 Rue Principale, Montreal, QC H3Z 2Y7');
$parser->parse();

$parser->getStateName();   // 'Quebec'
$parser->getStateCode();   // 'QC'
$parser->getPostalCode();  // 'H3Z2Y7'
$parser->getCountry();     // 'CA'

A US ZIP+4 splits across two fields rather than staying in one, which is what a database with separate columns wants:

PHP
use Pop\Parser\Address\AddressParser;

$parser = new AddressParser('123 Main St, Springfield, IL 62704-1234');
$parser->parse();

$parser->getPostalCode();  // '62704'
$parser->getZip4();        // '1234'
$parser->hasZip4();        // true

getFullAddress() puts it back together, and its three arguments control the delimiter, whether the state appears as a code or a full name, and whether the country is appended:

PHP
use Pop\Parser\Address\AddressParser;

$parser = new AddressParser('123 Main St Apt 4B, Springfield, IL 62704');
$parser->parse();

$parser->getFullAddress();
// '123 Main St, Apt 4B, Springfield, IL 62704'

$parser->getFullAddress(', ', false, true);
// '123 Main St, Apt 4B, Springfield, Illinois 62704, US'

Casting to a string is getFullAddress() with its defaults, and toArray() returns all eleven fields. As with names, every getter has a has*() companion.

A parser is reusable: pass a new string to parse() and it replaces both the parsed fields and the stored data, so a later bare parse() re-parses the string you last gave it.

Reference Data#

The lookup tables the parsers work from are public, which is what you want when a form has to validate against the same data the parser will later accept. Both classes are instantiated, not static:

PHP
use Pop\Parser\Address\AddressValues;
use Pop\Parser\Name\NameValues;

$values = new AddressValues();

$values->getStates();          // ['AL' => 'Alabama', 'AK' => 'Alaska', ...] — 52 US entries
$values->getStates('CA');      // the 13 Canadian provinces and territories
$values->getStateCodes();      // the keys alone
$values->getCommonRouteTypes();// the 38 everyday route types
$values->getRouteTypes();      // all 503, mapping each spelling to its abbreviation
$values->getDirections();
$values->getUnitTypes();

$names = new NameValues();

$names->getSalutations();      // ['mr' => 'Mr.', 'master' => 'Mr.', ...]
$names->getSuffixes();
$names->getLastnamePrefixes();
$names->getNicknameDelimiters();

getStates(), getStateCodes() and getStateNames() take a country of 'US' or 'CA', defaulting to 'US'. getSalutations() is a map — several spellings point at one canonical form — so populate a select from array_unique() over its values.

See Also#