Skip to content

Commit 52558b3

Browse files
committed
Number Support
- Can get / update a number. - Supports assigning to application. - Reduces nubmer ID to just the e.164 number (adds an API call). - Tries to emulate a resource based approach. - Does not embed request / response in entity (will refactor others later).
1 parent 372efcb commit 52558b3

File tree

10 files changed

+708
-40
lines changed

10 files changed

+708
-40
lines changed

src/Client.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* @method \Nexmo\Message\Client message()
2929
* @method \Nexmo\Verify\Client verify()
3030
* @method \Nexmo\Application\Client applications()
31+
* @method \Nexmo\Numbers\Client numbers()
3132
*/
3233
class Client
3334
{
@@ -81,7 +82,8 @@ public function __construct(CredentialsInterface $credentials, $options = array(
8182
$this->setFactory(new MapFactory([
8283
'message' => 'Nexmo\Message\Client',
8384
'verify' => 'Nexmo\Verify\Client',
84-
'applications' => 'Nexmo\Application\Client'
85+
'applications' => 'Nexmo\Application\Client',
86+
'numbers' => 'Nexmo\Numbers\Client',
8587
], $this));
8688
}
8789

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
/**
3+
* Nexmo Client Library for PHP
4+
*
5+
* @copyright Copyright (c) 2016 Nexmo, Inc. (http://nexmo.com)
6+
* @license https://github.com/Nexmo/nexmo-php/blob/master/LICENSE.txt MIT License
7+
*/
8+
9+
namespace Nexmo\Entity;
10+
11+
/**
12+
* Class Psr7Trait
13+
*
14+
* Allow an entity to contain last request / response objects.
15+
*/
16+
trait NoRequestResponseTrait
17+
{
18+
public function setResponse(\Psr\Http\Message\ResponseInterface $response)
19+
{
20+
throw new \RuntimeException(__CLASS__ . ' does not support request / response');
21+
}
22+
23+
public function setRequest(\Psr\Http\Message\RequestInterface $request)
24+
{
25+
throw new \RuntimeException(__CLASS__ . ' does not support request / response');
26+
}
27+
28+
public function getRequest()
29+
{
30+
return null;
31+
}
32+
33+
public function getResponse()
34+
{
35+
return null;
36+
}
37+
}

src/Numbers/Client.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
/**
3+
* Nexmo Client Library for PHP
4+
*
5+
* @copyright Copyright (c) 2016 Nexmo, Inc. (http://nexmo.com)
6+
* @license https://github.com/Nexmo/nexmo-php/blob/master/LICENSE.txt MIT License
7+
*/
8+
9+
namespace Nexmo\Numbers;
10+
11+
use Http\Client\Common\Exception\ClientErrorException;
12+
use Nexmo\Client\ClientAwareInterface;
13+
use Nexmo\Client\ClientAwareTrait;
14+
use Psr\Http\Message\ResponseInterface;
15+
use Nexmo\Client\Exception;
16+
use Zend\Diactoros\Request;
17+
18+
class Client implements ClientAwareInterface
19+
{
20+
use ClientAwareTrait;
21+
22+
public function update($number, $id = null)
23+
{
24+
if(!is_null($id)){
25+
$update = $this->get($id);
26+
}
27+
28+
if($number instanceof Number){
29+
$body = $number->getRequestData();
30+
if(!isset($update) AND !isset($body['country'])){
31+
$data = $this->get($number->getId());
32+
$body['msisdn'] = $data->getId();
33+
$body['country'] = $data->getCountry();
34+
}
35+
} else {
36+
$body = $number;
37+
}
38+
39+
if(isset($update)){
40+
$body['msisdn'] = $update->getId();
41+
$body['country'] = $update->getCountry();
42+
}
43+
44+
$request = new Request(
45+
\Nexmo\Client::BASE_REST . '/number/update',
46+
'POST',
47+
'php://temp',
48+
[
49+
'Accept' => 'application/json',
50+
'Content-Type' => 'application/x-www-form-urlencoded'
51+
]
52+
);
53+
54+
$request->getBody()->write(http_build_query($body));
55+
$response = $this->client->send($request);
56+
57+
if('200' != $response->getStatusCode()){
58+
throw $this->getException($response);
59+
}
60+
61+
if(isset($update) AND ($number instanceof Number)){
62+
return $this->get($number);
63+
}
64+
65+
if($number instanceof Number){
66+
return $this->get($number);
67+
}
68+
69+
return $this->get($body['msisdn']);
70+
}
71+
72+
public function get($number)
73+
{
74+
if($number instanceof Number){
75+
$query = ['pattern' => $number->getId()];
76+
} else {
77+
$query = ['pattern' => $number];
78+
}
79+
80+
$request = new Request(
81+
\Nexmo\Client::BASE_REST . '/account/numbers?' . http_build_query($query),
82+
'GET',
83+
'php://temp'
84+
);
85+
86+
$response = $this->client->send($request);
87+
88+
if($response->getStatusCode() != '200'){
89+
throw $this->getException($response);
90+
}
91+
92+
$body = json_decode($response->getBody()->getContents(), true);
93+
if(empty($body)){
94+
throw new Exception\Request('number not found', 404);
95+
}
96+
97+
if(!isset($body['count']) OR !isset($body['numbers'])){
98+
throw new Exception\Exception('unexpected response format');
99+
}
100+
101+
if($body['count'] != '1'){
102+
throw new Exception\Request('number not found', 404);
103+
}
104+
105+
if(!($number instanceof Number)){
106+
$number = new Number();
107+
}
108+
109+
$number->JsonUnserialize($body['numbers'][0]);
110+
111+
return $number;
112+
}
113+
114+
115+
116+
protected function getException(ResponseInterface $response)
117+
{
118+
$body = json_decode($response->getBody()->getContents(), true);
119+
$status = $response->getStatusCode();
120+
121+
if($status >= 400 AND $status < 500) {
122+
$e = new Exception\Request($body['error-code-label'], $status);
123+
} elseif($status >= 500 AND $status < 600) {
124+
$e = new Exception\Server($body['error-code-label'], $status);
125+
} else {
126+
$e = new Exception\Exception('Unexpected HTTP Status Code');
127+
throw $e;
128+
}
129+
130+
return $e;
131+
}
132+
133+
}

src/Numbers/Number.php

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
/**
3+
* Nexmo Client Library for PHP
4+
*
5+
* @copyright Copyright (c) 2016 Nexmo, Inc. (http://nexmo.com)
6+
* @license https://github.com/Nexmo/nexmo-php/blob/master/LICENSE.txt MIT License
7+
*/
8+
9+
namespace Nexmo\Numbers;
10+
11+
12+
use Nexmo\Application\Application;
13+
use Nexmo\Entity\EntityInterface;
14+
use Nexmo\Entity\JsonResponseTrait;
15+
use Nexmo\Entity\JsonSerializableInterface;
16+
use Nexmo\Entity\JsonSerializableTrait;
17+
use Nexmo\Entity\JsonUnserializableInterface;
18+
use Nexmo\Entity\NoRequestResponseTrait;
19+
20+
class Number implements EntityInterface, JsonSerializableInterface, JsonUnserializableInterface
21+
{
22+
use JsonSerializableTrait;
23+
use NoRequestResponseTrait;
24+
use JsonResponseTrait;
25+
26+
const TYPE_MOBILE = 'mobile-lvn';
27+
const TYPE_FIXED = 'landline';
28+
29+
const FEATURE_VOICE = 'VOICE';
30+
const FEATURE_SMS = 'SMS';
31+
32+
const WEBHOOK_MESSAGE = 'moHttpUrl';
33+
const WEBHOOK_VOICE_STATUS = 'voiceStatusCallbackUrl';
34+
35+
const ENDPOINT_SIP = 'sip';
36+
const ENDPOINT_TEL = 'tel';
37+
const ENDPOINT_VXML = 'vxml';
38+
const ENDPOINT_APP = 'app';
39+
40+
protected $data = [];
41+
42+
public function __construct($number = null, $country = null)
43+
{
44+
$this->data['msisdn'] = $number;
45+
$this->data['country'] = $country;
46+
}
47+
48+
public function getId()
49+
{
50+
return $this->fromData('msisdn');
51+
}
52+
53+
public function getMsisdn()
54+
{
55+
return $this->getId();
56+
}
57+
58+
public function getNumber()
59+
{
60+
return $this->getId();
61+
}
62+
63+
public function getCountry()
64+
{
65+
return $this->fromData('country');
66+
}
67+
68+
public function getType()
69+
{
70+
return $this->fromData('type');
71+
}
72+
73+
public function hasFeature($feature)
74+
{
75+
if(!isset($this->data['features'])){
76+
return false;
77+
}
78+
79+
return in_array($feature, $this->data['features']);
80+
}
81+
82+
public function getFeatures()
83+
{
84+
return $this->fromData('features');
85+
}
86+
87+
public function setWebhook($type, $url)
88+
{
89+
if(!in_array($type, [self::WEBHOOK_MESSAGE, self::WEBHOOK_VOICE_STATUS])){
90+
throw new \InvalidArgumentException("invalid webhook type `$type`");
91+
}
92+
93+
$this->data[$type] = $url;
94+
return $this;
95+
}
96+
97+
public function getWebhook($type)
98+
{
99+
return $this->fromData($type);
100+
}
101+
102+
public function hasWebhook($type)
103+
{
104+
return isset($this->data[$type]);
105+
}
106+
107+
public function setVoiceDestination($endpoint, $type = null)
108+
{
109+
if(is_null($type)){
110+
$type = $this->autoType($endpoint);
111+
}
112+
113+
if(self::ENDPOINT_APP == $type AND !($endpoint instanceof Application)){
114+
$endpoint = new Application($endpoint);
115+
}
116+
117+
$this->data['voiceCallbackValue'] = $endpoint;
118+
$this->data['voiceCallbackType'] = $type;
119+
120+
return $this;
121+
}
122+
123+
protected function autoType($endpoint)
124+
{
125+
if($endpoint instanceof Application){
126+
return self::ENDPOINT_APP;
127+
}
128+
129+
if(false !== strpos($endpoint, '@')){
130+
return self::ENDPOINT_SIP;
131+
}
132+
133+
if(0 === strpos(strtolower($endpoint), 'http')){
134+
return self::ENDPOINT_VXML;
135+
}
136+
137+
if(preg_match('#[a-z]+#', $endpoint)){
138+
return self::ENDPOINT_APP;
139+
}
140+
141+
return self::ENDPOINT_TEL;
142+
}
143+
144+
public function getVoiceDestination()
145+
{
146+
return $this->fromData('voiceCallbackValue');
147+
}
148+
149+
public function getVoiceType()
150+
{
151+
if(!isset($this->data['voiceCallbackType'])){
152+
return null;
153+
}
154+
155+
return $this->data['voiceCallbackType'];
156+
}
157+
158+
protected function fromData($name)
159+
{
160+
if(!isset($this->data[$name])){
161+
throw new \RuntimeException("`{$name}` has not been set");
162+
}
163+
164+
return $this->data[$name];
165+
}
166+
167+
public function JsonUnserialize(array $json)
168+
{
169+
$this->data = $json;
170+
}
171+
172+
function jsonSerialize()
173+
{
174+
$json = $this->data;
175+
if(isset($json['voiceCallbackValue']) AND ($json['voiceCallbackValue'] instanceof Application)){
176+
$json['voiceCallbackValue'] = $json['voiceCallbackValue']->getId();
177+
}
178+
179+
return $json;
180+
}
181+
}

0 commit comments

Comments
 (0)