3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\I18n\Validator
;
14 use Zend\Stdlib\ArrayUtils
;
15 use Zend\Validator\AbstractValidator
;
17 class PhoneNumber
extends AbstractValidator
19 const NO_MATCH
= 'phoneNumberNoMatch';
20 const UNSUPPORTED
= 'phoneNumberUnsupported';
21 const INVALID
= 'phoneNumberInvalid';
24 * Validation failure message template definitions
28 protected $messageTemplates = [
29 self
::NO_MATCH
=> 'The input does not match a phone number format',
30 self
::UNSUPPORTED
=> 'The country provided is currently unsupported',
31 self
::INVALID
=> 'Invalid type given. String expected',
35 * Phone Number Patterns
37 * @link http://code.google.com/p/libphonenumber/source/browse/trunk/resources/PhoneNumberMetadata.xml
40 protected static $phone = [];
43 * ISO 3611 Country Code
50 * Allow Possible Matches
54 protected $allowPossible = false;
61 protected $allowedTypes = [
72 * Constructor for the PhoneNumber validator
75 * - country | string | field or value
76 * - allowed_types | array | array of allowed types
77 * - allow_possible | boolean | allow possible matches aka non-strict
79 * @param array|Traversable $options
81 public function __construct($options = [])
83 if ($options instanceof Traversable
) {
84 $options = ArrayUtils
::iteratorToArray($options);
87 if (array_key_exists('country', $options)) {
88 $this->setCountry($options['country']);
90 $country = Locale
::getRegion(Locale
::getDefault());
91 $this->setCountry($country);
94 if (array_key_exists('allowed_types', $options)) {
95 $this->allowedTypes($options['allowed_types']);
98 if (array_key_exists('allow_possible', $options)) {
99 $this->allowPossible($options['allow_possible']);
102 parent
::__construct($options);
108 * @param array|null $types
111 public function allowedTypes(array $types = null)
113 if (null !== $types) {
114 $this->allowedTypes
= $types;
119 return $this->allowedTypes
;
125 * @param bool|null $possible
128 public function allowPossible($possible = null)
130 if (null !== $possible) {
131 $this->allowPossible
= (bool) $possible;
136 return $this->allowPossible
;
144 public function getCountry()
146 return $this->country
;
152 * @param string $country
155 public function setCountry($country)
157 $this->country
= $country;
165 * @param string $code
166 * @return array[]|false
168 protected function loadPattern($code)
170 if (! isset(static::$phone[$code])) {
171 if (! preg_match('/^[A-Z]{2}$/D', $code)) {
175 $file = __DIR__
. '/PhoneNumber/' . $code . '.php';
176 if (! file_exists($file)) {
180 static::$phone[$code] = include $file;
183 return static::$phone[$code];
187 * Returns true if and only if $value matches phone number format
189 * @param string $value
190 * @param array $context
193 public function isValid($value = null, $context = null)
195 if (! is_scalar($value)) {
196 $this->error(self
::INVALID
);
200 $this->setValue($value);
202 $country = $this->getCountry();
204 if (! $countryPattern = $this->loadPattern(strtoupper($country))) {
205 if (isset($context[$country])) {
206 $country = $context[$country];
209 if (! $countryPattern = $this->loadPattern(strtoupper($country))) {
210 $this->error(self
::UNSUPPORTED
);
216 $codeLength = strlen($countryPattern['code']);
219 * Check for existence of either:
220 * 1) E.123/E.164 international prefix
221 * 2) International double-O prefix
222 * 3) Bare country prefix
224 if (0 === strpos($value, '+' . $countryPattern['code'])) {
225 $valueNoCountry = substr($value, $codeLength +
1);
226 } elseif (0 === strpos($value, '00' . $countryPattern['code'])) {
227 $valueNoCountry = substr($value, $codeLength +
2);
228 } elseif (0 === strpos($value, $countryPattern['code'])) {
229 $valueNoCountry = substr($value, $codeLength);
232 // check against allowed types strict match:
233 foreach ($countryPattern['patterns']['national'] as $type => $pattern) {
234 if (in_array($type, $this->allowedTypes
)) {
236 if (preg_match($pattern, $value)) {
238 } elseif (isset($valueNoCountry) && preg_match($pattern, $valueNoCountry)) {
239 // this handles conditions where the country code and prefix are the same
245 // check for possible match:
246 if ($this->allowPossible()) {
247 foreach ($countryPattern['patterns']['possible'] as $type => $pattern) {
248 if (in_array($type, $this->allowedTypes
)) {
250 if (preg_match($pattern, $value)) {
252 } elseif (isset($valueNoCountry) && preg_match($pattern, $valueNoCountry)) {
253 // this handles conditions where the country code and prefix are the same
260 $this->error(self
::NO_MATCH
);