From fc3eff4031226c61e42dfdbcc26e571259383dca Mon Sep 17 00:00:00 2001 From: Raja Kapur Date: Mon, 4 Jun 2012 16:23:32 -0400 Subject: [PATCH 1/4] added CSV handler support --- src/Httpful/Bootstrap.php | 185 ++++++++++++++-------------- src/Httpful/Handlers/CsvHandler.php | 50 ++++++++ src/Httpful/Mime.php | 112 ++++++++--------- 3 files changed, 200 insertions(+), 147 deletions(-) create mode 100644 src/Httpful/Handlers/CsvHandler.php diff --git a/src/Httpful/Bootstrap.php b/src/Httpful/Bootstrap.php index 0d40bfd..ee2c460 100644 --- a/src/Httpful/Bootstrap.php +++ b/src/Httpful/Bootstrap.php @@ -1,93 +1,94 @@ - - */ -class Bootstrap -{ - - const DIR_GLUE = '/'; - const NS_GLUE = '\\'; - - public static $registered = false; - - /** - * Register the autoloader and any other setup needed - */ - public static function init() - { - spl_autoload_register(array('\Httpful\Bootstrap', 'autoload')); - self::registerHandlers(); - } - - /** - * The autoload magic (PSR-0 style) - * - * @param string $classname - */ - public static function autoload($classname) - { - self::_autoload(dirname(dirname(__FILE__)), $classname); - } - - /** - * Register the autoloader and any other setup needed - */ - public static function pharInit() - { - spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload')); - self::registerHandlers(); - } - - /** - * Phar specific autoloader - * - * @param string $classname - */ - public static function pharAutoload($classname) - { - self::_autoload('phar://httpful.phar', $classname); - } - - /** - * @param string base - * @param string classname - */ - private static function _autoload($base, $classname) - { - $parts = explode(self::NS_GLUE, $classname); - $path = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php'; - - if (file_exists($path)) { - require_once($path); - } - } - /** - * Register default mime handlers. Is idempotent. - */ - public static function registerHandlers() - { - if (self::$registered === true) { - return; - } - - // @todo check a conf file to load from that instead of - // hardcoding into the library? - $handlers = array( - \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), - \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), - \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), - ); - - foreach ($handlers as $mime => $handler) { - Httpful::register($mime, $handler); - } - - self::$registered = true; - } + + */ +class Bootstrap +{ + + const DIR_GLUE = '/'; + const NS_GLUE = '\\'; + + public static $registered = false; + + /** + * Register the autoloader and any other setup needed + */ + public static function init() + { + spl_autoload_register(array('\Httpful\Bootstrap', 'autoload')); + self::registerHandlers(); + } + + /** + * The autoload magic (PSR-0 style) + * + * @param string $classname + */ + public static function autoload($classname) + { + self::_autoload(dirname(dirname(__FILE__)), $classname); + } + + /** + * Register the autoloader and any other setup needed + */ + public static function pharInit() + { + spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload')); + self::registerHandlers(); + } + + /** + * Phar specific autoloader + * + * @param string $classname + */ + public static function pharAutoload($classname) + { + self::_autoload('phar://httpful.phar', $classname); + } + + /** + * @param string base + * @param string classname + */ + private static function _autoload($base, $classname) + { + $parts = explode(self::NS_GLUE, $classname); + $path = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php'; + + if (file_exists($path)) { + require_once($path); + } + } + /** + * Register default mime handlers. Is idempotent. + */ + public static function registerHandlers() + { + if (self::$registered === true) { + return; + } + + // @todo check a conf file to load from that instead of + // hardcoding into the library? + $handlers = array( + \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), + \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), + \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), + \Httpful\Mime::CSV => new \Httpful\Handlers\CsvHandler(), + ); + + foreach ($handlers as $mime => $handler) { + Httpful::register($mime, $handler); + } + + self::$registered = true; + } } \ No newline at end of file diff --git a/src/Httpful/Handlers/CsvHandler.php b/src/Httpful/Handlers/CsvHandler.php new file mode 100644 index 0000000..7d4e6f4 --- /dev/null +++ b/src/Httpful/Handlers/CsvHandler.php @@ -0,0 +1,50 @@ + + */ + +namespace Httpful\Handlers; + +class CsvHandler extends MimeHandlerAdapter +{ + /** + * @param string $body + * @return mixed + */ + public function parse($body) + { + if (empty($body)) + return null; + + $parsed = array(); + $fp = fopen('data://text/plain;base64,'.base64_encode($body), 'r'); + while (($r = fgetcsv($fp)) !== FALSE) { + $parsed[] = $r; + } + + if (is_null($parsed)) + throw new \Exception("Unable to parse response as CSV"); + return $parsed; + } + + /** + * @param mixed $payload + * @return string + */ + public function serialize($payload) + { + $fp = fopen('php://temp/maxmemory:'. (12*1024*1024), 'r+'); + $i = 0; + foreach ($payload as $fields) { + if($i++ == 0) { + fputcsv($fp, array_keys($fields)); + } + fputcsv($fp, $fields); + } + rewind($fp); + $data = stream_get_contents($fp); + fclose($fp); + return $data; + } +} \ No newline at end of file diff --git a/src/Httpful/Mime.php b/src/Httpful/Mime.php index b8560b3..63b04af 100644 --- a/src/Httpful/Mime.php +++ b/src/Httpful/Mime.php @@ -1,56 +1,58 @@ - - */ -class Mime -{ - const JSON = 'application/json'; - const XML = 'application/xml'; - const XHTML = 'application/html+xml'; - const FORM = 'application/x-www-form-urlencoded'; - const PLAIN = 'text/plain'; - const JS = 'text/javascript'; - const HTML = 'text/html'; - const YAML = 'application/x-yaml'; - - /** - * Map short name for a mime type - * to a full proper mime type - */ - public static $mimes = array( - 'json' => self::JSON, - 'xml' => self::XML, - 'form' => self::FORM, - 'plain' => self::PLAIN, - 'text' => self::PLAIN, - 'html' => self::HTML, - 'xhtml' => self::XHTML, - 'js' => self::JS, - 'javascript'=> self::JS, - 'yaml' => self::YAML, - ); - - /** - * Get the full Mime Type name from a "short name". - * Returns the short if no mapping was found. - * @return string full mime type (e.g. application/json) - * @param string common name for mime type (e.g. json) - */ - public static function getFullMime($short_name) - { - return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name; - } - - /** - * @return bool - * @param string $short_name - */ - public static function supportsMimeType($short_name) - { - return array_key_exists($short_name, self::$mimes); - } + + */ +class Mime +{ + const JSON = 'application/json'; + const XML = 'application/xml'; + const XHTML = 'application/html+xml'; + const FORM = 'application/x-www-form-urlencoded'; + const PLAIN = 'text/plain'; + const JS = 'text/javascript'; + const HTML = 'text/html'; + const YAML = 'application/x-yaml'; + const CSV = 'text/csv'; + + /** + * Map short name for a mime type + * to a full proper mime type + */ + public static $mimes = array( + 'json' => self::JSON, + 'xml' => self::XML, + 'form' => self::FORM, + 'plain' => self::PLAIN, + 'text' => self::PLAIN, + 'html' => self::HTML, + 'xhtml' => self::XHTML, + 'js' => self::JS, + 'javascript'=> self::JS, + 'yaml' => self::YAML, + 'csv' => self::CSV, + ); + + /** + * Get the full Mime Type name from a "short name". + * Returns the short if no mapping was found. + * @return string full mime type (e.g. application/json) + * @param string common name for mime type (e.g. json) + */ + public static function getFullMime($short_name) + { + return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name; + } + + /** + * @return bool + * @param string $short_name + */ + public static function supportsMimeType($short_name) + { + return array_key_exists($short_name, self::$mimes); + } } \ No newline at end of file From e5b7064ee8f19dd9e8674a11570931bc9d5f5286 Mon Sep 17 00:00:00 2001 From: Raja Kapur Date: Tue, 5 Jun 2012 11:33:02 -0400 Subject: [PATCH 2/4] Revert "added CSV handler support" This reverts commit fc3eff4031226c61e42dfdbcc26e571259383dca. --- src/Httpful/Bootstrap.php | 185 ++++++++++++++-------------- src/Httpful/Handlers/CsvHandler.php | 50 -------- src/Httpful/Mime.php | 112 +++++++++-------- 3 files changed, 147 insertions(+), 200 deletions(-) delete mode 100644 src/Httpful/Handlers/CsvHandler.php diff --git a/src/Httpful/Bootstrap.php b/src/Httpful/Bootstrap.php index ee2c460..0d40bfd 100644 --- a/src/Httpful/Bootstrap.php +++ b/src/Httpful/Bootstrap.php @@ -1,94 +1,93 @@ - - */ -class Bootstrap -{ - - const DIR_GLUE = '/'; - const NS_GLUE = '\\'; - - public static $registered = false; - - /** - * Register the autoloader and any other setup needed - */ - public static function init() - { - spl_autoload_register(array('\Httpful\Bootstrap', 'autoload')); - self::registerHandlers(); - } - - /** - * The autoload magic (PSR-0 style) - * - * @param string $classname - */ - public static function autoload($classname) - { - self::_autoload(dirname(dirname(__FILE__)), $classname); - } - - /** - * Register the autoloader and any other setup needed - */ - public static function pharInit() - { - spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload')); - self::registerHandlers(); - } - - /** - * Phar specific autoloader - * - * @param string $classname - */ - public static function pharAutoload($classname) - { - self::_autoload('phar://httpful.phar', $classname); - } - - /** - * @param string base - * @param string classname - */ - private static function _autoload($base, $classname) - { - $parts = explode(self::NS_GLUE, $classname); - $path = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php'; - - if (file_exists($path)) { - require_once($path); - } - } - /** - * Register default mime handlers. Is idempotent. - */ - public static function registerHandlers() - { - if (self::$registered === true) { - return; - } - - // @todo check a conf file to load from that instead of - // hardcoding into the library? - $handlers = array( - \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), - \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), - \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), - \Httpful\Mime::CSV => new \Httpful\Handlers\CsvHandler(), - ); - - foreach ($handlers as $mime => $handler) { - Httpful::register($mime, $handler); - } - - self::$registered = true; - } + + */ +class Bootstrap +{ + + const DIR_GLUE = '/'; + const NS_GLUE = '\\'; + + public static $registered = false; + + /** + * Register the autoloader and any other setup needed + */ + public static function init() + { + spl_autoload_register(array('\Httpful\Bootstrap', 'autoload')); + self::registerHandlers(); + } + + /** + * The autoload magic (PSR-0 style) + * + * @param string $classname + */ + public static function autoload($classname) + { + self::_autoload(dirname(dirname(__FILE__)), $classname); + } + + /** + * Register the autoloader and any other setup needed + */ + public static function pharInit() + { + spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload')); + self::registerHandlers(); + } + + /** + * Phar specific autoloader + * + * @param string $classname + */ + public static function pharAutoload($classname) + { + self::_autoload('phar://httpful.phar', $classname); + } + + /** + * @param string base + * @param string classname + */ + private static function _autoload($base, $classname) + { + $parts = explode(self::NS_GLUE, $classname); + $path = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php'; + + if (file_exists($path)) { + require_once($path); + } + } + /** + * Register default mime handlers. Is idempotent. + */ + public static function registerHandlers() + { + if (self::$registered === true) { + return; + } + + // @todo check a conf file to load from that instead of + // hardcoding into the library? + $handlers = array( + \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), + \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), + \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), + ); + + foreach ($handlers as $mime => $handler) { + Httpful::register($mime, $handler); + } + + self::$registered = true; + } } \ No newline at end of file diff --git a/src/Httpful/Handlers/CsvHandler.php b/src/Httpful/Handlers/CsvHandler.php deleted file mode 100644 index 7d4e6f4..0000000 --- a/src/Httpful/Handlers/CsvHandler.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ - -namespace Httpful\Handlers; - -class CsvHandler extends MimeHandlerAdapter -{ - /** - * @param string $body - * @return mixed - */ - public function parse($body) - { - if (empty($body)) - return null; - - $parsed = array(); - $fp = fopen('data://text/plain;base64,'.base64_encode($body), 'r'); - while (($r = fgetcsv($fp)) !== FALSE) { - $parsed[] = $r; - } - - if (is_null($parsed)) - throw new \Exception("Unable to parse response as CSV"); - return $parsed; - } - - /** - * @param mixed $payload - * @return string - */ - public function serialize($payload) - { - $fp = fopen('php://temp/maxmemory:'. (12*1024*1024), 'r+'); - $i = 0; - foreach ($payload as $fields) { - if($i++ == 0) { - fputcsv($fp, array_keys($fields)); - } - fputcsv($fp, $fields); - } - rewind($fp); - $data = stream_get_contents($fp); - fclose($fp); - return $data; - } -} \ No newline at end of file diff --git a/src/Httpful/Mime.php b/src/Httpful/Mime.php index 63b04af..b8560b3 100644 --- a/src/Httpful/Mime.php +++ b/src/Httpful/Mime.php @@ -1,58 +1,56 @@ - - */ -class Mime -{ - const JSON = 'application/json'; - const XML = 'application/xml'; - const XHTML = 'application/html+xml'; - const FORM = 'application/x-www-form-urlencoded'; - const PLAIN = 'text/plain'; - const JS = 'text/javascript'; - const HTML = 'text/html'; - const YAML = 'application/x-yaml'; - const CSV = 'text/csv'; - - /** - * Map short name for a mime type - * to a full proper mime type - */ - public static $mimes = array( - 'json' => self::JSON, - 'xml' => self::XML, - 'form' => self::FORM, - 'plain' => self::PLAIN, - 'text' => self::PLAIN, - 'html' => self::HTML, - 'xhtml' => self::XHTML, - 'js' => self::JS, - 'javascript'=> self::JS, - 'yaml' => self::YAML, - 'csv' => self::CSV, - ); - - /** - * Get the full Mime Type name from a "short name". - * Returns the short if no mapping was found. - * @return string full mime type (e.g. application/json) - * @param string common name for mime type (e.g. json) - */ - public static function getFullMime($short_name) - { - return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name; - } - - /** - * @return bool - * @param string $short_name - */ - public static function supportsMimeType($short_name) - { - return array_key_exists($short_name, self::$mimes); - } + + */ +class Mime +{ + const JSON = 'application/json'; + const XML = 'application/xml'; + const XHTML = 'application/html+xml'; + const FORM = 'application/x-www-form-urlencoded'; + const PLAIN = 'text/plain'; + const JS = 'text/javascript'; + const HTML = 'text/html'; + const YAML = 'application/x-yaml'; + + /** + * Map short name for a mime type + * to a full proper mime type + */ + public static $mimes = array( + 'json' => self::JSON, + 'xml' => self::XML, + 'form' => self::FORM, + 'plain' => self::PLAIN, + 'text' => self::PLAIN, + 'html' => self::HTML, + 'xhtml' => self::XHTML, + 'js' => self::JS, + 'javascript'=> self::JS, + 'yaml' => self::YAML, + ); + + /** + * Get the full Mime Type name from a "short name". + * Returns the short if no mapping was found. + * @return string full mime type (e.g. application/json) + * @param string common name for mime type (e.g. json) + */ + public static function getFullMime($short_name) + { + return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name; + } + + /** + * @return bool + * @param string $short_name + */ + public static function supportsMimeType($short_name) + { + return array_key_exists($short_name, self::$mimes); + } } \ No newline at end of file From 77e8fe014adc285b702fe7bd3f688ac871c7d0ae Mon Sep 17 00:00:00 2001 From: Raja Kapur Date: Tue, 5 Jun 2012 11:44:44 -0400 Subject: [PATCH 3/4] Added CSV support using php's fgetcsv and fputcsv functions for streams --- src/Httpful/Bootstrap.php | 3 +- src/Httpful/Handlers/CsvHandler.php | 50 +++++++++++++++++++++++++++++ src/Httpful/Mime.php | 4 ++- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/Httpful/Handlers/CsvHandler.php diff --git a/src/Httpful/Bootstrap.php b/src/Httpful/Bootstrap.php index 0d40bfd..0056dd9 100644 --- a/src/Httpful/Bootstrap.php +++ b/src/Httpful/Bootstrap.php @@ -82,6 +82,7 @@ public static function registerHandlers() \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), + \Httpful\Mime::CSV => new \Httpful\Handlers\CsvHandler(), ); foreach ($handlers as $mime => $handler) { @@ -90,4 +91,4 @@ public static function registerHandlers() self::$registered = true; } -} \ No newline at end of file +} diff --git a/src/Httpful/Handlers/CsvHandler.php b/src/Httpful/Handlers/CsvHandler.php new file mode 100644 index 0000000..3359351 --- /dev/null +++ b/src/Httpful/Handlers/CsvHandler.php @@ -0,0 +1,50 @@ + + */ + +namespace Httpful\Handlers; + +class CsvHandler extends MimeHandlerAdapter +{ + /** + * @param string $body + * @return mixed + */ + public function parse($body) + { + if (empty($body)) + return null; + + $parsed = array(); + $fp = fopen('data://text/plain;base64,' . base64_encode($body), 'r'); + while (($r = fgetcsv($fp)) !== FALSE) { + $parsed[] = $r; + } + + if (empty($parsed)) + throw new \Exception("Unable to parse response as CSV"); + return $parsed; + } + + /** + * @param mixed $payload + * @return string + */ + public function serialize($payload) + { + $fp = fopen('php://temp/maxmemory:'. (6*1024*1024), 'r+'); + $i = 0; + foreach ($payload as $fields) { + if($i++ == 0) { + fputcsv($fp, array_keys($fields)); + } + fputcsv($fp, $fields); + } + rewind($fp); + $data = stream_get_contents($fp); + fclose($fp); + return $data; + } +} diff --git a/src/Httpful/Mime.php b/src/Httpful/Mime.php index b8560b3..1a7c456 100644 --- a/src/Httpful/Mime.php +++ b/src/Httpful/Mime.php @@ -16,6 +16,7 @@ class Mime const JS = 'text/javascript'; const HTML = 'text/html'; const YAML = 'application/x-yaml'; + const CSV = 'text/csv'; /** * Map short name for a mime type @@ -32,6 +33,7 @@ class Mime 'js' => self::JS, 'javascript'=> self::JS, 'yaml' => self::YAML, + 'csv' => self::CSV, ); /** @@ -53,4 +55,4 @@ public static function supportsMimeType($short_name) { return array_key_exists($short_name, self::$mimes); } -} \ No newline at end of file +} From fb24511ad6a384bef92ba9d6127903ffd3e92573 Mon Sep 17 00:00:00 2001 From: Raja Kapur Date: Wed, 6 Jun 2012 13:04:36 -0400 Subject: [PATCH 4/4] added unit tests for CSV response parsing --- tests/Httpful/HttpfulTest.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index 5e269fd..03d7ecb 100644 --- a/tests/Httpful/HttpfulTest.php +++ b/tests/Httpful/HttpfulTest.php @@ -30,6 +30,15 @@ class HttpfulTest extends \PHPUnit_Framework_TestCase Connection: keep-alive Transfer-Encoding: chunked"; const SAMPLE_JSON_RESPONSE = '{"key":"value","object":{"key":"value"},"array":[1,2,3,4]}'; + const SAMPLE_CSV_HEADER = +"HTTP/1.1 200 OK +Content-Type: text/csv +Connection: keep-alive +Transfer-Encoding: chunked"; + const SAMPLE_CSV_RESPONSE = +"Key1,Key2 +Value1,Value2 +\"40.0\",\"Forty\""; const SAMPLE_XML_RESPONSE = '2a stringTRUE'; const SAMPLE_XML_HEADER = "HTTP/1.1 200 OK @@ -75,16 +84,19 @@ function testShortMime() $this->assertEquals(Mime::JSON, Mime::getFullMime('json')); $this->assertEquals(Mime::XML, Mime::getFullMime('xml')); $this->assertEquals(Mime::HTML, Mime::getFullMime('html')); + $this->assertEquals(Mime::CSV, Mime::getFullMime('csv')); // Valid long ones $this->assertEquals(Mime::JSON, Mime::getFullMime(Mime::JSON)); $this->assertEquals(Mime::XML, Mime::getFullMime(Mime::XML)); $this->assertEquals(Mime::HTML, Mime::getFullMime(Mime::HTML)); + $this->assertEquals(Mime::CSV, Mime::getFullMime(Mime::CSV)); // No false positives $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::HTML)); $this->assertNotEquals(Mime::JSON, Mime::getFullMime(Mime::XML)); $this->assertNotEquals(Mime::HTML, Mime::getFullMime(Mime::JSON)); + $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::CSV)); } function testSettingStrictSsl() @@ -121,6 +133,11 @@ function testSendsAndExpectsType() ->sendsAndExpectsType('application/x-www-form-urlencoded'); $this->assertEquals(Mime::FORM, $r->expected_type); $this->assertEquals(Mime::FORM, $r->content_type); + + $r = Request::init() + ->sendsAndExpectsType(Mime::CSV); + $this->assertEquals(Mime::CSV, $r->expected_type); + $this->assertEquals(Mime::CSV, $r->content_type); } function testIni() @@ -194,6 +211,17 @@ function testXMLResponseParse() $this->assertEquals("a string", (string) $string); } + function testCsvResponseParse() + { + $req = Request::init()->sendsAndExpects(Mime::CSV); + $response = new Response(self::SAMPLE_CSV_RESPONSE, self::SAMPLE_CSV_HEADER, $req); + + $this->assertEquals("Key1", $response->body[0][0]); + $this->assertEquals("Value1", $response->body[1][0]); + $this->assertInternalType('string', $response->body[2][0]); + $this->assertEquals("40.0", $response->body[2][0]); + } + function testParsingContentTypeCharset() { $req = Request::init()->sendsAndExpects(Mime::JSON);