File Coverage

blib/lib/Number/Phone/UK.pm
Criterion Covered Total %
statement 167 170 99.4
branch 55 60 93.3
condition 12 15 80.0
subroutine 32 32 100.0
pod 9 9 100.0
total 275 286 97.2


line stmt bran cond sub pod time code
1             package Number::Phone::UK;
2              
3 15     15   6762 use strict;
  15         38  
  15         526  
4              
5 15     15   88 use Scalar::Util 'blessed';
  15         37  
  15         749  
6 15     15   697 use Number::Phone::UK::Data;
  15         64  
  15         637  
7              
8 15     15   118 use base 'Number::Phone';
  15         47  
  15         19738  
9              
10             our $VERSION = '1.72';
11              
12             my $cache = {};
13              
14             =head1 NAME
15              
16             Number::Phone::UK - UK-specific methods for Number::Phone
17              
18             =head1 SYNOPSIS
19              
20             use Number::Phone;
21              
22             $daves_phone = Number::Phone->new('+44 1234 567890');
23              
24             =cut
25              
26             sub new {
27 338     338 1 2217 my $class = shift;
28 338         576 my $number = shift;
29              
30 338         831 $number = '+44'._clean_number($number);
31 338 100       959 if(is_valid($number)) {
32 301         725 $number =~ s/^0/+44/;
33 301         697 my $target_class = $class->_get_class(_clean_number($number));
34 301 100       1067 return undef if($class ne $target_class);
35 295         2187 return bless(\$number, $target_class);
36 38         234 } else { return undef; }
37             }
38              
39             =head1 DATABASE
40              
41             Number::Phone::UK uses a large database, access via L. This
42             database lives in a file, and normally only the little bits of it that you access will
43             ever get loaded into memory. This means, however, that creating Number::Phone::UK objects
44             almost always involves disk access and so is slow compared to data for some other
45             countries. There are two ways to avoid this slowness.
46              
47             First, if you don't need all the functionality you can use L.
48              
49             Second, if you can accept slow startup - eg when your server starts - then you can call
50             C<< Number::Phone::UK::Data->slurp() >> from your code, which will pull the entire database
51             into memory. This will take a few minutes, and on a 64-bit machine will consume of the
52             order of 200MB of memory.
53              
54             The database uses L. This may have some problems if you connect to it,
55             C, and then try to access the database from multiple processes. We attempt to
56             work around this by re-connecting to the database after forking. This is, of course,
57             not a problem if you C the database before forking.
58              
59             =head1 METHODS
60              
61             The following methods from Number::Phone are overridden:
62              
63             =over 4
64              
65             =item new
66              
67             The constructor, you should never have to call this yourself. To create an
68             object the canonical incantation is C<< Number::Phone->new('+44 ...') >>.
69              
70             =item data_source
71              
72             Returns a string telling where and when the data that drives this class was last updated, looking something like:
73              
74             "OFCOM at Wed Sep 30 10:37:39 2020 UTC"
75              
76             The current value of this is also documented in L.
77              
78             =item is_valid
79              
80             The number is valid within the national numbering scheme. It may or may
81             not yet be allocated, or it may be reserved. Any number which returns
82             true for any of the following methods will also be valid.
83              
84             =cut
85              
86             sub _get_class {
87 300     301   583 my $class = shift;
88 300         601 my $number = shift;
89 300         672 foreach my $prefix (_prefixes($number)) {
90 2835 100       359802 if(exists(Number::Phone::UK::Data::db()->{subclass}->{$prefix})) {
91 43 50       12639 return $class if(Number::Phone::UK::Data::db()->{subclass}->{$prefix} eq '');
92              
93 43         5981 my $desired_subclass = Number::Phone::UK::Data::db()->{subclass}->{$prefix};
94 43         5615 my $subclass = "Number::Phone::UK::$desired_subclass";
95 43     4   3574 eval "use $subclass";
  4     4   31  
  4     3   14  
  4     3   27  
  4     3   32  
  4     3   13  
  4     3   34  
  3     3   39  
  3     2   14  
  3     2   20  
  3     2   25  
  3     2   30  
  3         28  
  3         26  
  3         7  
  3         33  
  3         26  
  3         11  
  3         25  
  3         27  
  3         11  
  3         29  
  3         25  
  3         9  
  3         24  
  2         17  
  2         5  
  2         36  
  2         15  
  2         14  
  2         16  
  2         19  
  2         10  
  2         23  
  2         18  
  2         5  
  2         14  
96 43         246 return $subclass;
97             }
98             }
99 257         33898 return $class;
100             }
101              
102             sub _clean_number {
103 1211     1212   1964 my $clean = shift;
104 1211         3020 $clean =~ s/[^0-9+]//g; # strip non-digits/plusses
105 1211         4209 $clean =~ s/^\+44//; # remove leading +44
106 1211         2457 $clean =~ s/^0//; # kill leading zero
107 1211         3384 return $clean;
108             }
109              
110             sub _prefixes {
111 794     794   1340 my $number = shift;
112 794         2107 map { substr($number, 0, $_) } reverse(1..length($number));
  7858         15928  
113             }
114              
115             sub is_valid {
116 365     365 1 2059 my $number = shift;
117              
118             # If called as an object method, it *must* be valid otherwise the
119             # object would never have been instantiated.
120             # If called as a subroutine, that's the constructor doing its thang.
121 365 100       1401 return 1 if(blessed($number));
122              
123             # otherwise we have to validate
124              
125             # if we've seen this number before, use cached result
126 337 100       1955 return 1 if($cache->{$number}->{is_valid});
127              
128             # assume it's OK unless proven otherwise
129 202         634 $cache->{$number}->{is_valid} = 1;
130              
131 202         446 my $cleaned_number = _clean_number($number);
132              
133 202         520 my @prefixes = _prefixes($cleaned_number);
134              
135             # quickly check length
136 202 100 100     1290 return $cache->{$number}->{is_valid} = 0 if(length($cleaned_number) < 7 || length($cleaned_number) > 10);
137              
138             # 04 and 06 are invalid, only 05[56] are valid
139 185 100       790 return $cache->{$number}->{is_valid} = 0 if($cleaned_number =~ /^(4|5[01234789]|6)/);
140              
141             # slightly more rigourous length check for some unallocated geographic numbers
142             # 07, 02x and 011x are always ten digits
143 179 100 100     986 return $cache->{$number}->{is_valid} = 0 if($cleaned_number =~ /^([27]|11)/ && length($cleaned_number) != 10);
144              
145 171         379 my $telco;
146             my $format;
147 171         388 foreach my $prefix (@prefixes) {
148 884 100       636415 if(exists(Number::Phone::UK::Data::db()->{telco}->{$prefix})) {
149 165         64839 $telco = Number::Phone::UK::Data::db()->{telco}->{$prefix};
150 165         32378 last;
151             }
152             }
153 171         1489 foreach my $prefix (@prefixes) {
154 884 100       559929 if(exists(Number::Phone::UK::Data::db()->{format}->{$prefix})) {
155 165         61474 $format = Number::Phone::UK::Data::db()->{format}->{$prefix};
156 165         29423 last;
157             }
158             }
159              
160 171         1573 $cache->{$number}->{is_allocated} = 0;
161 171         531 $cache->{$number}->{format} = $format;
162 171 100       497 if($telco) {
163 110         241 $cache->{$number}->{is_allocated} = 1;
164 110         307 $cache->{$number}->{operator} = $telco;
165             }
166              
167 171 100 66     1251 if($cache->{$number}->{format} && $cache->{$number}->{format} =~ /\+/) {
168 165         941 my($arealength, $subscriberlength) = split(/\+/, $cache->{$number}->{format});
169             # for hateful mixed thing
170 165 100       839 my @subscriberlengths = ($subscriberlength =~ m{/}) ? split(/\//, $subscriberlength) : ($subscriberlength);
171 165         1097 $subscriberlength =~ s/^(\d+).*/$1/; # for hateful mixed thing
172 165         689 $cache->{$number}->{areacode} = substr($cleaned_number, 0, $arealength);
173 165         635 $cache->{$number}->{subscriber} = substr($cleaned_number, $arealength);
174             $cache->{$number}->{areaname} = (
175             map {
176 88         32755 Number::Phone::UK::Data::db()->{areanames}->{$_}
177             } grep {
178 165         510 exists(Number::Phone::UK::Data::db()->{areanames}->{$_})
  1624         417863  
179             } @prefixes
180             )[0];
181 165 100       21965 if(!grep { length($cache->{$number}->{subscriber}) == $_ } @subscriberlengths) {
  183         1002  
182             # number wrong length!
183 6         43 $cache->{$number} = { is_valid => 0 };
184 6         34 return 0;
185             }
186             }
187              
188 165         768 return $cache->{$number}->{is_valid};
189             }
190              
191             # now define the is_* methods that we over-ride
192             sub is_fixed_line {
193 30 100   30 1 745 return 0 if(is_mobile(@_));
194 16         71 return undef;
195             }
196              
197             sub is_drama {
198 80     80 1 365 my $self = shift;
199              
200 80         154 my $num = _clean_number(${$self});
  80         208  
201              
202 80         1298 my @drama_numbers = (
203             # Leeds, Sheffield, Nottingham, Leicester, Bristol, Reading
204             qr/^11[3-8]4960[0-9]{3}$/,
205             # Birmingham, Edinburgh, Glasgow, Liverpool, Manchester
206             qr/^1[2-6]14960[0-9]{3}$/,
207             # London
208             qr/^2079460[0-9]{3}$/,
209             # Tyneside/Durham/Sunderland
210             qr/^1914980[0-9]{3}$/,
211             # Northern Ireland
212             qr/^2896496[0-9]{3}$/,
213             # Cardiff
214             qr/^2920180[0-9]{3}$/,
215             # No area
216             qr/^1632960[0-9]{3}$/,
217             # Mobile
218             qr/^7700900[0-9]{3}$/,
219             # Freephone
220             qr/^8081570[0-9]{3}$/,
221             # Premium Rate
222             qr/^9098790[0-9]{3}$/,
223             # UK Wide
224             qr/^3069990[0-9]{3}$/,
225             );
226              
227 80         230 foreach my $d (@drama_numbers) {
228 552 100       2150 return 1 if ($num =~ $d);
229             }
230              
231 32         312 return 0;
232             }
233              
234             foreach my $is (qw(
235             geographic network_service tollfree corporate
236             personal pager mobile specialrate adult allocated ipphone
237             )) {
238 15     15   146 no strict 'refs';
  15         63  
  15         3797  
239             *{__PACKAGE__."::is_$is"} = sub {
240 540     540   42904 my $self = shift;
241 540 100       943 if(!exists($cache->{${$self}}->{"is_$is"})) {
  540         2158  
242 288         76965 $cache->{${$self}}->{"is_$is"} =
243             grep {
244             exists(
245             Number::Phone::UK::Data::db()->{
246             { geographic => 'geo_prefices',
247             network_service => 'network_svc_prefices',
248             tollfree => 'free_prefices',
249             corporate => 'corporate_prefices',
250             personal => 'personal_prefices',
251             pager => 'pager_prefices',
252             mobile => 'mobile_prefices',
253             specialrate => 'special_prefices',
254             adult => 'adult_prefices',
255             ipphone => 'ip_prefices'
256             }->{$is}
257 2876         1191785 }->{$_}
258             );
259 288         487 } _prefixes(_clean_number(${$self}));
  288         682  
260             }
261 540         1323 $cache->{${$self}}->{"is_$is"};
  540         2539  
262             }
263             }
264              
265             # define the other methods
266              
267             foreach my $method (qw(operator areacode areaname subscriber)) {
268 15     15   141 no strict 'refs';
  15         37  
  15         10012  
269             *{__PACKAGE__."::$method"} = sub {
270 359     359   611 my $self = shift;
271 359         608 return $cache->{${$self}}->{$method};
  359         2624  
272             }
273             }
274              
275             =item is_allocated
276              
277             The number has been allocated to a telco for use. It may or may not yet
278             be in use or may be reserved.
279              
280             =item is_drama
281              
282             The number is intended for use in fiction. OFCOM has allocated numerous small
283             ranges for this purpose. These numbers will not be allocated to real customers.
284             See L
285             for the authoritative source.
286              
287             =item is_geographic
288              
289             The number refers to a geographic area.
290              
291             =item is_fixed_line
292              
293             The number, when in use, can only refer to a fixed line.
294              
295             (we can't tell whether a number is a fixed line, but we can tell that
296             some are *not*).
297              
298             =item is_mobile
299              
300             The number, when in use, can only refer to a mobile phone.
301              
302             =item is_pager
303              
304             The number, when in use, can only refer to a pager.
305              
306             =item is_tollfree
307              
308             Callers will not be charged for calls to this number under normal circumstances.
309              
310             =item is_specialrate
311              
312             The number, when in use, attracts special rates. For instance, national
313             dialling at local rates, or premium rates for services.
314              
315             =item is_adult
316              
317             The number, when in use, goes to a service of an adult nature, such as porn.
318              
319             =item is_personal
320              
321             The number, when in use, goes to an individual person.
322              
323             =item is_corporate
324              
325             The number, when in use, goes to a business.
326              
327             =item is_ipphone
328              
329             The number, when in use, is terminated using VoIP.
330              
331             =item is_network_service
332              
333             The number is some kind of network service such as a human operator, directory
334             enquiries, emergency services etc
335              
336             =item country_code
337              
338             Returns 44.
339              
340             =cut
341              
342 94     94 1 363 sub country_code { 44; }
343              
344             =item regulator
345              
346             Returns informational text.
347              
348             =cut
349              
350 2     2 1 14 sub regulator { 'OFCOM, http://www.ofcom.org.uk/'; }
351              
352             =item areacode
353              
354             Return the area code - if applicable - for the number. If not applicable,
355             returns undef.
356              
357             =item areaname
358              
359             Return the area name - if applicable - for the number, or undef.
360              
361             =item location
362              
363             For geographic numbers, this returns the location of the exchange to which
364             that number is assigned, if available. Otherwise returns undef.
365              
366             =cut
367              
368             sub location {
369 6     6 1 3587 my $self = shift;
370              
371 6 100       30 return undef unless($self->is_geographic());
372              
373 4         17 my $cleaned_number = _clean_number(${$self});
  4         20  
374              
375 4         20 my @prefixes = _prefixes($cleaned_number);
376              
377             # uncoverable branch true
378 4 50       23 if(!$ENV{TESTINGKILLTHEWABBIT}) {
379 0         0 eval "require Number::Phone::UK::DetailedLocations"; # uncoverable statement
380             }
381 4 100       69 require Number::Phone::UK::Exchanges if(!$Number::Phone::UK::Exchanges::db);
382              
383 4         670 foreach(@prefixes) {
384 28 100       268 if(exists($Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_})) {
385             return [
386             $Number::Phone::UK::Exchanges::db->{exchg_positions}->{$Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_}}->{lat},
387             $Number::Phone::UK::Exchanges::db->{exchg_positions}->{$Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_}}->{long}
388 4         51 ];
389             }
390             }
391             # may become coverable if I ever test the location of a number
392             # in an areacode that wasn't in the data dump I got years ago
393 0         0 return undef; # uncoverable statement
394             }
395              
396             =item subscriber
397              
398             Return the subscriber part of the number
399              
400             =item operator
401              
402             Return the name of the telco operating this number, in an appropriate
403             character set and with optional details such as their web site or phone
404             number.
405              
406             =item format
407              
408             Return a sanely formatted version of the number, complete with IDD code, eg
409             for the UK number (0208) 771-2924 it would return +44 20 8771 2924.
410              
411             =cut
412              
413             sub format {
414 63     63 1 845 my $self = shift;
415 63         116 my $r;
416              
417 63 100 66     206 if($self->areacode()) { # if there's an areacode ...
    100 66        
    50          
    0          
418 38         110 $r = '+'.country_code().' '.$self->areacode().' ';
419 38 100       235 if( length($self->subscriber()) == 7) { $r .= substr($self->subscriber(), 0, 3).' '.substr($self->subscriber(), 3) }
  2 100       17  
420 11         58 elsif(length($self->subscriber()) == 8) { $r .= substr($self->subscriber(), 0, 4).' '.substr($self->subscriber(), 4) }
421 25         68 else { $r .= $self->subscriber() }
422             } elsif($self->subscriber && $self->subscriber =~ /^7/) { # mobiles/pagers don't have areacodes but should be formatted as if they do
423 9         41 $r = '+'.country_code().
424             ' '.substr($self->subscriber(), 0, 4).
425             ' '.substr($self->subscriber(), 4);
426             } elsif(!$self->is_allocated() || !$cache->{${self}}->{format}) { # if not allocated or no format
427 16         57 $r = '+'.country_code().' '.substr(${$self}, 3)
  16         54  
428             } elsif($self->subscriber()) { # if there's a subscriber ...
429 0         0 $r = '+'.country_code().' '.$self->subscriber
430             }
431 63         491 return $r;
432             }
433              
434             =item intra_country_dial_to
435              
436             Within the UK numbering plan you can *always* dial 0xxxx xxxxxx
437             for intra-country calls. In most places the leading 0$areacode is
438             optional but in some it is required (see eg
439             L) and over time this
440             will apply to more areas.
441              
442             =cut
443              
444             sub intra_country_dial_to {
445 10     10 1 21 my $from = shift;
446 10         17 my $to = shift;
447              
448 10 100       25 die if(!$to->is_allocated());
449 9 100       22 return '0'.($to->areacode() ? $to->areacode() : '').$to->subscriber();
450             }
451              
452             =item country
453              
454             If the number is_international, return the two-letter ISO country code.
455              
456             NYI
457              
458             =back
459              
460             =head1 LIMITATIONS/BUGS/FEEDBACK
461              
462             The results are only as up-to-date as the data included from OFCOM's
463             official documentation of number range allocations.
464              
465             No attempt is made to deal with number portability.
466              
467             Please report bugs at L, including, if possible, a test case.
468              
469             I welcome feedback from users.
470              
471             =head1 LICENCE
472              
473             You may use, modify and distribute this software under the same terms as
474             perl itself.
475              
476             =head1 AUTHOR
477              
478             David Cantrell Edavid@cantrell.org.ukE
479              
480             Copyright 2023
481              
482             =cut
483              
484             1;