Skip to content
Snippets Groups Projects
Commit 7e1ba3c5 authored by John Twyman's avatar John Twyman
Browse files

(WIP) Upgrade SDK to v8

parent 72161002
Branches
Tags
1 merge request!3Update Twilio SDK to v8
Showing
with 937 additions and 171 deletions
<?php
namespace Twilio\Base;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\TwilioException;
use Twilio\Http\Client as HttpClient;
use Twilio\Http\CurlClient;
use Twilio\Rest\Api;
use Twilio\Security\RequestValidator;
use Twilio\VersionInfo;
/**
* @property \Twilio\Rest\Api\V2010\AccountInstance $account
* @property Api $api
*/
class BaseClient
{
const ENV_ACCOUNT_SID = 'TWILIO_ACCOUNT_SID';
const ENV_AUTH_TOKEN = 'TWILIO_AUTH_TOKEN';
const ENV_REGION = 'TWILIO_REGION';
const ENV_EDGE = 'TWILIO_EDGE';
const DEFAULT_REGION = 'us1';
const ENV_LOG = 'TWILIO_LOG_LEVEL';
protected $username;
protected $password;
protected $accountSid;
protected $region;
protected $edge;
protected $httpClient;
protected $environment;
protected $userAgentExtensions;
protected $logLevel;
protected $_account;
/**
* Initializes the Twilio Client
*
* @param string $username Username to authenticate with
* @param string $password Password to authenticate with
* @param string $accountSid Account SID to authenticate with, defaults to
* $username
* @param string $region Region to send requests to, defaults to 'us1' if Edge
* provided
* @param HttpClient $httpClient HttpClient, defaults to CurlClient
* @param mixed[] $environment Environment to look for auth details, defaults
* to $_ENV
* @param string[] $userAgentExtensions Additions to the user agent string
* @throws ConfigurationException If valid authentication is not present
*/
public function __construct(
string $username = null,
string $password = null,
string $accountSid = null,
string $region = null,
HttpClient $httpClient = null,
array $environment = null,
array $userAgentExtensions = null
) {
$this->environment = $environment ?: \getenv();
$this->username = $this->getArg($username, self::ENV_ACCOUNT_SID);
$this->password = $this->getArg($password, self::ENV_AUTH_TOKEN);
$this->region = $this->getArg($region, self::ENV_REGION);
$this->edge = $this->getArg(null, self::ENV_EDGE);
$this->logLevel = $this->getArg(null, self::ENV_LOG);
$this->userAgentExtensions = $userAgentExtensions ?: [];
if (!$this->username || !$this->password) {
throw new ConfigurationException('Credentials are required to create a Client');
}
$this->accountSid = $accountSid ?: $this->username;
if ($httpClient) {
$this->httpClient = $httpClient;
} else {
$this->httpClient = new CurlClient();
}
}
/**
* Determines argument value accounting for environment variables.
*
* @param string $arg The constructor argument
* @param string $envVar The environment variable name
* @return ?string Argument value
*/
public function getArg(?string $arg, string $envVar): ?string
{
if ($arg) {
return $arg;
}
if (\array_key_exists($envVar, $this->environment)) {
return $this->environment[$envVar];
}
return null;
}
/**
* Makes a request to the Twilio API using the configured http client
* Authentication information is automatically added if none is provided
*
* @param string $method HTTP Method
* @param string $uri Fully qualified url
* @param string[] $params Query string parameters
* @param string[] $data POST body data
* @param string[] $headers HTTP Headers
* @param string $username User for Authentication
* @param string $password Password for Authentication
* @param int $timeout Timeout in seconds
* @return \Twilio\Http\Response Response from the Twilio API
*/
public function request(
string $method,
string $uri,
array $params = [],
array $data = [],
array $headers = [],
string $username = null,
string $password = null,
int $timeout = null
): \Twilio\Http\Response{
$username = $username ?: $this->username;
$password = $password ?: $this->password;
$logLevel = (getenv('DEBUG_HTTP_TRAFFIC') === 'true' ? 'debug' : $this->getLogLevel());
$headers['User-Agent'] = 'twilio-php/' . VersionInfo::string() .
' (' . php_uname("s") . ' ' . php_uname("m") . ')' .
' PHP/' . PHP_VERSION;
$headers['Accept-Charset'] = 'utf-8';
if ($this->userAgentExtensions) {
$headers['User-Agent'] .= ' ' . implode(' ', $this->userAgentExtensions);
}
if (!\array_key_exists('Accept', $headers)) {
$headers['Accept'] = 'application/json';
}
$uri = $this->buildUri($uri);
if ($logLevel === 'debug') {
error_log('-- BEGIN Twilio API Request --');
error_log('Request Method: ' . $method);
$u = parse_url($uri);
if (isset($u['path'])) {
error_log('Request URL: ' . $u['path']);
}
if (isset($u['query']) && strlen($u['query']) > 0) {
error_log('Query Params: ' . $u['query']);
}
error_log('Request Headers: ');
foreach ($headers as $key => $value) {
if (strpos(strtolower($key), 'authorization') === false) {
error_log("$key: $value");
}
}
error_log('-- END Twilio API Request --');
}
$response = $this->getHttpClient()->request(
$method,
$uri,
$params,
$data,
$headers,
$username,
$password,
$timeout
);
if ($logLevel === 'debug') {
error_log('Status Code: ' . $response->getStatusCode());
error_log('Response Headers:');
$responseHeaders = $response->getHeaders();
foreach ($responseHeaders as $key => $value) {
error_log("$key: $value");
}
}
return $response;
}
/**
* Build the final request uri
*
* @param string $uri The original request uri
* @return string Request uri
*/
public function buildUri(string $uri): string
{
if ($this->region == null && $this->edge == null) {
return $uri;
}
$parsedUrl = \parse_url($uri);
$pieces = \explode('.', $parsedUrl['host']);
$product = $pieces[0];
$domain = \implode('.', \array_slice($pieces, -2));
$newEdge = $this->edge;
$newRegion = $this->region;
if (count($pieces) == 4) { // product.region.twilio.com
$newRegion = $newRegion ?: $pieces[1];
} elseif (count($pieces) == 5) { // product.edge.region.twilio.com
$newEdge = $newEdge ?: $pieces[1];
$newRegion = $newRegion ?: $pieces[2];
}
if ($newEdge != null && $newRegion == null) {
$newRegion = self::DEFAULT_REGION;
}
$parsedUrl['host'] = \implode('.', \array_filter([$product, $newEdge, $newRegion, $domain]));
return RequestValidator::unparse_url($parsedUrl);
}
/**
* Magic getter to lazy load domains
*
* @param string $name Domain to return
* @return \Twilio\Domain The requested domain
* @throws TwilioException For unknown domains
*/
public function __get(string $name)
{
$method = 'get' . \ucfirst($name);
if (\method_exists($this, $method)) {
return $this->$method();
}
throw new TwilioException('Unknown domain ' . $name);
}
/**
* Magic call to lazy load contexts
*
* @param string $name Context to return
* @param mixed[] $arguments Context to return
* @return \Twilio\InstanceContext The requested context
* @throws TwilioException For unknown contexts
*/
public function __call(string $name, array $arguments)
{
$method = 'context' . \ucfirst($name);
if (\method_exists($this, $method)) {
return \call_user_func_array([$this, $method], $arguments);
}
throw new TwilioException('Unknown context ' . $name);
}
/**
* Provide a friendly representation
*
* @return string Machine friendly representation
*/
public function __toString(): string
{
return '[Client ' . $this->getAccountSid() . ']';
}
/**
* Validates connection to new SSL certificate endpoint
*
* @param CurlClient $client
* @throws TwilioException if request fails
*/
public function validateSslCertificate(CurlClient $client): void
{
$response = $client->request('GET', 'https://tls-test.twilio.com:443');
if ($response->getStatusCode() < 200 || $response->getStatusCode() > 300) {
throw new TwilioException('Failed to validate SSL certificate');
}
}
/**
* @return \Twilio\Rest\Api\V2010\AccountContext Account provided as the
* authenticating account
*/
public function getAccount(): \Twilio\Rest\Api\V2010\AccountContext
{
return $this->api->v2010->account;
}
/**
* Retrieve the Username
*
* @return string Current Username
*/
public function getUsername(): string
{
return $this->username;
}
/**
* Retrieve the Password
*
* @return string Current Password
*/
public function getPassword(): string
{
return $this->password;
}
/**
* Retrieve the AccountSid
*
* @return string Current AccountSid
*/
public function getAccountSid(): string
{
return $this->accountSid;
}
/**
* Retrieve the Region
*
* @return string Current Region
*/
public function getRegion(): string
{
return $this->region;
}
/**
* Retrieve the Edge
*
* @return string Current Edge
*/
public function getEdge(): string
{
return $this->edge;
}
/**
* Set Edge
*
* @param string $uri Edge to use, unsets the Edge when called with no arguments
*/
public function setEdge(string $edge = null): void
{
$this->edge = $this->getArg($edge, self::ENV_EDGE);
}
/**
* Retrieve the HttpClient
*
* @return HttpClient Current HttpClient
*/
public function getHttpClient(): HttpClient
{
return $this->httpClient;
}
/**
* Set the HttpClient
*
* @param HttpClient $httpClient HttpClient to use
*/
public function setHttpClient(HttpClient $httpClient): void
{
$this->httpClient = $httpClient;
}
/**
* Retrieve the log level
*
* @return ?string Current log level
*/
public function getLogLevel(): ?string
{
return $this->logLevel;
}
/**
* Set log level to debug
*
* @param string $logLevel log level to use
*/
public function setLogLevel(string $logLevel = null): void
{
$this->logLevel = $this->getArg($logLevel, self::ENV_LOG);
}
}
<?php
namespace Twilio\Base;
use Twilio\Exceptions\TwilioException;
use Twilio\Values;
/**
* @property bool $mms
* @property bool $sms
* @property bool $voice
* @property bool $fax
*/
class PhoneNumberCapabilities
{
protected $mms;
protected $sms;
protected $voice;
protected $fax;
public function __construct(array $capabilities)
{
$this->mms = Values::array_get($capabilities, 'mms', "false");
$this->sms = Values::array_get($capabilities, 'sms', "false");
$this->voice = Values::array_get($capabilities, 'voice', "false");
$this->fax = Values::array_get($capabilities, 'fax', "false");
}
/**
* Access the mms
*/
public function getMms(): bool
{
return $this->mms;
}
/**
* Access the sms
*/
public function getSms(): bool
{
return $this->sms;
}
/**
* Access the voice
*/
public function getVoice(): bool
{
return $this->voice;
}
/**
* Access the fax
*/
public function getFax(): bool
{
return $this->fax;
}
public function __get(string $name)
{
if (\property_exists($this, $name)) {
$method = 'get' . \ucfirst($name);
return $this->$method();
}
throw new TwilioException('Unknown subresource ' . $name);
}
public function __toString(): string
{
return "[Twilio.Base.PhoneNumberCapabilities " .
"(
mms: " . json_encode($this->mms) . ",
sms: " . json_encode($this->sms) . ",
voice: " . json_encode($this->voice) . ",
fax: " . json_encode($this->fax) . "
)]";
}
}
......@@ -2,21 +2,50 @@
namespace Twilio;
class Deserialize {
use Twilio\Base\PhoneNumberCapabilities;
class Deserialize
{
/**
* Deserialize a string date into a DateTime object
*
* @param string $s A date or date and time, can be iso8601, rfc2822,
* @param string $s A date or date and time, can be iso8601, rfc2822,
* YYYY-MM-DD format.
* @return \DateTime DateTime corresponding to the input string, in UTC time.
* @return \DateTime|string DateTime corresponding to the input string, in UTC time.
*/
public static function dateTime($s) {
public static function dateTime(?string $s)
{
try {
return new \DateTime($s, new \DateTimeZone('UTC'));
if ($s) {
return new \DateTime($s, new \DateTimeZone('UTC'));
}
} catch (\Exception $e) {
return $s;
// no-op
}
return $s;
}
/**
* Deserialize an array into a PhoneNumberCapabilities object
*
* @param array|null $arr An array
* @return PhoneNumberCapabilities|array PhoneNumberCapabilities object corresponding to the input array.
*/
public static function phoneNumberCapabilities(?array $arr)
{
try {
if ($arr) {
$required = ["mms", "sms", "voice", "fax"];
if (count(array_intersect($required, array_keys($arr))) > 0) {
return new PhoneNumberCapabilities($arr);
}
}
} catch (\Exception $e) {
// no-op
}
return $arr;
}
}
......@@ -4,6 +4,7 @@
namespace Twilio;
use Twilio\Http\Response;
use Twilio\Rest\Client;
/**
......@@ -13,7 +14,7 @@ use Twilio\Rest\Client;
*/
abstract class Domain {
/**
* @var \Twilio\Rest\Client Twilio Client
* @var Client Twilio Client
*/
protected $client;
......@@ -24,7 +25,7 @@ abstract class Domain {
/**
* Construct a new Domain
* @param \Twilio\Rest\Client $client used to communicate with Twilio
* @param Client $client used to communicate with Twilio
*/
public function __construct(Client $client) {
$this->client = $client;
......@@ -37,8 +38,8 @@ abstract class Domain {
* @param string $uri Version relative URI
* @return string Absolute URL for this domain
*/
public function absoluteUrl($uri) {
return implode('/', array(trim($this->baseUrl, '/'), trim($uri, '/')));
public function absoluteUrl(string $uri): string {
return \implode('/', [\trim($this->baseUrl, '/'), \trim($uri, '/')]);
}
/**
......@@ -51,12 +52,13 @@ abstract class Domain {
* @param array $headers HTTP headers to send with the request
* @param string $user User to authenticate as
* @param string $password Password
* @param null $timeout Request timeout
* @return \Twilio\Http\Response the response for the request
* @param int $timeout Request timeout
* @return Response the response for the request
*/
public function request($method, $uri, $params = array(), $data = array(),
$headers = array(), $user = null, $password=null,
$timeout=null) {
public function request(string $method, string $uri,
array $params = [], array $data = [], array $headers = [],
string $user = null, string $password = null,
int $timeout = null): Response {
$url = $this->absoluteUrl($uri);
return $this->client->request(
$method,
......@@ -70,14 +72,11 @@ abstract class Domain {
);
}
/**
* @return \Twilio\Rest\Client
*/
public function getClient() {
public function getClient(): Client {
return $this->client;
}
public function __toString() {
public function __toString(): string {
return '[Domain]';
}
}
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
......@@ -6,6 +6,8 @@ namespace Twilio\Exceptions;
class RestException extends TwilioException {
protected $statusCode;
protected $details;
protected $moreInfo;
/**
* Construct the exception. Note: The message is NOT binary safe.
......@@ -13,10 +15,14 @@ class RestException extends TwilioException {
* @param string $message [optional] The Exception message to throw.
* @param int $code [optional] The Exception code.
* @param int $statusCode [optional] The HTTP Status code.
* @param string $moreInfo [optional] More information about the error.
* @param array $details [optional] Additional details about the error.
* @since 5.1.0
*/
public function __construct($message, $code, $statusCode) {
public function __construct(string $message, int $code, int $statusCode, string $moreInfo = '', array $details = []) {
$this->statusCode = $statusCode;
$this->moreInfo = $moreInfo;
$this->details = $details;
parent::__construct($message, $code);
}
......@@ -24,9 +30,23 @@ class RestException extends TwilioException {
* Get the HTTP Status Code of the RestException
* @return int HTTP Status Code
*/
public function getStatusCode() {
public function getStatusCode(): int {
return $this->statusCode;
}
/**
* Get more information of the RestException
* @return string More error information
*/
public function getMoreInfo(): string {
return $this->moreInfo;
}
}
\ No newline at end of file
/**
* Get the details of the RestException
* @return exception details
*/
public function getDetails(): array {
return $this->details;
}
}
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
......@@ -5,7 +5,8 @@ namespace Twilio\Http;
interface Client {
public function request($method, $url, $params = array(), $data = array(),
$headers = array(), $user = null, $password = null,
$timeout = null);
}
\ No newline at end of file
public function request(string $method, string $url,
array $params = [], array $data = [], array $headers = [],
string $user = null, string $password = null,
int $timeout = null): Response;
}
......@@ -4,174 +4,237 @@
namespace Twilio\Http;
use Twilio\Exceptions\ConfigurationException;
use Twilio\Exceptions\EnvironmentException;
class CurlClient implements Client {
const DEFAULT_TIMEOUT = 60;
protected $curlOptions = array();
protected $debugHttp = false;
public const DEFAULT_TIMEOUT = 60;
protected $curlOptions = [];
public function __construct(array $options = array()) {
public $lastRequest;
public $lastResponse;
public function __construct(array $options = []) {
$this->curlOptions = $options;
$this->debugHttp = getenv('DEBUG_HTTP_TRAFFIC') === 'true';
}
public function request($method, $url, $params = array(), $data = array(),
$headers = array(), $user = null, $password = null,
$timeout = null) {
public function request(string $method, string $url,
array $params = [], array $data = [], array $headers = [],
string $user = null, string $password = null,
int $timeout = null): Response {
$options = $this->options($method, $url, $params, $data, $headers,
$user, $password, $timeout);
$this->lastRequest = $options;
$this->lastResponse = null;
try {
if (!$curl = curl_init()) {
if (!$curl = \curl_init()) {
throw new EnvironmentException('Unable to initialize cURL');
}
if (!curl_setopt_array($curl, $options)) {
throw new EnvironmentException(curl_error($curl));
if (!\curl_setopt_array($curl, $options)) {
throw new EnvironmentException(\curl_error($curl));
}
if (!$response = curl_exec($curl)) {
throw new EnvironmentException(curl_error($curl));
if (!$response = \curl_exec($curl)) {
throw new EnvironmentException(\curl_error($curl));
}
$parts = explode("\r\n\r\n", $response, 3);
list($head, $body) = ($parts[0] == 'HTTP/1.1 100 Continue')
? array($parts[1], $parts[2])
: array($parts[0], $parts[1]);
$parts = \explode("\r\n\r\n", $response, 3);
if ($this->debugHttp) {
$u = parse_url($url);
$hdrLine = $method . ' ' . $u['path'];
if (isset($u['query']) && strlen($u['query']) > 0 ) {
$hdrLine = $hdrLine . '?' . $u['query'];
}
error_log($hdrLine);
foreach ($headers as $key => $value) {
error_log("$key: $value");
}
if ($method === 'POST') {
error_log("\n" . $options[CURLOPT_POSTFIELDS] . "\n");
}
}
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
list($head, $body) = (
\preg_match('/\AHTTP\/1.\d 100 Continue\Z/', $parts[0])
|| \preg_match('/\AHTTP\/1.\d 200 Connection established\Z/', $parts[0])
|| \preg_match('/\AHTTP\/1.\d 200 Tunnel established\Z/', $parts[0])
)
? array($parts[1], $parts[2])
: array($parts[0], $parts[1]);
$statusCode = \curl_getinfo($curl, CURLINFO_HTTP_CODE);
$responseHeaders = array();
$headerLines = explode("\r\n", $head);
array_shift($headerLines);
$responseHeaders = [];
$headerLines = \explode("\r\n", $head);
\array_shift($headerLines);
foreach ($headerLines as $line) {
list($key, $value) = explode(':', $line, 2);
list($key, $value) = \explode(':', $line, 2);
$responseHeaders[$key] = $value;
}
curl_close($curl);
\curl_close($curl);
if (isset($buffer) && is_resource($buffer)) {
fclose($buffer);
if (isset($options[CURLOPT_INFILE]) && \is_resource($options[CURLOPT_INFILE])) {
\fclose($options[CURLOPT_INFILE]);
}
if ($this->debugHttp) {
error_log("HTTP/1.1 $statusCode");
foreach ($responseHeaders as $key => $value) {
error_log("$key: $value");
}
error_log("\n$body");
}
return new Response($statusCode, $body, $responseHeaders);
$this->lastResponse = new Response($statusCode, $body, $responseHeaders);
return $this->lastResponse;
} catch (\ErrorException $e) {
if (isset($curl) && is_resource($curl)) {
curl_close($curl);
if (isset($curl) && \is_resource($curl)) {
\curl_close($curl);
}
if (isset($buffer) && is_resource($buffer)) {
fclose($buffer);
if (isset($options[CURLOPT_INFILE]) && \is_resource($options[CURLOPT_INFILE])) {
\fclose($options[CURLOPT_INFILE]);
}
throw $e;
}
}
public function options($method, $url, $params = array(), $data = array(),
$headers = array(), $user = null, $password = null,
$timeout = null) {
$timeout = is_null($timeout)
? self::DEFAULT_TIMEOUT
: $timeout;
$options = $this->curlOptions + array(
public function options(string $method, string $url,
array $params = [], array $data = [], array $headers = [],
string $user = null, string $password = null,
int $timeout = null): array {
$timeout = $timeout ?? self::DEFAULT_TIMEOUT;
$options = $this->curlOptions + [
CURLOPT_URL => $url,
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_INFILESIZE => -1,
CURLOPT_HTTPHEADER => array(),
CURLOPT_INFILESIZE => Null,
CURLOPT_HTTPHEADER => [],
CURLOPT_TIMEOUT => $timeout,
);
];
foreach ($headers as $key => $value) {
$options[CURLOPT_HTTPHEADER][] = "$key: $value";
}
if ($user && $password) {
$options[CURLOPT_HTTPHEADER][] = 'Authorization: Basic ' . base64_encode("$user:$password");
$options[CURLOPT_HTTPHEADER][] = 'Authorization: Basic ' . \base64_encode("$user:$password");
}
$body = $this->buildQuery($params);
if ($body) {
$options[CURLOPT_URL] .= '?' . $body;
$query = $this->buildQuery($params);
if ($query) {
$options[CURLOPT_URL] .= '?' . $query;
}
switch (strtolower(trim($method))) {
switch (\strtolower(\trim($method))) {
case 'get':
$options[CURLOPT_HTTPGET] = true;
break;
case 'post':
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = $this->buildQuery($data);
if ($this->hasFile($data)) {
[$headers, $body] = $this->buildMultipartOptions($data);
$options[CURLOPT_POSTFIELDS] = $body;
$options[CURLOPT_HTTPHEADER] = \array_merge($options[CURLOPT_HTTPHEADER], $headers);
}
elseif ($headers['Content-Type'] === 'application/json') {
$options[CURLOPT_POSTFIELDS] = json_encode($data);
}
else {
$options[CURLOPT_POSTFIELDS] = $this->buildQuery($data);
}
break;
case 'put':
$options[CURLOPT_PUT] = true;
if ($data) {
if ($buffer = fopen('php://memory', 'w+')) {
$dataString = $this->buildQuery($data);
fwrite($buffer, $dataString);
fseek($buffer, 0);
$options[CURLOPT_INFILE] = $buffer;
$options[CURLOPT_INFILESIZE] = strlen($dataString);
} else {
throw new EnvironmentException('Unable to open a temporary file');
}
$options[CURLOPT_CUSTOMREQUEST] = 'PUT';
if ($this->hasFile($data)) {
[$headers, $body] = $this->buildMultipartOptions($data);
$options[CURLOPT_POSTFIELDS] = $body;
$options[CURLOPT_HTTPHEADER] = \array_merge($options[CURLOPT_HTTPHEADER], $headers);
}
elseif ($headers['Content-Type'] === 'application/json') {
$options[CURLOPT_POSTFIELDS] = json_encode($data);
}
else {
$options[CURLOPT_POSTFIELDS] = $this->buildQuery($data);
}
break;
case 'head':
$options[CURLOPT_NOBODY] = true;
break;
default:
$options[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
$options[CURLOPT_CUSTOMREQUEST] = \strtoupper($method);
}
return $options;
}
public function buildQuery($params) {
$parts = array();
public function buildQuery(?array $params): string {
$parts = [];
$params = $params ?: [];
foreach ($params as $key => $value) {
if (\is_array($value)) {
foreach ($value as $item) {
$parts[] = \urlencode((string)$key) . '=' . \urlencode((string)$item);
}
} else {
$parts[] = \urlencode((string)$key) . '=' . \urlencode((string)$value);
}
}
return \implode('&', $parts);
}
if (is_string($params)) {
return $params;
private function hasFile(array $data): bool {
foreach ($data as $value) {
if ($value instanceof File) {
return true;
}
}
$params = $params ?: array();
return false;
}
foreach ($params as $key => $value) {
if (is_array($value)) {
foreach ($value as $item) {
$parts[] = urlencode((string)$key) . '=' . urlencode((string)$item);
private function buildMultipartOptions(array $data): array {
$boundary = \uniqid('', true);
$delimiter = "-------------{$boundary}";
$body = '';
foreach ($data as $key => $value) {
if ($value instanceof File) {
$contents = $value->getContents();
if ($contents === null) {
$chunk = \file_get_contents($value->getFileName());
$filename = \basename($value->getFileName());
} elseif (\is_resource($contents)) {
$chunk = '';
while (!\feof($contents)) {
$chunk .= \fread($contents, 8096);
}
$filename = $value->getFileName();
} elseif (\is_string($contents)) {
$chunk = $contents;
$filename = $value->getFileName();
} else {
throw new \InvalidArgumentException('Unsupported content type');
}
$headers = '';
$contentType = $value->getContentType();
if ($contentType !== null) {
$headers .= "Content-Type: {$contentType}\r\n";
}
$body .= \vsprintf("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n%s\r\n%s\r\n", [
$delimiter,
$key,
$filename,
$headers,
$chunk,
]);
} else {
$parts[] = urlencode((string)$key) . '=' . urlencode((string)$value);
$body .= \vsprintf("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", [
$delimiter,
$key,
$value,
]);
}
}
return implode('&', $parts);
$body .= "--{$delimiter}--\r\n";
return [
[
"Content-Type: multipart/form-data; boundary={$delimiter}",
'Content-Length: ' . \strlen($body),
],
$body,
];
}
}
<?php
declare(strict_types=1);
namespace Twilio\Http;
final class File {
/**
* @var string
*/
private $fileName;
/**
* @var resource|string|mixed|null
*/
private $contents;
/**
* @var string|null
*/
private $contentType;
/**
* @param string $fileName full file path or file name for passed $contents
* @param string|resource|mixed|null $contents
* @param string $contentType
*/
public function __construct(string $fileName, $contents = null, string $contentType = null) {
$this->fileName = $fileName;
$this->contents = $contents;
$this->contentType = $contentType;
}
/**
* @return resource|string|mixed|null
*/
public function getContents() {
return $this->contents;
}
public function getFileName(): string {
return $this->fileName;
}
public function getContentType(): ?string {
return $this->contentType;
}
}
<?php
namespace Twilio\Http;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Query;
use GuzzleHttp\Psr7\Request;
use Twilio\Exceptions\HttpException;
final class GuzzleClient implements Client {
/**
* @var ClientInterface
*/
private $client;
public function __construct(ClientInterface $client) {
$this->client = $client;
}
public function request(string $method, string $url,
array $params = [], array $data = [], array $headers = [],
string $user = null, string $password = null,
int $timeout = null): Response {
try {
$options = [
'timeout' => $timeout,
'auth' => [$user, $password],
'allow_redirects' => false,
];
if ($params) {
$options['query'] = Query::build($params, PHP_QUERY_RFC1738);
}
if ($method === 'POST' || $method === 'PUT') {
if ($this->hasFile($data)) {
$options['multipart'] = $this->buildMultipartParam($data);
} else {
$options['body'] = Query::build($data, PHP_QUERY_RFC1738);
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
}
$response = $this->client->send(new Request($method, $url, $headers), $options);
} catch (BadResponseException $exception) {
$response = $exception->getResponse();
} catch (\Exception $exception) {
throw new HttpException('Unable to complete the HTTP request', 0, $exception);
}
// Casting the body (stream) to a string performs a rewind, ensuring we return the entire response.
// See https://stackoverflow.com/a/30549372/86696
return new Response($response->getStatusCode(), (string)$response->getBody(), $response->getHeaders());
}
private function hasFile(array $data): bool {
foreach ($data as $value) {
if ($value instanceof File) {
return true;
}
}
return false;
}
private function buildMultipartParam(array $data): array {
$multipart = [];
foreach ($data as $key => $value) {
if ($value instanceof File) {
$contents = $value->getContents();
if ($contents === null) {
$contents = fopen($value->getFileName(), 'rb');
}
$chunk = [
'name' => $key,
'contents' => $contents,
'filename' => $value->getFileName(),
];
if ($value->getContentType() !== null) {
$chunk['headers']['Content-Type'] = $value->getContentType();
}
} else {
$chunk = [
'name' => $key,
'contents' => $value,
];
}
$multipart[] = $chunk;
}
return $multipart;
}
}
......@@ -9,7 +9,7 @@ class Response {
protected $content;
protected $statusCode;
public function __construct($statusCode, $content, $headers = array()) {
public function __construct(int $statusCode, ?string $content, ?array $headers = []) {
$this->statusCode = $statusCode;
$this->content = $content;
$this->headers = $headers;
......@@ -19,25 +19,22 @@ class Response {
* @return mixed
*/
public function getContent() {
return json_decode($this->content, true);
return \json_decode($this->content, true);
}
/**
* @return mixed
*/
public function getStatusCode() {
public function getStatusCode(): int {
return $this->statusCode;
}
public function getHeaders() {
public function getHeaders(): array {
return $this->headers;
}
public function ok() {
public function ok(): bool {
return $this->getStatusCode() < 400;
}
public function __toString() {
public function __toString(): string {
return '[Response] HTTP ' . $this->getStatusCode() . ' ' . $this->content;
}
}
......@@ -6,14 +6,14 @@ namespace Twilio;
class InstanceContext {
protected $version;
protected $solution = array();
protected $solution = [];
protected $uri;
public function __construct(Version $version) {
$this->version = $version;
}
public function __toString() {
public function __toString(): string {
return '[InstanceContext]';
}
}
\ No newline at end of file
}
......@@ -6,19 +6,23 @@ namespace Twilio;
class InstanceResource {
protected $version;
protected $context = null;
protected $properties = array();
protected $solution = array();
protected $context;
protected $properties = [];
protected $solution = [];
public function __construct(Version $version) {
$this->version = $version;
}
public function toArray() {
public function toArray(): array {
return $this->properties;
}
public function __toString() {
public function __toString(): string {
return '[InstanceResource]';
}
}
\ No newline at end of file
public function __isset($name): bool {
return \array_key_exists($name, $this->properties);
}
}
......@@ -12,20 +12,25 @@ class AccessToken {
private $ttl;
private $identity;
private $nbf;
private $region;
/** @var Grant[] $grants */
private $grants;
/** @var string[] $customClaims */
private $customClaims;
public function __construct($accountSid, $signingKeySid, $secret, $ttl = 3600, $identity = null) {
public function __construct(string $accountSid, string $signingKeySid, string $secret, int $ttl = 3600, string $identity = null, string $region = null) {
$this->signingKeySid = $signingKeySid;
$this->accountSid = $accountSid;
$this->secret = $secret;
$this->ttl = $ttl;
$this->region = $region;
if (!is_null($identity)) {
if ($identity !== null) {
$this->identity = $identity;
}
$this->grants = array();
$this->grants = [];
$this->customClaims = [];
}
/**
......@@ -35,7 +40,7 @@ class AccessToken {
*
* @return $this updated access token
*/
public function setIdentity($identity) {
public function setIdentity(string $identity): self {
$this->identity = $identity;
return $this;
}
......@@ -45,18 +50,18 @@ class AccessToken {
*
* @return string the identity
*/
public function getIdentity() {
public function getIdentity(): string {
return $this->identity;
}
/**
* Set the nbf of this access token
*
* @param integer $nbf nbf in epoch seconds of the grant
* @param int $nbf nbf in epoch seconds of the grant
*
* @return $this updated access token
*/
public function setNbf($nbf) {
public function setNbf(int $nbf): self {
$this->nbf = $nbf;
return $this;
}
......@@ -64,12 +69,33 @@ class AccessToken {
/**
* Returns the nbf of the grant
*
* @return integer the nbf in epoch seconds
* @return int the nbf in epoch seconds
*/
public function getNbf() {
public function getNbf(): int {
return $this->nbf;
}
/**
* Set the region of this access token
*
* @param string $region Home region of the account sid in this access token
*
* @return $this updated access token
*/
public function setRegion(string $region): self {
$this->region = $region;
return $this;
}
/**
* Returns the region of this access token
*
* @return string Home region of the account sid in this access token
*/
public function getRegion(): string {
return $this->region;
}
/**
* Add a grant to the access token
*
......@@ -77,21 +103,34 @@ class AccessToken {
*
* @return $this the updated access token
*/
public function addGrant(Grant $grant) {
public function addGrant(Grant $grant): self {
$this->grants[] = $grant;
return $this;
}
/**
* Allows to set custom claims, which then will be encoded into JWT payload.
*
* @param string $name
* @param string $value
*/
public function addClaim(string $name, string $value): void {
$this->customClaims[$name] = $value;
}
public function toJWT($algorithm = 'HS256') {
$header = array(
public function toJWT(string $algorithm = 'HS256'): string {
$header = [
'cty' => 'twilio-fpa;v=1',
'typ' => 'JWT'
);
];
if ($this->region) {
$header['twr'] = $this->region;
}
$now = time();
$now = \time();
$grants = array();
$grants = [];
if ($this->identity) {
$grants['identity'] = $this->identity;
}
......@@ -99,32 +138,32 @@ class AccessToken {
foreach ($this->grants as $grant) {
$payload = $grant->getPayload();
if (empty($payload)) {
$payload = json_decode('{}');
$payload = \json_decode('{}');
}
$grants[$grant->getGrantKey()] = $payload;
}
if (empty($grants)) {
$grants = json_decode('{}');
$grants = \json_decode('{}');
}
$payload = array(
$payload = \array_merge($this->customClaims, [
'jti' => $this->signingKeySid . '-' . $now,
'iss' => $this->signingKeySid,
'sub' => $this->accountSid,
'exp' => $now + $this->ttl,
'grants' => $grants
);
]);
if (!is_null($this->nbf)) {
if ($this->nbf !== null) {
$payload['nbf'] = $this->nbf;
}
return JWT::encode($payload, $this->secret, $algorithm, $header);
}
public function __toString() {
public function __toString(): string {
return $this->toJWT();
}
}
\ No newline at end of file
}
......@@ -20,16 +20,16 @@ class ScopeURI {
public $privilege;
public $params;
public function __construct($service, $privilege, $params = array()) {
public function __construct(string $service, string $privilege, array $params = []) {
$this->service = $service;
$this->privilege = $privilege;
$this->params = $params;
}
public function toString() {
public function toString(): string {
$uri = "scope:{$this->service}:{$this->privilege}";
if (count($this->params)) {
$uri .= "?" . http_build_query($this->params, '', '&');
if (\count($this->params)) {
$uri .= '?' . \http_build_query($this->params, '', '&');
}
return $uri;
}
......@@ -41,27 +41,27 @@ class ScopeURI {
* @return ScopeURI The parsed scope uri
* @throws \UnexpectedValueException
*/
public static function parse($uri) {
if (strpos($uri, 'scope:') !== 0) {
public static function parse(string $uri): ScopeURI {
if (\strpos($uri, 'scope:') !== 0) {
throw new \UnexpectedValueException(
'Not a scope URI according to scheme');
}
$parts = explode('?', $uri, 1);
$parts = \explode('?', $uri, 1);
$params = null;
if (count($parts) > 1) {
parse_str($parts[1], $params);
if (\count($parts) > 1) {
\parse_str($parts[1], $params);
}
$parts = explode(':', $parts[0], 2);
$parts = \explode(':', $parts[0], 2);
if (count($parts) != 3) {
if (\count($parts) !== 3) {
throw new \UnexpectedValueException(
'Not enough parts for scope URI');
}
list($scheme, $service, $privilege) = $parts;
[$scheme, $service, $privilege] = $parts;
return new ScopeURI($service, $privilege, $params);
}
}
\ No newline at end of file
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment