Skip to content

Commit d1edaca

Browse files
author
mapril
committed
added base object, test and example
1 parent 893adea commit d1edaca

File tree

3 files changed

+385
-0
lines changed

3 files changed

+385
-0
lines changed
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
namespace Examples\Unwrapped;
3+
require_once (dirname(__FILE__).'/../bootstrap.php');
4+
use SparkPost\SparkPost;
5+
use SparkPost\APIResource;
6+
7+
$key = 'YOURAPIKEY';
8+
SparkPost::setConfig(array('key'=>$key));
9+
10+
try {
11+
// define the endpoint
12+
APIResource::$endpoint = 'templates';
13+
14+
$templateConfig = array(
15+
'name' => 'Summer Sale!',
16+
'content.from' => 'marketing@bounces.company.example',
17+
'content.subject' => 'Summer deals',
18+
'content.html' => '<b>Check out these deals!</b>',
19+
);
20+
$results = APIResource::sendRequest($templateConfig);
21+
echo 'Congrats you can use your SDK!';
22+
} catch (\Exception $exception) {
23+
echo $exception->getMessage();
24+
}
25+
?>

lib/SparkPost/APIResource.php

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<?php
2+
namespace SparkPost;
3+
use Guzzle\Http\Client;
4+
use Guzzle\Http\Exception\ClientErrorResponseException;
5+
6+
/**
7+
* @desc SDK interface for managing SparkPost API endpoints
8+
*/
9+
class APIResource {
10+
11+
/**
12+
* @desc name of the API endpoint, mainly used for URL construction.
13+
* @var string
14+
*/
15+
public static $endpoint;
16+
17+
/**
18+
* @desc singleton holder to create a guzzle http client
19+
* @var \GuzzleHttp\Client
20+
*/
21+
protected static $request;
22+
23+
/**
24+
* @desc Mapping for values passed into the send method to the values needed for the respective API
25+
* @var array
26+
*/
27+
protected static $parameterMappings = array();
28+
29+
/**
30+
* @desc Sets up default structure and default values for the model that is acceptable by the API
31+
* @var array
32+
*/
33+
protected static $structure = array();
34+
35+
/**
36+
* @desc Ensure that this class cannot be instansiated
37+
*/
38+
private function __construct() {}
39+
40+
/**
41+
* @desc Creates and returns a guzzle http client.
42+
* @return \GuzzleHttp\Client
43+
*/
44+
protected static function getHttpClient() {
45+
if(!isset(self::$request)) {
46+
self::$request = new Client();
47+
}
48+
return self::$request;
49+
}
50+
51+
/**
52+
* @desc Private Method helper to get the configuration values to create the base url for the current API endpoint
53+
*
54+
* @return string base url for the transmissions API
55+
*/
56+
protected static function getBaseUrl($config) {
57+
$baseUrl = '/api/' . $config['version'] . '/' . static::$endpoint;
58+
return $config['protocol'] . '://' . $config['host'] . ($config['port'] ? ':' . $config['port'] : '') . $baseUrl;
59+
}
60+
61+
62+
/**
63+
* @desc Private Method helper to reference parameter mappings and set the right value for the right parameter
64+
*/
65+
protected static function setMappedValue (&$model, $mapKey, $value) {
66+
//get mapping
67+
if( empty(static::$parameterMappings) ) {
68+
// if parameterMappings is empty we can assume that no wrapper is defined
69+
// for the current endpoint and we will use the mapKey to define the mappings directly
70+
$mapPath = $mapKey;
71+
}elseif(array_key_exists($mapKey, static::$parameterMappings)) {
72+
// use only defined parameter mappings to construct $model
73+
$mapPath = static::$parameterMappings[$mapKey];
74+
} else {
75+
return;
76+
}
77+
78+
$path = explode('.', $mapPath);
79+
$temp = &$model;
80+
foreach( $path as $key ) {
81+
if( !isset($temp[$key]) ){
82+
$temp[$key] = null;
83+
}
84+
$temp = &$temp[$key];
85+
}
86+
$temp = $value;
87+
88+
}
89+
90+
protected static function buildRequestModel( $requestConfig, $model=array() ) {
91+
foreach($requestConfig as $key=>$value) {
92+
self::setMappedValue($model, $key, $value);
93+
}
94+
return $model;
95+
}
96+
97+
/**
98+
* @desc Method for issuing POST requests
99+
*
100+
* @return array API repsonse represented as key-value pairs
101+
*/
102+
public static function sendRequest( $requestConfig ) {
103+
$hostConfig = SparkPost::getConfig();
104+
$request = self::getHttpClient();
105+
106+
//create model from $transmissionConfig
107+
$model = static::$structure;
108+
$requestModel = self::buildRequestModel( $requestConfig, $model );
109+
110+
//send the request
111+
try {
112+
$response = $request->post(
113+
self::getBaseUrl($hostConfig),
114+
array('authorization' => $hostConfig['key']),
115+
json_encode($requestModel),
116+
array("verify"=>$hostConfig['strictSSL'])
117+
)->send();
118+
119+
return $response->json();
120+
}
121+
/*
122+
* Handles 4XX responses
123+
*/
124+
catch (ClientErrorResponseException $exception) {
125+
$response = $exception->getResponse();
126+
$responseArray = $response->json();
127+
throw new \Exception(json_encode($responseArray['errors']));
128+
}
129+
/*
130+
* Handles 5XX Errors, Configuration Errors, and a catch all for other errors
131+
*/
132+
catch (\Exception $exception) {
133+
throw new \Exception("Unable to contact ".ucfirst(static::$endpoint)." API: ". $exception->getMessage());
134+
}
135+
}
136+
137+
138+
/**
139+
* @desc Wrapper method for issuing GET request to current API endpoint
140+
*
141+
* @param string $resourcePath (optional) string resource path of specific resource
142+
* @param array $options (optional) query string parameters
143+
* @return array Result set of transmissions found
144+
*/
145+
public static function fetchResource( $resourcePath=null, $options=array() ) {
146+
return self::callResource( 'get', $resourcePath, $options );
147+
}
148+
149+
/**
150+
* @desc Wrapper method for issuing DELETE request to current API endpoint
151+
*
152+
* @param string $resourcePath (optional) string resource path of specific resource
153+
* @param array $options (optional) query string parameters
154+
* @return array Result set of transmissions found
155+
*/
156+
public static function deleteResource( $resourcePath=null, $options=array() ) {
157+
return self::callResource( 'delete', $resourcePath, $options );
158+
}
159+
160+
/**
161+
* @desc Private Method for issuing GET and DELETE request to current API endpoint
162+
*
163+
* This method is responsible for getting the collection _and_
164+
* a specific entity from the API endpoint
165+
*
166+
* If resourcePath parameter is omitted, then we fetch the collection
167+
*
168+
* @param string $action HTTP method type
169+
* @param string $resourcePath (optional) string resource path of specific resource
170+
* @param array $options (optional) query string parameters
171+
* @return array Result set of action performed on resource
172+
*/
173+
private static function callResource( $action, $resourcePath=null, $options=array() ) {
174+
175+
if( !in_array( $action, array('get', 'delete') ) ) throw new \Exception('Invalid resource action');
176+
177+
//build the url
178+
$hostConfig = SparkPost::getConfig();
179+
$url = self::getBaseUrl($hostConfig);
180+
if (!is_null($resourcePath)){
181+
$url .= '/'.$resourcePath;
182+
}
183+
184+
// untested:
185+
if( !empty($options) ) {
186+
$queryString = http_build_query($options);
187+
$url .= '?'.$queryString;
188+
}
189+
190+
$request = self::getHttpClient();
191+
192+
//make request
193+
try {
194+
$response = $request->{$action}($url, array('authorization' => $hostConfig['key']), array("verify"=>$hostConfig['strictSSL']))->send();
195+
return $response->json();
196+
}
197+
/*
198+
* Handles 4XX responses
199+
*/
200+
catch (ClientErrorResponseException $exception) {
201+
$response = $exception->getResponse();
202+
$statusCode = $response->getStatusCode();
203+
if($statusCode === 404) {
204+
throw new \Exception("The specified resource does not exist", 404);
205+
}
206+
throw new \Exception("Received bad response from ".ucfirst(static::$endpoint)." API: ". $statusCode );
207+
}
208+
/*
209+
* Handles 5XX Errors, Configuration Errors, and a catch all for other errors
210+
*/
211+
catch (\Exception $exception) {
212+
throw new \Exception("Unable to contact ".ucfirst(static::$endpoint)." API: ". $exception->getMessage());
213+
}
214+
}
215+
216+
}

test/unit/APIResourceTest.php

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
namespace SparkPost\Test;
3+
4+
use SparkPost\APIResource;
5+
use SparkPost\SparkPost;
6+
use Guzzle\Plugin\Mock\MockPlugin;
7+
use Guzzle\Http\Message\Response;
8+
9+
10+
class APIResourceTest extends \PHPUnit_Framework_TestCase {
11+
12+
private $client = null;
13+
14+
/**
15+
* Allows access to private methods
16+
*
17+
* This is needed to mock the GuzzleHttp\Client responses
18+
*
19+
* @param string $name
20+
* @return ReflectionMethod
21+
*/
22+
private static function getMethod($name) {
23+
$class = new \ReflectionClass('\SparkPost\APIResource');
24+
$method = $class->getMethod($name);
25+
$method->setAccessible(true);
26+
return $method;
27+
}
28+
29+
/**
30+
* (non-PHPdoc)
31+
* @before
32+
* @see PHPUnit_Framework_TestCase::setUp()
33+
*/
34+
public function setUp() {
35+
SparkPost::setConfig(array('key'=>'blah'));
36+
$this->client = self::getMethod('getHttpClient')->invoke(null); //so we can bootstrap api responses
37+
APIResource::$endpoint = 'someValidEndpoint'; // when using APIResource directly an endpoint needs to be set.
38+
}
39+
40+
/**
41+
* @desc Ensures that the configuration class is not instantiable.
42+
*/
43+
public function testConstructorCannotBeCalled() {
44+
$class = new \ReflectionClass('\SparkPost\Transmission');
45+
$this->assertFalse($class->isInstantiable());
46+
}
47+
48+
/**
49+
* @desc tests happy path
50+
*/
51+
public function testFetchWithGoodResponse() {
52+
$mock = new MockPlugin();
53+
$mock->addResponse(new Response(200, array(), '{"results":[{"test":"This is a test"}, {"test":"two"}]}'));
54+
$this->client->addSubscriber($mock);
55+
$this->assertEquals(array("results"=>array(array('test'=>'This is a test'), array('test'=>'two'))), APIResource::fetchResource());
56+
}
57+
58+
/**
59+
* @desc tests happy path
60+
*/
61+
public function testDeleteWithGoodResponse() {
62+
$mock = new MockPlugin();
63+
$mock->addResponse(new Response(200, array(), '{"results":[{"test":"This is a test"}]}'));
64+
$this->client->addSubscriber($mock);
65+
66+
$this->assertEquals(array("results"=>array(array('test'=>'This is a test'))), APIResource::deleteResource('someId'));
67+
}
68+
69+
/**
70+
* @desc tests 404 bad response
71+
* @expectedException Exception
72+
* @expectedExceptionMessage The specified resource does not exist
73+
*/
74+
public function testFetchWith404Response() {
75+
$mock = new MockPlugin();
76+
$mock->addResponse(new Response(404, array()));
77+
$this->client->addSubscriber($mock);
78+
APIResource::fetchResource('someId');
79+
}
80+
81+
/**
82+
* @desc tests unknown bad response
83+
* @expectedException Exception
84+
* @expectedExceptionMessage Received bad response from SomeValidEndpoint API: 400
85+
*/
86+
public function testFetchWithOtherBadResponse() {
87+
$mock = new MockPlugin();
88+
$mock->addResponse(new Response(400, array()));
89+
$this->client->addSubscriber($mock);
90+
APIResource::fetchResource('someId');
91+
}
92+
93+
/**
94+
* @desc tests bad response
95+
* @expectedException Exception
96+
* @expectedExceptionMessageRegExp /Unable to contact SomeValidEndpoint API:.* /
97+
*/
98+
public function testFetchForCatchAllException() {
99+
$mock = new MockPlugin();
100+
$mock->addResponse(new Response(500));
101+
$this->client->addSubscriber($mock);
102+
APIResource::fetchResource('someId');
103+
}
104+
105+
/**
106+
* @desc tests happy path
107+
*/
108+
public function testSuccessfulSend() {
109+
$body = array("result"=>array("transmission_id"=>"11668787484950529"), "status"=>array("message"=> "ok","code"=> "1000"));
110+
$mock = new MockPlugin();
111+
$mock->addResponse(new Response(200, array(), json_encode($body)));
112+
$this->client->addSubscriber($mock);
113+
114+
115+
$this->assertEquals($body, APIResource::sendRequest(array('text'=>'awesome email')));
116+
}
117+
118+
/**
119+
* @desc tests bad response
120+
* @expectedException Exception
121+
* @expectedExceptionMessage ["This is a fake error"]
122+
*/
123+
public function testSendFor400Exception() {
124+
$body = array('errors'=>array('This is a fake error'));
125+
$mock = new MockPlugin();
126+
$mock->addResponse(new Response(400, array(), json_encode($body)));
127+
$this->client->addSubscriber($mock);
128+
APIResource::sendRequest(array('text'=>'awesome email'));
129+
}
130+
131+
132+
/**
133+
* @desc tests bad response
134+
* @expectedException Exception
135+
* @expectedExceptionMessageRegExp /Unable to contact SomeValidEndpoint API:.* /
136+
*/
137+
public function testSendForCatchAllException() {
138+
$mock = new MockPlugin();
139+
$mock->addResponse(new Response(500));
140+
$this->client->addSubscriber($mock);
141+
APIResource::sendRequest(array('text'=>'awesome email'));
142+
}
143+
144+
}

0 commit comments

Comments
 (0)