diff options
author | Jordan Nornhold <nornholdj@gmail.com> | 2015-09-08 22:24:16 -0400 |
---|---|---|
committer | Jordan Nornhold <nornholdj@gmail.com> | 2015-09-08 22:24:16 -0400 |
commit | aeea514f81c652257b64e606c771cc51249db49c (patch) | |
tree | 7884f1a4c5b418fda7ef84693f7e1d0aed0b6f0a | |
parent | 3fb7f608b1252d1bf973f79af8f98ee11b9f1a0d (diff) | |
parent | c48ff8467d2a4ae229df6a3b8949e82d0d792ee5 (diff) | |
download | php-sparkpost-aeea514f81c652257b64e606c771cc51249db49c.zip php-sparkpost-aeea514f81c652257b64e606c771cc51249db49c.tar.gz php-sparkpost-aeea514f81c652257b64e606c771cc51249db49c.tar.bz2 |
Merge pull request #15 from MattApril/master
SDK Refactor for easy extensibility
-rw-r--r-- | examples/unwrapped/create_template.php | 25 | ||||
-rw-r--r-- | lib/SparkPost/APIResource.php | 216 | ||||
-rw-r--r-- | lib/SparkPost/SparkPost.php | 4 | ||||
-rw-r--r-- | lib/SparkPost/Transmission.php | 145 | ||||
-rw-r--r-- | test/unit/APIResourceTest.php | 144 | ||||
-rw-r--r-- | test/unit/SparkPostTest.php | 1 | ||||
-rw-r--r-- | test/unit/TransmissionTest.php | 4 |
7 files changed, 410 insertions, 129 deletions
diff --git a/examples/unwrapped/create_template.php b/examples/unwrapped/create_template.php new file mode 100644 index 0000000..717430f --- /dev/null +++ b/examples/unwrapped/create_template.php @@ -0,0 +1,25 @@ +<?php +namespace Examples\Unwrapped; +require_once (dirname(__FILE__).'/../bootstrap.php'); +use SparkPost\SparkPost; +use SparkPost\APIResource; + +$key = 'YOURAPIKEY'; +SparkPost::setConfig(array('key'=>$key)); + +try { + // define the endpoint + APIResource::$endpoint = 'templates'; + + $templateConfig = array( + 'name' => 'Summer Sale!', + 'content.from' => 'marketing@bounces.company.example', + 'content.subject' => 'Summer deals', + 'content.html' => '<b>Check out these deals!</b>', + ); + $results = APIResource::sendRequest($templateConfig); + echo 'Congrats you can use your SDK!'; +} catch (\Exception $exception) { + echo $exception->getMessage(); +} +?>
\ No newline at end of file diff --git a/lib/SparkPost/APIResource.php b/lib/SparkPost/APIResource.php new file mode 100644 index 0000000..469ba7e --- /dev/null +++ b/lib/SparkPost/APIResource.php @@ -0,0 +1,216 @@ +<?php +namespace SparkPost; +use Guzzle\Http\Client; +use Guzzle\Http\Exception\ClientErrorResponseException; + +/** + * @desc SDK interface for managing SparkPost API endpoints + */ +class APIResource { + + /** + * @desc name of the API endpoint, mainly used for URL construction. + * @var string + */ + public static $endpoint; + + /** + * @desc singleton holder to create a guzzle http client + * @var \GuzzleHttp\Client + */ + protected static $request; + + /** + * @desc Mapping for values passed into the send method to the values needed for the respective API + * @var array + */ + protected static $parameterMappings = array(); + + /** + * @desc Sets up default structure and default values for the model that is acceptable by the API + * @var array + */ + protected static $structure = array(); + + /** + * @desc Ensure that this class cannot be instansiated + */ + private function __construct() {} + + /** + * @desc Creates and returns a guzzle http client. + * @return \GuzzleHttp\Client + */ + protected static function getHttpClient() { + if(!isset(self::$request)) { + self::$request = new Client(); + } + return self::$request; + } + + /** + * @desc Private Method helper to get the configuration values to create the base url for the current API endpoint + * + * @return string base url for the transmissions API + */ + protected static function getBaseUrl($config) { + $baseUrl = '/api/' . $config['version'] . '/' . static::$endpoint; + return $config['protocol'] . '://' . $config['host'] . ($config['port'] ? ':' . $config['port'] : '') . $baseUrl; + } + + + /** + * @desc Private Method helper to reference parameter mappings and set the right value for the right parameter + */ + protected static function setMappedValue (&$model, $mapKey, $value) { + //get mapping + if( empty(static::$parameterMappings) ) { + // if parameterMappings is empty we can assume that no wrapper is defined + // for the current endpoint and we will use the mapKey to define the mappings directly + $mapPath = $mapKey; + }elseif(array_key_exists($mapKey, static::$parameterMappings)) { + // use only defined parameter mappings to construct $model + $mapPath = static::$parameterMappings[$mapKey]; + } else { + return; + } + + $path = explode('.', $mapPath); + $temp = &$model; + foreach( $path as $key ) { + if( !isset($temp[$key]) ){ + $temp[$key] = null; + } + $temp = &$temp[$key]; + } + $temp = $value; + + } + + protected static function buildRequestModel( $requestConfig, $model=array() ) { + foreach($requestConfig as $key=>$value) { + self::setMappedValue($model, $key, $value); + } + return $model; + } + + /** + * @desc Method for issuing POST requests + * + * @return array API repsonse represented as key-value pairs + */ + public static function sendRequest( $requestConfig ) { + $hostConfig = SparkPost::getConfig(); + $request = self::getHttpClient(); + + //create model from $transmissionConfig + $model = static::$structure; + $requestModel = self::buildRequestModel( $requestConfig, $model ); + + //send the request + try { + $response = $request->post( + self::getBaseUrl($hostConfig), + array('authorization' => $hostConfig['key']), + json_encode($requestModel), + array("verify"=>$hostConfig['strictSSL']) + )->send(); + + return $response->json(); + } + /* + * Handles 4XX responses + */ + catch (ClientErrorResponseException $exception) { + $response = $exception->getResponse(); + $responseArray = $response->json(); + throw new \Exception(json_encode($responseArray['errors'])); + } + /* + * Handles 5XX Errors, Configuration Errors, and a catch all for other errors + */ + catch (\Exception $exception) { + throw new \Exception("Unable to contact ".ucfirst(static::$endpoint)." API: ". $exception->getMessage()); + } + } + + + /** + * @desc Wrapper method for issuing GET request to current API endpoint + * + * @param string $resourcePath (optional) string resource path of specific resource + * @param array $options (optional) query string parameters + * @return array Result set of transmissions found + */ + public static function fetchResource( $resourcePath=null, $options=array() ) { + return self::callResource( 'get', $resourcePath, $options ); + } + + /** + * @desc Wrapper method for issuing DELETE request to current API endpoint + * + * @param string $resourcePath (optional) string resource path of specific resource + * @param array $options (optional) query string parameters + * @return array Result set of transmissions found + */ + public static function deleteResource( $resourcePath=null, $options=array() ) { + return self::callResource( 'delete', $resourcePath, $options ); + } + + /** + * @desc Private Method for issuing GET and DELETE request to current API endpoint + * + * This method is responsible for getting the collection _and_ + * a specific entity from the API endpoint + * + * If resourcePath parameter is omitted, then we fetch the collection + * + * @param string $action HTTP method type + * @param string $resourcePath (optional) string resource path of specific resource + * @param array $options (optional) query string parameters + * @return array Result set of action performed on resource + */ + private static function callResource( $action, $resourcePath=null, $options=array() ) { + + if( !in_array( $action, array('get', 'delete') ) ) throw new \Exception('Invalid resource action'); + + //build the url + $hostConfig = SparkPost::getConfig(); + $url = self::getBaseUrl($hostConfig); + if (!is_null($resourcePath)){ + $url .= '/'.$resourcePath; + } + + // untested: + if( !empty($options) ) { + $queryString = http_build_query($options); + $url .= '?'.$queryString; + } + + $request = self::getHttpClient(); + + //make request + try { + $response = $request->{$action}($url, array('authorization' => $hostConfig['key']), array("verify"=>$hostConfig['strictSSL']))->send(); + return $response->json(); + } + /* + * Handles 4XX responses + */ + catch (ClientErrorResponseException $exception) { + $response = $exception->getResponse(); + $statusCode = $response->getStatusCode(); + if($statusCode === 404) { + throw new \Exception("The specified resource does not exist", 404); + } + throw new \Exception("Received bad response from ".ucfirst(static::$endpoint)." API: ". $statusCode ); + } + /* + * Handles 5XX Errors, Configuration Errors, and a catch all for other errors + */ + catch (\Exception $exception) { + throw new \Exception("Unable to contact ".ucfirst(static::$endpoint)." API: ". $exception->getMessage()); + } + } + +} diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 5c2a554..755df46 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -51,6 +51,10 @@ class SparkPost { } return self::$config; } + + public static function unsetConfig() { + self::$config = NULL; + } } ?>
\ No newline at end of file diff --git a/lib/SparkPost/Transmission.php b/lib/SparkPost/Transmission.php index dcc3fe1..4e54f1d 100644 --- a/lib/SparkPost/Transmission.php +++ b/lib/SparkPost/Transmission.php @@ -6,18 +6,15 @@ use Guzzle\Http\Exception\ClientErrorResponseException; /** * @desc SDK interface for managing transmissions */ -class Transmission { - /** - * @desc singleton holder to create a guzzle http client - * @var \GuzzleHttp\Client - */ - private static $request; +class Transmission extends APIResource { + + public static $endpoint = 'transmissions'; /** * @desc Mapping for values passed into the send method to the values needed for the Transmission API * @var array */ - private static $parameterMappings = array( + protected static $parameterMappings = array( 'campaign'=>'campaign_id', 'metadata'=>'metadata', 'substitutionData'=>'substitution_data', @@ -42,7 +39,7 @@ class Transmission { * @desc Sets up default structure and default values for the model that is acceptable by the API * @var array */ - private static $structure = array( + protected static $structure = array( 'return_path'=>"default@sparkpostmail.com", 'content'=>array( 'html'=>null, @@ -53,48 +50,6 @@ class Transmission { ); /** - * @desc Ensure that this class cannot be instansiated - */ - private function __construct() {} - - /** - * @desc Creates and returns a guzzle http client. - * @return \GuzzleHttp\Client - */ - private static function getHttpClient() { - if(!isset(self::$request)) { - self::$request = new Client(); - } - return self::$request; - } - - - /** - * @desc Private Method helper to reference parameter mappings and set the right value for the right parameter - */ - private static function setMappedValue (&$model, $mapKey, $value) { - //get mapping - if(array_key_exists($mapKey, self::$parameterMappings)) { - $temp = &$model; - $path = explode('.', self::$parameterMappings[$mapKey]); - foreach( $path as $key ) { - $temp = &$temp[$key]; - } - $temp = $value; - } //ignore anything we don't have a mapping for - } - - /** - * @desc Private Method helper to get the configuration values to create the base url for the transmissions API - * - * @return string base url for the transmissions API - */ - private static function getBaseUrl($config) { - $baseUrl = '/api/' . $config['version'] . '/transmissions'; - return $config['protocol'] . '://' . $config['host'] . ($config['port'] ? ':' . $config['port'] : '') . $baseUrl; - } - - /** * @desc Method for issuing POST request to the Transmissions API * * This method assumes that all the appropriate fields have @@ -120,80 +75,8 @@ class Transmission { * * @return array API repsonse represented as key-value pairs */ - public static function send($transmissionConfig) { - $hostConfig = SparkPost::getConfig(); - $request = self::getHttpClient(); - - //create model from $transmissionConfig - $model = self::$structure; - foreach($transmissionConfig as $key=>$value) { - self::setMappedValue($model, $key, $value); - } - - //send the request - try { - $response = $request->post(self::getBaseUrl($hostConfig), array('authorization' => $hostConfig['key']), json_encode($model), array("verify"=>$hostConfig['strictSSL']))->send(); - return $response->json(); - } - /* - * Handles 4XX responses - */ - catch (ClientErrorResponseException $exception) { - $response = $exception->getResponse(); - $responseArray = $response->json(); - throw new \Exception(json_encode($responseArray['errors'])); - } - /* - * Handles 5XX Errors, Configuration Errors, and a catch all for other errors - */ - catch (\Exception $exception) { - throw new \Exception('Unable to contact Transmissions API: '. $exception->getMessage()); - } - } - - /** - * @desc Private Method for issuing GET request to Transmissions API - * - * This method is responsible for getting the collection _and_ - * a specific entity from the Transmissions API - * - * If TransmissionID parameter is omitted, then we fetch the collection - * - * @param string $transmissionID (optional) string Transmission ID of specific Transmission to retrieve - * @return array Result set of transmissions found - */ - private static function fetch ($transmissionID = null) { - //figure out the url - $hostConfig = SparkPost::getConfig(); - $url = self::getBaseUrl($hostConfig); - if (!is_null($transmissionID)){ - $url .= '/'.$transmissionID; - } - - $request = self::getHttpClient(); - - //make request - try { - $response = $request->get($url, array('authorization' => $hostConfig['key']), array("verify"=>$hostConfig['strictSSL']))->send(); - return $response->json(); - } - /* - * Handles 4XX responses - */ - catch (ClientErrorResponseException $exception) { - $response = $exception->getResponse(); - $statusCode = $response->getStatusCode(); - if($statusCode === 404) { - throw new \Exception("The specified Transmission ID does not exist", 404); - } - throw new \Exception("Received bad response from Transmission API: ". $statusCode); - } - /* - * Handles 5XX Errors, Configuration Errors, and a catch all for other errors - */ - catch (\Exception $exception) { - throw new \Exception('Unable to contact Transmissions API: '. $exception->getMessage()); - } + public static function send( $transmissionConfig ) { + return self::sendRequest( $transmissionConfig ); } /** @@ -202,8 +85,12 @@ class Transmission { * * @return array result Set of transmissions */ - public static function all() { - return self::fetch(); + public static function all( $campaignID=null, $templateID=null ) { + $options = array(); + if( $campaignID !== NULL ) $options['campaign_id'] = $campaignID; + if( $templateID !== NULL ) $options['template_id'] = $templateID; + + return self::fetchResource( null, $options ); } /** @@ -214,7 +101,11 @@ class Transmission { * @return array result Single transmission represented in key-value pairs */ public static function find($transmissionID) { - return self::fetch($transmissionID); + return self::fetchResource($transmissionID); + } + + public static function delete( $transmissionID ) { + return self::deleteResource($transmissionID); } } diff --git a/test/unit/APIResourceTest.php b/test/unit/APIResourceTest.php new file mode 100644 index 0000000..fc85fa3 --- /dev/null +++ b/test/unit/APIResourceTest.php @@ -0,0 +1,144 @@ +<?php +namespace SparkPost\Test; + +use SparkPost\APIResource; +use SparkPost\SparkPost; +use Guzzle\Plugin\Mock\MockPlugin; +use Guzzle\Http\Message\Response; + + +class APIResourceTest extends \PHPUnit_Framework_TestCase { + + private $client = null; + + /** + * Allows access to private methods + * + * This is needed to mock the GuzzleHttp\Client responses + * + * @param string $name + * @return ReflectionMethod + */ + private static function getMethod($name) { + $class = new \ReflectionClass('\SparkPost\APIResource'); + $method = $class->getMethod($name); + $method->setAccessible(true); + return $method; + } + + /** + * (non-PHPdoc) + * @before + * @see PHPUnit_Framework_TestCase::setUp() + */ + public function setUp() { + SparkPost::setConfig(array('key'=>'blah')); + $this->client = self::getMethod('getHttpClient')->invoke(null); //so we can bootstrap api responses + APIResource::$endpoint = 'someValidEndpoint'; // when using APIResource directly an endpoint needs to be set. + } + + /** + * @desc Ensures that the configuration class is not instantiable. + */ + public function testConstructorCannotBeCalled() { + $class = new \ReflectionClass('\SparkPost\Transmission'); + $this->assertFalse($class->isInstantiable()); + } + + /** + * @desc tests happy path + */ + public function testFetchWithGoodResponse() { + $mock = new MockPlugin(); + $mock->addResponse(new Response(200, array(), '{"results":[{"test":"This is a test"}, {"test":"two"}]}')); + $this->client->addSubscriber($mock); + $this->assertEquals(array("results"=>array(array('test'=>'This is a test'), array('test'=>'two'))), APIResource::fetchResource()); + } + + /** + * @desc tests happy path + */ + public function testDeleteWithGoodResponse() { + $mock = new MockPlugin(); + $mock->addResponse(new Response(200, array(), '{"results":[{"test":"This is a test"}]}')); + $this->client->addSubscriber($mock); + + $this->assertEquals(array("results"=>array(array('test'=>'This is a test'))), APIResource::deleteResource('someId')); + } + + /** + * @desc tests 404 bad response + * @expectedException Exception + * @expectedExceptionMessage The specified resource does not exist + */ + public function testFetchWith404Response() { + $mock = new MockPlugin(); + $mock->addResponse(new Response(404, array())); + $this->client->addSubscriber($mock); + APIResource::fetchResource('someId'); + } + + /** + * @desc tests unknown bad response + * @expectedException Exception + * @expectedExceptionMessage Received bad response from SomeValidEndpoint API: 400 + */ + public function testFetchWithOtherBadResponse() { + $mock = new MockPlugin(); + $mock->addResponse(new Response(400, array())); + $this->client->addSubscriber($mock); + APIResource::fetchResource('someId'); + } + + /** + * @desc tests bad response + * @expectedException Exception + * @expectedExceptionMessageRegExp /Unable to contact SomeValidEndpoint API:.* / + */ + public function testFetchForCatchAllException() { + $mock = new MockPlugin(); + $mock->addResponse(new Response(500)); + $this->client->addSubscriber($mock); + APIResource::fetchResource('someId'); + } + + /** + * @desc tests happy path + */ + public function testSuccessfulSend() { + $body = array("result"=>array("transmission_id"=>"11668787484950529"), "status"=>array("message"=> "ok","code"=> "1000")); + $mock = new MockPlugin(); + $mock->addResponse(new Response(200, array(), json_encode($body))); + $this->client->addSubscriber($mock); + + + $this->assertEquals($body, APIResource::sendRequest(array('text'=>'awesome email'))); + } + + /** + * @desc tests bad response + * @expectedException Exception + * @expectedExceptionMessage ["This is a fake error"] + */ + public function testSendFor400Exception() { + $body = array('errors'=>array('This is a fake error')); + $mock = new MockPlugin(); + $mock->addResponse(new Response(400, array(), json_encode($body))); + $this->client->addSubscriber($mock); + APIResource::sendRequest(array('text'=>'awesome email')); + } + + + /** + * @desc tests bad response + * @expectedException Exception + * @expectedExceptionMessageRegExp /Unable to contact SomeValidEndpoint API:.* / + */ + public function testSendForCatchAllException() { + $mock = new MockPlugin(); + $mock->addResponse(new Response(500)); + $this->client->addSubscriber($mock); + APIResource::sendRequest(array('text'=>'awesome email')); + } + +} diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index 650cb36..f40461d 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -20,6 +20,7 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase { * @expectedExceptionMessage No configuration has been provided */ public function testGetConfigEmptyException() { + SparkPost::unsetConfig(); SparkPost::getConfig(); } diff --git a/test/unit/TransmissionTest.php b/test/unit/TransmissionTest.php index de72f2a..ca30b4a 100644 --- a/test/unit/TransmissionTest.php +++ b/test/unit/TransmissionTest.php @@ -68,7 +68,7 @@ class TransmissionTest extends \PHPUnit_Framework_TestCase { /** * @desc tests 404 bad response * @expectedException Exception - * @expectedExceptionMessage The specified Transmission ID does not exist + * @expectedExceptionMessage The specified resource does not exist */ public function testFindWith404Response() { $mock = new MockPlugin(); @@ -80,7 +80,7 @@ class TransmissionTest extends \PHPUnit_Framework_TestCase { /** * @desc tests unknown bad response * @expectedException Exception - * @expectedExceptionMessage Received bad response from Transmission API: 400 + * @expectedExceptionMessage Received bad response from Transmissions API: 400 */ public function testFindWithOtherBadResponse() { $mock = new MockPlugin(); |