As part of job interview, I was asked to write a cli script that takes a list of phone numbers to validate from a file and as an option in the command line. It will then produce a CSV file with three columns “phone number”, “carrier”, “status”.
The status column should indicate if the phone number is valid or not valid. The carrier field should contain the carrier name if the phone number is valid.
I am using libphonenumber-for-php and https://github.com/splitbrain/php-cli
They asked me to show knowledge in PHP programming using the latest techniques and open source technologies available. Can anybody see obvious improvements where I am not doing this?
So far I have come up with the following which works:
require 'vendor/autoload.php';
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;
use libphonenumber\PhoneNumberUtil;
use libphonenumber\PhoneNumberToCarrierMapper;
use libphonenumber\PhoneNumberType;
class mobileValidator extends CLI
{
// override the default log level
protected $logdefault = 'info';
// register options and arguments
protected function setup(Options $options)
{
$options->setHelp('This script takes a list of phone numbers from a file (or as an option) and validates them as UK mobile numbers.');
$options->registerOption('file', 'The file containing a list of phone numbers to validate', 'f', 'filename');
$options->registerOption('numbers', 'List of numbers passed in as an option (numbers should separated by a comma).', 'n', 'numbers');
}
/**
* The main Program
* Arguments and options have been parsed when this is run
* @param Options $options
* @return void
*/
protected function main(Options $options)
{
if (!$options->getOpt('file') && !$options->getOpt('numbers')) {
$this->error('No files or numbers have been supplied - Cannot Continue.');
exit();
}
$this->notice('Validation Process Started');
//Get options
$opNum = $options->getOpt('numbers');
$file = $options->getOpt('file');
$numbersFromOp = array();
$numbersFromFile = array();
//Set country code uk
$countryCode = 44;
//Set regions codes to Uk, Guernsey, Jersey and Isle of Man
$regionCodes = array('GB', 'GG', 'JE', 'IM');
//get validator dependencies
$phoneUtil = PhoneNumberUtil::getInstance();
$carrierMapper = PhoneNumberToCarrierMapper::getInstance();
$phoneNumberType = PhoneNumberType::MOBILE;
//Get numbers from passed in option
if ($opNum) {
$numbersFromOp = explode(',', $opNum);
}
//Check if files is set and actually exists
if ($file) {
if(file_exists($file)) {
$numbersFromFile = file($file);
}
else {
$this->warning("File not found ".$file);
if(!$opNum){
$this->critical('File not found and no numbers have been supplied - Cannot Continue.');
exit();
}
}
}
//marge all number arrays to be validated
$phoneNumbers = array_merge($numbersFromFile, $numbersFromOp);
//Validate the numbers
$validator = new validator($phoneNumbers, $phoneUtil, $carrierMapper, $phoneNumberType, $countryCode, $regionCodes);
$results = $validator->validateNumbers();
//write numbers to the csv file
$this->generateCSV($results);
$this->success('Validation process completed successfully');
}
/**
* generateCSV
* Simple function to write a csv file
* @param array $results
* @return void
*/
protected function generateCSV(array $results){
// Open a file to write to
$fp = fopen('output/phone_numbers_'. date('d-M-Y-H-i').'.csv' , 'wb');
$headerRow = array ('Phone Number', 'Carrier', 'Status');
$i = 0;
foreach( $results as $fields ){
if( $i === 0 ){
fputcsv($fp, $headerRow ); // First write the headers
}
fputcsv($fp, $fields); // Then write the fields
$i++;
}
fclose($fp);
}
/**
* Override log function
* Added error_log
* @param string $level
* @param string $message
* @param array $context
*/
public function log($level, $message, array $context = array())
{
// is this log level wanted?
if (!isset($this->loglevel[$level])) return;
/** @var string $prefix */
/** @var string $color */
/** @var resource $channel */
list($prefix, $color, $channel) = $this->loglevel[$level];
if (!$this->colors->isEnabled()) $prefix = '';
$message = $this->interpolate($message, $context);
error_log($message);
$this->colors->ptln($prefix . $message, $color, $channel);
}
}
// execute it
$cli = new mobileValidator();
$cli->run();
The validator class:
use libphonenumber\PhoneNumberUtil;
use libphonenumber\PhoneNumberToCarrierMapper;
use libphonenumber\NumberParseException;
class validator
{
private $phoneNumbers;
private $phoneUtil;
private $carrierMapper;
private $phoneNumberType;
private $countryCode;
private $regionCodes;
private const VALID = 'Valid';
private const INVALID = 'Invalid';
function __construct( array $phoneNumbers, PhoneNumberUtil $phoneUtil, PhoneNumberToCarrierMapper $carrierMapper, $phoneNumberType, $countryCode, $regionCodes)
{
$this->phoneNumbers = $phoneNumbers;
$this->phoneUtil = $phoneUtil;
$this->carrierMapper = $carrierMapper;
$this->phoneNumberType = $phoneNumberType;
$this->countryCode = $countryCode;
$this->regionCodes = $regionCodes;
}
/**
* Returns validated results
* Loops through the phone numbers and validates as uk mobile numbers (including the channel islands).
* Results returned as an array( number, carrier, valid)
* @return array $results
*/
public function validateNumbers(){
$results = array();
//loop through supplied phone numbers
foreach ($this->phoneNumbers as $phoneNumber) {
$number = trim($phoneNumber);
try {
$phoneNumberObject = $this->phoneUtil->parse($number, 'GB');
}
catch (NumberParseException $e) {
$results[] = array($number,'', self::INVALID);
error_log($e);
continue;
}
//get country code and region code
$countryCode = $phoneNumberObject->getCountryCode();
$regionCode = $this->phoneUtil->getRegionCodeForNumber($phoneNumberObject);
$valid = false;
//Number is considered valid if the country code matches supplied country code
// and the region code matches one of the supplied region codes
if($countryCode === $this->countryCode && in_array($regionCode, $this->regionCodes)){
$valid = $this->phoneUtil->isValidNumber($phoneNumberObject);
}
$type = $this->phoneUtil->getNumberType($phoneNumberObject);
$carrier = '';
$validMobile = self::INVALID;
//if the number is valid and the type is mobile attempt to get the carrier
if ($valid && $type === $this->phoneNumberType) {
$carrier = $this->carrierMapper->getNameForNumber($phoneNumberObject, 'en');
if ($carrier === '') {
$carrier = 'unknown';
}
$validMobile = self::VALID;
}
//add to the results array
$results[] = array($number,$carrier, $validMobile);
}
return $results;
}
}