Build a PHP Phone Number Validator Using Twilio and laminas-validator
Time to read: 7 minutes
No matter where your data comes from — a user, database, an environment variable, or somewhere else entirely — it must be validated. If not, how can you be sure that it's valid?
This is such an important and accepted part of modern software development that all of the major PHP frameworks, such as Laravel, Symfony, and Mezzio provide a validation component. If you're not using a framework, Composer can quickly integrate a third-party library such as laminas-validator.
However, what if a validation library or a framework's validation component doesn't support a validator that matches your use case? In that case, it's time to write a custom validator.
In this tutorial, I'm going to show you how to write a custom phone number validator for laminas-validator using Twilio's Lookup API.
Tutorial Requirements
To follow this tutorial you need the following components:
- PHP 7.4.
- A free Twilio account. If you are new to Twilio click here to create a free account now and receive a $10 credit when you upgrade to a paid account.
- Composer installed globally.
Why write a custom phone number validator?
In my fictitious small business, The Little PHP Shop, customers can register for an account using an email address and phone number. laminas-validator already supports email address validation, but it doesn't support phone number validation. Given that, we need to write a custom validator.
Validating phone numbers isn't an easy task, partly because there are just so many ways to write valid ones. For example, in the list below you can see various combinations of valid German phone numbers.
- +49 123 4567 1234
- +49 123 45671234
- +49 (0)123 45671234
- +49 12345671234
- +4912345671234
- 004912345671234
- 0123 4567 1234
- 0123 45671234
- 012345671234
Bearing in mind that this is just one country, I hope you see that it could be quite complex to code up a solution to validate phone numbers from every country in the world covering all of their possible permutations.
So to avoid that path of rabid complexity, in this tutorial I'm going to show you how to use Twilio's Lookup API to implement phone number validation in a comparatively simple way.
Before I describe how if you're not familiar with the Lookup API, here's a quick overview:
The Lookup API provides a way to retrieve additional information about a phone number.
The API returns two things if a phone number is valid:
- An HTTP 200 status code, showing that the request succeeded.
- A JSON payload containing a range of details about the number. It includes the carrier, phone number type, and phone number in multiple formats, which you can see below.
If the number isn't valid, then an HTTP 404 status code is returned, along with a JSON response body with applicable details as to why and what to do.
In short, how the validator will then work is like this:
- It will make a request to the Lookup API
- If the request returns phone number details, then we use that as confirmation that the phone number exists and is therefore valid.
- If it returns an error, then it's safe to say that the phone number does not exist, and is not valid.
To simplify interacting with the Lookup API, we're going to use Twilio's PHP helper library. Let's begin!
Setup the project directory
The first thing we need to do is to create the project directory structure and then change into the new directory. To do that, run the commands below. If you are using a Unix or macOS computer:
If you're using Microsoft Windows, use the commands below instead.
Write the code
Create a Composer configuration
Next, in the project's root directory, create a new file named composer.json, and paste the code below into it.
The configuration sets up a PSR-4 autoloaded namespace for our code Settermjd\Validator
which points to src, and the required versions of PHP.
Install the required dependencies
Next, we need to install the project's dependencies:
- Twilio's PHP Helper Library: You need this to interact with Twilio's Lookup API.
- PHP dotenv: This avoids storing your Twilio credentials in with the PHP code, which is one of the 12 principles of modern application design.
- laminas-validator: as we're building a custom Laminas validator, then we need to base classes so that we can build upon them.
Run the command below, in the root directory of the project, to install them. We could have added them to composer.json in the previous step, but using composer require will ensure that the latest versions are installed—no matter when you follow this tutorial.
Retrieve your Twilio credentials
Next, you need to retrieve your Twilio credentials so that the validator can make authenticated requests to Twilio's Lookup API. You will store these credentials in a new file named .env. Create the file in the root directory of the project, then paste the code below into it.
Next, from the Twilio Console's Dashboard, copy your "Account SID" and "Auth Token" and paste them in place of the respective placeholder values in .env.
Create the phone number validator
Next, create a new file named PhoneNumber.php in src/Twilio/ and copy the code from GitHub into it. Then, let's step through it.
It starts by setting the namespace and importing the required classes.
After that, it defines a new class, PhoneNumber
which extends Laminas\Validator\AbstractValidator
. We're doing this as AbstractValidator
greatly simplifies implementing a custom laminas validator.
PhoneNumber
then defines two constants and an array named $messageTemplates
. The array defines message templates for when a phone number cannot be validated, letting us return a different message based on whether the phone number being validated is in national or international format.
It then defines two private class member variables:
$client
: This will store the TwilioClient
object which we'll use to make requests to Twilio's Lookup API.$countryCode
: This, optionally, stores the phone number's country code, which is required if the number is in national format.
After that, it defines the class' constructor. The method takes one parameter, an array named $options
, which contains one or more initialization options. At most, the constructor will make use of two: an initialized Client
object and a country code. These are used to initialize the applicable class member variables if present.
Next, it defines the isValid
method, required by AbstractValidator
, where we define the validation logic. The role of the method is to return true
if the value passed to it validates, and false
otherwise.
Here's how it will work. It takes one argument, $value
, which is a string containing the phone number to validate. If the phone number is empty, the code returns false
immediately. It does this as an empty phone number, whether national or international, would always return an HTTP 404. Given that, there's no point wasting the user's time by making a request to Twilio that is bound to fail.
If the phone number isn't empty, it initializes an array, $fetchOptions
, based on the number format being validated.
In the table below, you can see how it needs to be composed, based on the phone number's format.
International Number |
National Number |
[ "type" => ["carrier"], ] |
[ "type" => ["carrier"], "countryCode" => "US", ] |
Finally, a request is made to Twilio's Lookup API wrapped in a try/catch block. This is because if the request returns an HTTP 404, then a TwilioException
is thrown. If one is thrown, then we know that the number isn't valid. Here, we set the appropriate error message, based on whether the country code was set and return false
. However, if the request succeeds, true
is returned.
Before we test the code, there's one final thing I want to cover. As the request returns phone number information in the response, it might seem wasteful to throw that information away. However, using the Lookup API can be far less work than creating a regular expression capable of validating every possible phone number yourself. Plus, the Lookup API doesn't have a dedicated validation endpoint.
This is especially the case when you consider that requests for carrier and caller name information aren't free; Carrier information costs $0.005 per phone number looked up. Caller name information (currently available only in the US) costs $0.01 per phone number looked up.
Test the code
Now that the code's written, it's time to test that it works. Create a new file, named index.php in the root directory of the project. In it, paste the code below.
Replace the phone number placeholder, 'xxxxxxxxxxxxxx', with a valid international phone number, such as "+14151231234". Then, in the terminal, run the command below to test that the validator works. If successful, you should see "Is valid." printed to the terminal.
Now, let's test it using a valid national number. First, replace the phone number you used with a phone number in national format, such as "0417111111".
Secondly, add a second key/value pair to the array passed to PhoneNumber
's constructor, where the key's name is countryCode
and its value is the two-letter country code of the phone number you're validating, so that it resembles the example below.
With the changes made, run the command below to validate the number.
If you've entered a valid combination of phone number and country code, then you should see "Is valid." printed to the terminal.
Finally, let's test the validator using an invalid phone number. Replace the phone number you used with "+1" and run the command above, again. This time, you should see "Is not valid." printed to the terminal.
That's how to build a PHP phone number validator using Twilio
There's a lot more functionality that we could have tapped into. However, our custom validator does what we need. If the available laminas validators don't quite do what you need, why not have a go at writing a custom one yourself.
I'd love to see what you build!
Matthew Setter is a PHP Editor in the Twilio Voices team and a polyglot developer. He’s also the author of Mezzio Essentials and Docker Essentials. When he’s not writing PHP code, he’s editing great PHP articles here at Twilio. You can find him at msetter@twilio.com; he's also settermjd on Twitter and GitHub.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.