diff options
-rw-r--r-- | examples/unwrapped/create_template.php | 25 | ||||
-rw-r--r-- | lib/SparkPost/APIResource.php | 216 | ||||
-rw-r--r-- | test/unit/APIResourceTest.php | 144 |
3 files changed, 385 insertions, 0 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/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')); + } + +} |