Modern WordPress Development: You should throw an exception when you encounter a WP_Error
WordPress methods like wp_insert_user
don’t throw exceptions when they fail, but instead — and in some cases only if you tell the method to — return a WP_Error object. This class has all the hallmarks of error handling — messages and status codes, and so on — but, purely because exceptions aren’t thrown, I’m not sure I can argue for its use when your code really depends on it.
What’s more, I’m not sure its use flies for folks who are just trying to write sufficiently simple code.
Whether you’re playing for low CRAP scores in your unit testing, or you’re just trying to do good by your friends at work, leaning hard on the WP Error class means that features you build around critical points, like new user creation, are ripe with if
or switch
condition checking — if they’re not, you’re just not doing your due diligence — which add layers of unnecessary complexity.
Although I don’t go so far as to say WP Error sucks, I’m not the first to bring this up. This debate as to whether WP Error should throw exceptions has been already brought to the WordPress core community, who made — I think — a sane argument for keeping WP Error as-is.
Wait, really? Yup. In short: WP Error predates PHP Exception Handling, so the WordPress Community at the time created its own solution that has since become convention —part of the WordPress way. There is another debate outside the scope of this writeup about how — or whether — the WordPress way is diverging from modern application development, if you’re looking for some #hotdrama.
Still, for my part, where I am leading a refactor of our internal but critical WordPress apps at WhereBy.Us, we’re watching for returning WP_Error objects for one purpose: to throw that exception.
Take a modern but real-world example where we are using WordPress as part of a larger service-provision stack, where we need to create a WordPress user with a smorgasbord of custom fields. A lot of cool stuff depends on this user creation event, so when it works we want high confidence that something didn’t screw-up along the way.
And why might something screw up? Well, in WordPress, only basic properties of a user live in the wp_users table in the database, while all additional, custom properties — called usermeta — live in another. What’s more, unless you’re inserting users into both tables directly (which I actually like, but we’re not doing just yet), user creation via wp_insert_user
and update_user_meta
will make a ton of database queries and are points of failure that, if they bork, return a WP_Error.
Something like this could only partially succeed without throwing-up any red flags:
$wordPressUserId = wp_insert_user($anArrayOfArguments);
update_user_meta($wordPressUserId, $metaName1, $metaValue1);
update_user_meta($wordPressUserId, $metaName2, $metaValue2);
update_user_meta($wordPressUserId, $metaName3, $metaValue3);
update_user_meta($wordPressUserId, $metaName4, $metaValue4);
update_user_meta($wordPressUserId, $metaName5, $metaValue5);
update_user_meta($wordPressUserId, $metaName6, $metaValue6);
— so, you could check for an error with is_wp_error
:
$wordPressUserId = wp_insert_user($anArrayOfArguments, true);
if (is_wp_error($wordPressUserId){ /* do something */ }$result1 = update_user_meta($wordPressUserId, $metaName1, $metaValue1);
if (is_wp_error($result1) { /* do something */ }$result2 =update_user_meta($wordPressUserId, $metaName2, $metaValue2);
if (is_wp_error($result2) { /* do something */ }$result3 =update_user_meta($wordPressUserId, $metaName3, $metaValue3);
if (is_wp_error($result3) { /* do something */ }$result4 =update_user_meta($wordPressUserId, $metaName4, $metaValue4);
if (is_wp_error($result4) { /* do something */ }$result5 =update_user_meta($wordPressUserId, $metaName5, $metaValue5);
if (is_wp_error($result5) { /* do something */ }$result6 = update_user_meta($wordPressUserId, $metaName6, $metaValue6);
if (is_wp_error($result6) { /* do something */ }
This becomes exponentially more complex if you want to respond to different errors in unique ways, or want to delete the user and its custom properties that were successfully created up to the point of a failure.
We’ve addressed this by writing our own WordPress wrapper methods (where we need to) and throwing exceptions if the return value is a WP_Error.
First, our wrapper for wp_insert_user
:
private function insertUser(string $email, $firstName, $lastName) : int
{
if (!function_exists('wp_insert_user')) {
return -1;
}
$wordPressUserArguments= array(
'user_email' => strtolower($email),
'user_login' => strtolower($email),
'user_pass' => null,
'first_name' => $firstName,
'last_name' => $lastName
);
$results = wp_insert_user($wordPressUserArguments, true);
if (is_a($results, 'WP_Error')) {
throw new \Exception();
}
return $results;
}
— and our wrapper for update_user_meta
:
private function updateUserMeta($id, $metaName, $metaValue) : int
{
if (!function_exists('update_user_meta')) {
return -1;
}
$results = update_user_meta($id, $metaName, $metaValue);
if (is_a($results, 'WP_Error')) {
throw new \Exception();
}
return $results;
}
Note: if you want wp_insert_user
to return any error at all, you need to pass the boolean true
as its second argument. Otherwise — I think, I’m not looking it up right now — it returns just a 0
.
Idea: you could totally pass the message that comes back with WP_Error as the exception message.
So, now, because our insertUser
and updateUserMeta
methods return exceptions, we’re able to lean-in on basic PHP Exception Handling and move our creation attempt into try / catch
blocks.
public function createNewMember(MemberDto $dto) : int
{
$email = $dto->wordPressUserEmail;
$firstName = $dto->wordPressUserFirstName;
$lastName = $dto->wordPressUserLastName;
$response = null;
try {
$response = $this->insertUser($email, $firstName, $lastName);
$this->updateUserMeta($response, 'someValue1', $dto->someValue1);
$this->updateUserMeta($response, 'someValue2', $dto->someValue2);
$this->updateUserMeta($response, 'someValue3', $dto->someValue3);
$this->updateUserMeta($response, 'someValue4', $dto->someValue4); } catch (\Exception $e) {
$response = -1; /* or do something more interesting */
}
return $response;
}
In this way, no matter how many custom fields you choose to add in the future, as soon as one fails our app will catch that exception and do something interesting with it. You’re able to keep your functions simple, diligent, and devoid of complicated-to-deal-with, complicated-to-test, and complicated-to-read if
statements.
Clapping (👏) is a super way to brighten my day. Check out my podcast Metric: The User Experience Design Podcast (right here on Medium), and consider subscribing to my newsletter Doing User Experience Work. ❤
I don’t often blog about code, but if you think I should, please let me know.