Переглянути джерело

Adding dependency injection for client request/response. Adding ability to set request method and post data to a URL. You can now set headers on requests.

Jon Wenmoth 12 роки тому
батько
коміт
ef4ea5ea1f

+ 137 - 90
src/JonnyW/PhantomJs/Client.php

@@ -2,18 +2,20 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
 namespace JonnyW\PhantomJs;
 
-use  JonnyW\PhantomJs\ClientInterface;
-use  JonnyW\PhantomJs\Exception\NoPhantomJsException;
-use  JonnyW\PhantomJs\Exception\CommandFailedException;
-use  JonnyW\PhantomJs\Exception\NotWriteableException;
-use  JonnyW\PhantomJs\Exception\InvalidUrlException;
-use  JonnyW\PhantomJs\Response;
+use JonnyW\PhantomJs\ClientInterface;
+use JonnyW\PhantomJs\Exception\NoPhantomJsException;
+use JonnyW\PhantomJs\Exception\CommandFailedException;
+use JonnyW\PhantomJs\Exception\NotWriteableException;
+use JonnyW\PhantomJs\Message\FactoryInterface;
+use JonnyW\PhantomJs\Message\Factory;
+use JonnyW\PhantomJs\Message\RequestInterface;
+use JonnyW\PhantomJs\Message\ResponseInterface;
 
 /**
  * PHP PhantomJs
@@ -22,13 +24,6 @@ use  JonnyW\PhantomJs\Response;
  */
 class Client implements ClientInterface
 {
-	/**
-	 * Path to phantomJS executable
-	 *
-	 * @var string
-	 */
-	protected $phantomJS;
-	
 	/**
 	 * Client instance
 	 *
@@ -36,60 +31,110 @@ class Client implements ClientInterface
 	 */
 	private static $instance;
 
+	/**
+	 * Message factory instance
+	 *
+	 * @var JonnyW\PhantomJs\Message\FactoryInterface
+	 */
+	protected $factory;
+
+	/**
+	 * Path to phantomJS executable
+	 *
+	 * @var string
+	 */
+	protected $phantomJS;
+
 	/**
 	 * Internal constructor
 	 *
+	 * @param JonnyW\PhantomJs\Message\FactoryInterface $factory
 	 * @return void
 	 */
-	public function __construct()
+	public function __construct(FactoryInterface $factory = null)
 	{
-		$this->phantomJS 	= 'bin/phantomjs';
-		$this->timeout 		= 5000;
+		if(!$factory instanceof FactoryInterface) {
+			$factory = Factory::getInstance();
+		}
+
+		$this->factory  = $factory;
+		$this->phantomJS  = 'bin/phantomjs';
+		$this->timeout   = 5000;
 	}
-	
+
 	/**
 	 * Get singleton instance
 	 *
+	 * @param JonnyW\PhantomJs\Message\FactoryInterface $factory
 	 * @return JonnyW\PhantomJs\ClientInterface
 	 */
-	public static function getInstance()
+	public static function getInstance(FactoryInterface $factory = null)
 	{
 		if(!self::$instance instanceof ClientInterface) {
-			self::$instance = new Client();
+			self::$instance = new Client($factory);
 		}
 
 		return self::$instance;
 	}
-	
+
 	/**
-	 * Open page and return HTML
+	 * Get message factory instance
 	 *
-	 * @param string $url
-	 * @return string
+	 * @return JonnyW\PhantomJs\Message\FactoryInterface
 	 */
-	public function open($url)
+	public function getMessageFactory()
 	{
-		return $this->request($url, $this->openCmd);
+		return $this->factory;
 	}
-	
+
 	/**
-	 * Screen capture URL
+	 * Send request
 	 *
-	 * @param string $url
-	 * @pram string $file
-	 * @return string
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
+	 * @param string $file
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
+	 */
+	public function send(RequestInterface $request, ResponseInterface $response, $file = null)
+	{
+		if(!is_null($file)) {
+			return $this->capture($request, $response, $file);
+		}
+
+		return $this->open($request, $response);
+	}
+
+	/**
+	 * Open page
+	 *
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
+	 */
+	public function open(RequestInterface $request, ResponseInterface $response)
+	{
+		return $this->request($request, $response, $this->openCmd);
+	}
+
+	/**
+	 * Screen capture
+	 *
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
+	 * @param string $file
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
 	 */
-	public function capture($url, $file)
+	public function capture(RequestInterface $request, ResponseInterface $response, $file)
 	{
 		if(!is_writable(dirname($file))) {
 			throw new NotWriteableException(sprintf('Path is not writeable by PhantomJs: %s', $file));
 		}
-	
+
 		$cmd = sprintf($this->captureCmd, $file);
-	
-		return $this->request($url, $cmd);
+
+		return $this->request($request, $response, $cmd);
 	}
-	
+
 	/**
 	 * Set new PhantomJs path
 	 *
@@ -101,12 +146,12 @@ class Client implements ClientInterface
 		if(!file_exists($path) || !is_executable($path)) {
 			throw new NoPhantomJsException(sprintf('PhantomJs file does not exist or is not executable: %s', $path));
 		}
-	
+
 		$this->phantomJS = $path;
-		
+
 		return $this;
 	}
-	
+
 	/**
 	 * Set timeout period (in milliseconds)
 	 *
@@ -116,74 +161,73 @@ class Client implements ClientInterface
 	public function setTimeout($period)
 	{
 		$this->timeout = $period;
-		
+
 		return $this;
 	}
 
 	/**
-	 * Call PhantomJs command
+	 * Make PhantomJS request
 	 *
-	 * @param string $url
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
 	 * @param string $cmd
-	 * @return JonnyW\PhantomJs\Response
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
 	 */
-	protected function request($url, $cmd)
+	protected function request(RequestInterface $request, ResponseInterface $response, $cmd)
 	{
-		// Validate URL
-		if(!filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)) {
-			throw new InvalidUrlException(sprintf('Invalid URL provided: %s', $url));
-		}
-	
 		// Validate PhantomJS executable
 		if(!file_exists($this->phantomJS) || !is_executable($this->phantomJS)) {
 			throw new NoPhantomJsException(sprintf('PhantomJs file does not exist or is not executable: %s', $this->phantomJS));
 		}
-	
+
 		try {
-			
+
 			$script = false;
-			
+
 			$data = sprintf(
 				$this->wrapper,
+				$request->getHeaders('json'),
 				$this->timeout,
-				$url, 
+				$request->getUrl(),
+				$request->getMethod(),
+				$request->getBody(),
 				$cmd
 			);
-	
-			$script 	= $this->writeScript($data);
-			$cmd 		= escapeshellcmd(sprintf("%s %s", $this->phantomJS, $script));
-			
-			$data = shell_exec($cmd);
-			$data = $this->parse($data);
-	
+
+			$script = $this->writeScript($data);
+			$cmd  = escapeshellcmd(sprintf("%s %s", $this->phantomJS, $script));
+
+			$result = shell_exec($cmd);
+			$result = $this->parse($result);
+
 			$this->removeScript($script);
-			
-			$response = new Response($data);
+
+			$response->setData($result);
 		}
 		catch(NotWriteableException $e) {
 			throw $e;
 		}
 		catch(\Exception $e) {
-		
+
 			$this->removeScript($script);
-		
+
 			throw new CommandFailedException(sprintf('Error when executing PhantomJs command: %s - %s', $cmd, $e->getMessage()));
 		}
 
 		return $response;
 	}
-	
+
 	/**
 	 * Write temporary script file and
 	 * return path to file
-	 * 
+	 *
 	 * @param string $data
 	 * @return JonnyW\PhantomJs\ClientInterface
 	 */
 	protected function writeScript($data)
 	{
 		$file = tempnam('/tmp', 'phantomjs');
-		
+
 		// Could not create tmp file
 		if(!$file || !is_writable($file)) {
 			throw new NotWriteableException('Could not create tmp file on system. Please check your tmp directory and make sure it is writeable.');
@@ -191,15 +235,15 @@ class Client implements ClientInterface
 
 		// Could not write script data to tmp file
 		if(file_put_contents($file, $data) === false) {
-		
+
 			$this->removeScript($file);
-		
+
 			throw new NotWriteableException(sprintf('Could not write data to tmp file: %s. Please check your tmp directory and make sure it is writeable.', $file));
 		}
-		
+
 		return $file;
 	}
-	
+
 	/**
 	 * Remove temporary script file
 	 *
@@ -211,14 +255,14 @@ class Client implements ClientInterface
 		if($file && file_exists($file)) {
 			unlink($file);
 		}
-		
+
 		return $this;
 	}
-	
+
 	/**
 	 * If data from JSON string format
 	 * and return array
-	 * 
+	 *
 	 * @param string $data
 	 * @return array
 	 */
@@ -228,57 +272,60 @@ class Client implements ClientInterface
 		if($data === null || !is_string($data)) {
 			return array();
 		}
-		
+
 		// Not a JSON string
 		if(substr($data, 0, 1) !== '{') {
 			return array();
 		}
-		
+
 		// Return decoded JSON string
 		return (array) json_decode($data, true);
 	}
-	
+
 	/**
 	 * PhantomJs base wrapper
 	 *
 	 * @var string
 	 */
 	protected $wrapper = <<<EOF
-	
+
 	var page = require('webpage').create(),
-		response = {};
+		response = {},
+		headers = %1\$s;
 
-	page.settings.resourceTimeout = %1\$s;		
+	page.settings.resourceTimeout = %2\$s;
 	page.onResourceTimeout = function(e) {
 		response 		= e;
 		response.status = e.errorCode;
 	};
-	
+
 	page.onResourceReceived = function (r) {
 		if(!response.status) response = r;
 	};
-	
-	page.open('%2\$s', function (status) {
-	
+
+	page.customHeaders = headers ? headers : {};
+
+	page.open('%3\$s', '%4\$s', '%5\$s', function(status) {
+
 		if(status === 'success') {
-			%3\$s
+			%6\$s
 		}
 
 		console.log(JSON.stringify(response, undefined, 4));
 		phantom.exit();
 	});
 EOF;
-	
+
 	/**
-	 * PhantomJs screen capture 
+	 * PhantomJs screen capture
 	 * command template
 	 *
 	 * @var string
 	 */
 	protected $captureCmd = <<<EOF
-	
+
 			page.render('%1\$s');
-	
+
 			response.content = page.evaluate(function () {
 				return document.getElementsByTagName('html')[0].innerHTML
 			});
@@ -291,7 +338,7 @@ EOF;
 	 * @var string
 	 */
 	protected $openCmd = <<<EOF
-	
+
 			response.content = page.evaluate(function () {
 				return document.getElementsByTagName('html')[0].innerHTML
 			});

+ 27 - 13
src/JonnyW/PhantomJs/ClientInterface.php

@@ -2,12 +2,15 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
 namespace JonnyW\PhantomJs;
 
+use JonnyW\PhantomJs\Message\RequestInterface;
+use JonnyW\PhantomJs\Message\ResponseInterface;
+
 /**
  * PHP PhantomJs
  *
@@ -15,24 +18,35 @@ namespace JonnyW\PhantomJs;
  */
 interface ClientInterface
 {
-	
 	/**
-	 * Open page and return HTML
+	 * Send request
+	 *
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
+	 * @param string $file
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
+	 */
+	public function send(RequestInterface $request, ResponseInterface $response, $file = null);
+
+	/**
+	 * Open page
 	 *
-	 * @param string $url
-	 * @return string
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
 	 */
-	public function open($url);
-	
+	public function open(RequestInterface $request, ResponseInterface $response);
+
 	/**
-	 * Screen capture URL
+	 * Screen capture
 	 *
-	 * @param string $url
-	 * @pram string $file
-	 * @return string
+	 * @param JonnyW\PhantomJs\Message\RequestInterface $request
+	 * @param JonnyW\PhantomJs\Message\ResponseInterface $response
+	 * @param string $file
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
 	 */
-	public function capture($url, $file);
-	
+	public function capture(RequestInterface $request, ResponseInterface $response, $file);
+
 	/**
 	 * Set new PhantomJs path
 	 *

+ 1 - 1
src/JonnyW/PhantomJs/Exception/CommandFailedException.php

@@ -2,7 +2,7 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */

+ 19 - 0
src/JonnyW/PhantomJs/Exception/InvalidMethodException.php

@@ -0,0 +1,19 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Exception;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class InvalidMethodException extends \Exception
+{
+
+}

+ 1 - 1
src/JonnyW/PhantomJs/Exception/InvalidUrlException.php

@@ -2,7 +2,7 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */

+ 1 - 1
src/JonnyW/PhantomJs/Exception/NoPhantomJsException.php

@@ -2,7 +2,7 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */

+ 1 - 1
src/JonnyW/PhantomJs/Exception/NotWriteableException.php

@@ -2,7 +2,7 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */

+ 65 - 0
src/JonnyW/PhantomJs/Message/Factory.php

@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Message;
+
+use  JonnyW\PhantomJs\Message\FactoryInterface;
+use  JonnyW\PhantomJs\Message\RequestInterface;
+use  JonnyW\PhantomJs\Message\Request;
+use  JonnyW\PhantomJs\Message\Response;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class Factory implements FactoryInterface
+{
+	/**
+	 * Client instance
+	 *
+	 * @var JonnyW\PhantomJs\Message\FactoryInterface
+	 */
+	private static $instance;
+
+	/**
+	 * Get singleton instance
+	 *
+	 * @return JonnyW\PhantomJs\Message\FactoryInterface
+	 */
+	public static function getInstance()
+	{
+		if(!self::$instance instanceof FactoryInterface) {
+			self::$instance = new Factory();
+		}
+
+		return self::$instance;
+	}
+
+	/**
+	 * Create request instance
+	 *
+	 * @param string $method
+	 * @param string $url
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function createRequest($method = RequestInterface::METHOD_GET, $url = null)
+	{
+		return new Request($method, $url);
+	}
+
+	/**
+	 * Create response instance
+	 *
+	 * @return JonnyW\PhantomJs\Message\Response
+	 */
+	public function createResponse()
+	{
+		return new Response();
+	}
+}

+ 42 - 0
src/JonnyW/PhantomJs/Message/FactoryInterface.php

@@ -0,0 +1,42 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Message;
+
+use  JonnyW\PhantomJs\Message\RequestInterface;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+interface FactoryInterface
+{
+	/**
+	 * Get singleton instance
+	 *
+	 * @return JonnyW\PhantomJs\Message\FactoryInterface
+	 */
+	public static function getInstance();
+
+	/**
+	 * Create request instance
+	 *
+	 * @param string $url
+	 * @param string $method
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function createRequest($url, $method = RequestInterface::METHOD_GET);
+
+	/**
+	 * Create response instance
+	 *
+	 * @return JonnyW\PhantomJs\Message\Response
+	 */
+	public function createResponse();
+}

+ 268 - 0
src/JonnyW/PhantomJs/Message/Request.php

@@ -0,0 +1,268 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace JonnyW\PhantomJs\Message;
+
+use JonnyW\PhantomJs\Message\RequestInterface;
+use JonnyW\PhantomJs\Exception\InvalidUrlException;
+use JonnyW\PhantomJs\Exception\InvalidMethodException;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class Request implements RequestInterface
+{
+	/**
+	 * Headers
+	 *
+	 * @var array
+	 */
+	protected $headers;
+
+	/**
+	 * Request data
+	 *
+	 * @var array
+	 */
+	protected $data;
+
+	/**
+	 * Request method
+	 *
+	 * @var string
+	 */
+	protected $method;
+
+	/**
+	 * Request URL
+	 *
+	 * @var string
+	 */
+	protected $url;
+
+	/**
+	 * Internal constructor
+	 *
+	 * @param string $method
+	 * @param string $url
+	 * @return void
+	 */
+	public function __construct($method = RequestInterface::METHOD_GET, $url = null)
+	{
+		$this->headers  = array();
+		$this->data  = array();
+
+		$this->setMethod($method);
+
+		if($url) {
+			$this->setUrl($url);
+		}
+	}
+
+	/**
+	 * Set request method
+	 *
+	 * @param string $method
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setMethod($method)
+	{
+		$method   = strtoupper($method);
+		$reflection  = new \ReflectionClass('JonnyW\PhantomJs\Message\RequestInterface');
+
+		// Validate method
+		if(!$reflection->hasConstant('METHOD_' . $method)) {
+			throw new InvalidMethodException(sprintf('Invalid method provided: %s', $method));
+		}
+
+		$this->method = $method;
+
+		return $this;
+	}
+
+	/**
+	 * Get request method
+	 *
+	 * @return string
+	 */
+	public function getMethod()
+	{
+		return $this->method;
+	}
+
+	/**
+	 * Set request URL
+	 *
+	 * @param string $url
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setUrl($url)
+	{
+		// Validate URL
+		if(!filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED)) {
+			throw new InvalidUrlException(sprintf('Invalid URL provided: %s', $url));
+		}
+
+		$this->url = $url;
+
+		return $this;
+	}
+
+	/**
+	 * Get request URL
+	 *  - Assembles query string for GET
+	 *  and HEAD requests
+	 *
+	 * @return string
+	 */
+	public function getUrl()
+	{
+		if(!in_array($this->getMethod(), array(RequestInterface::METHOD_GET, RequestInterface::METHOD_HEAD))) {
+			return $this->url;
+		}
+
+		$url = $this->url;
+
+		// Add query string to URL
+		if(count($this->data)) {
+
+			$url  .= false === strpos($url, '?') ? '?' : '&';
+			$url  .= urldecode(http_build_query($this->data));
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Get content body
+	 *  - Returns query string if not GET or HEAD
+	 *
+	 * @return string
+	 */
+	public function getBody()
+	{
+		if(in_array($this->getMethod(), array(RequestInterface::METHOD_GET, RequestInterface::METHOD_HEAD))) {
+			return '';
+		}
+
+		return urldecode(http_build_query($this->getRequestData()));
+	}
+
+	/**
+	 * Set request data
+	 *
+	 * @param array $data
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setRequestData(array $data)
+	{
+		$this->data = $data;
+
+		return $this;
+	}
+
+	/**
+	 * Get request data
+	 *
+	 * @param boolean $flat
+	 * @return array
+	 */
+	public function getRequestData($flat = true)
+	{
+		if($flat) {
+			$this->flattenData($this->data);
+		}
+
+		return $this->data;
+	}
+
+	/**
+	 * Flatten data into single
+	 * dimensional array
+	 *
+	 * @param array $data
+	 * @param string $prefix
+	 * @param string $format
+	 * @return array
+	 */
+	protected function flattenData(array $data, $prefix  = '', $format = '%s')
+	{
+		$flat = array();
+
+		foreach($data as $name => $value) {
+
+			$ref = $prefix . sprintf($format, $name);
+
+			if(is_array($value)) {
+
+				$flat += $this->flattenArray($value, $ref, '[%s]');
+				continue;
+			}
+
+			$flat[$ref] = $value;
+		}
+
+		return $flat;
+	}
+
+	/**
+	 * Set headers
+	 *
+	 * @param array $headers
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setHeaders(array $headers)
+	{
+		$this->headers = $headers;
+	}
+
+	/**
+	 * Add single header
+	 *
+	 * @param string $header
+	 * @param string $value
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function addHeader($header, $value)
+	{
+		$this->headers[$header] = $value;
+
+		return $this;
+	}
+
+	/**
+	 * Merge headers with existing
+	 *
+	 * @param array $headers
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function addHeaders(array $headers)
+	{
+		$this->headers = array_merge($this->headers, $headers);
+
+		return $this;
+	}
+
+	/**
+	 * Get request headers
+	 *
+	 * @param string $format
+	 * @return array
+	 */
+	public function getHeaders($format = 'default')
+	{
+		if($format == 'json') {
+			return json_encode($this->headers);
+		}
+
+		return $this->headers;
+	}
+}

+ 115 - 0
src/JonnyW/PhantomJs/Message/RequestInterface.php

@@ -0,0 +1,115 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace JonnyW\PhantomJs\Message;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+interface RequestInterface
+{
+	const METHOD_OPTIONS = 'OPTIONS';
+	const METHOD_GET     = 'GET';
+	const METHOD_HEAD    = 'HEAD';
+	const METHOD_POST    = 'POST';
+	const METHOD_PUT     = 'PUT';
+	const METHOD_DELETE  = 'DELETE';
+	const METHOD_PATCH   = 'PATCH';
+
+	/**
+	 * Set request method
+	 *
+	 * @param string $method
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setMethod($method);
+
+	/**
+	 * Get request method
+	 *
+	 * @return string
+	 */
+	public function getMethod();
+
+	/**
+	 * Set request URL
+	 *
+	 * @param string $url
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setUrl($url);
+
+	/**
+	 * Get request URL
+	 *  - Assembles query string for GET
+	 *  and HEAD requests
+	 *
+	 * @return string
+	 */
+	public function getUrl();
+
+	/**
+	 * Get content body
+	 *  - Returns query string if not GET or HEAD
+	 *
+	 * @return string
+	 */
+	public function getBody();
+
+	/**
+	 * Set request data
+	 *
+	 * @param array $data
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setRequestData(array $data);
+
+	/**
+	 * Get request data
+	 *
+	 * @param boolean $flat
+	 * @return array
+	 */
+	public function getRequestData($flat = true);
+
+	/**
+	 * Set headers
+	 *
+	 * @param array $headers
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function setHeaders(array $headers);
+
+	/**
+	 * Add single header
+	 *
+	 * @param string $header
+	 * @param string $value
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function addHeader($header, $value);
+
+	/**
+	 * Merge headers with existing
+	 *
+	 * @param array $headers
+	 * @return JonnyW\PhantomJs\Message\Request
+	 */
+	public function addHeaders(array $headers);
+
+	/**
+	 * Get request headers
+	 *
+	 * @param string $format
+	 * @return array
+	 */
+	public function getHeaders($format = 'default');
+}

+ 45 - 43
src/JonnyW/PhantomJs/Response.php → src/JonnyW/PhantomJs/Message/Response.php

@@ -2,13 +2,13 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
-namespace JonnyW\PhantomJs;
+namespace JonnyW\PhantomJs\Message;
 
-use JonnyW\PhantomJs\ResponseInterface;
+use JonnyW\PhantomJs\Message\ResponseInterface;
 
 /**
  * PHP PhantomJs
@@ -16,62 +16,62 @@ use JonnyW\PhantomJs\ResponseInterface;
  * @author Jon Wenmoth <contact@jonnyw.me>
  */
 class Response implements ResponseInterface
-{	
+{
 	/**
 	 * Http headers array
 	 *
 	 * @var array
 	 */
 	protected $headers;
-	
+
 	/**
 	 * Response int
 	 *
 	 * @var string
 	 */
 	protected $status;
-	
+
 	/**
 	 * Response body
 	 *
 	 * @var string
 	 */
 	protected $content;
-	
-	/** 
+
+	/**
 	 * Response content type header
-	 * 
+	 *
 	 * @var string
 	 */
 	protected $contentType;
-	
+
 	/**
 	 * Requested URL
 	 *
 	 * @var string
 	 */
 	protected $url;
-	
+
 	/**
 	 * Redirected URL
 	 *
 	 * @var string
 	 */
 	protected $redirectUrl;
-	
+
 	/**
 	 * Request time string
-	 * 
+	 *
 	 * @var string
 	 */
 	protected $time;
-	
-	/** 
-	 * Internal constructor
+
+	/**
+	 * Set response data
 	 *
-	 * @return void
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
 	 */
-	public function __construct(array $data)
+	public function setData(array $data)
 	{
 		$this->headers = array();
 
@@ -79,68 +79,70 @@ class Response implements ResponseInterface
 		if(isset($data['headers'])) {
 			$this->setHeaders((array) $data['headers']);
 		}
-		
+
 		// Set status
 		if(isset($data['status'])) {
 			$this->status = $data['status'];
 		}
-		
+
 		// Set content
 		if(isset($data['content'])) {
 			$this->content = $data['content'];
 		}
-		
+
 		// Set content type string
 		if(isset($data['contentType'])) {
 			$this->contentType = $data['contentType'];
 		}
-				
+
 		// Set request URL
 		if(isset($data['url'])) {
 			$this->url = $data['url'];
 		}
-		
+
 		// Set redirect URL
 		if(isset($data['redirectURL'])) {
 			$this->redirectUrl = $data['redirectURL'];
 		}
-		
+
 		// Set time string
 		if(isset($data['time'])) {
 			$this->time = $data['time'];
 		}
+
+		return $this;
 	}
-	
+
 	/**
 	 * Set headers array
 	 *
 	 * @param array $headers
-	 * @return 
+	 * @return
 	 */
 	protected function setHeaders(array $headers)
 	{
 		foreach($headers as $header) {
-			
+
 			if(isset($header['name']) && isset($header['value'])) {
 				$this->headers[$header['name']] = $header['value'];
 			}
 		}
-		
+
 		return $this;
 	}
-	
-	/** 
+
+	/**
 	 * Get HTTP headers array
 	 *
 	 * @return array
 	 */
 	public function getHeaders()
 	{
-		return (array) $this->headers;	
+		return (array) $this->headers;
 	}
-	
+
 	/**
-	 * Get HTTP header value for code 
+	 * Get HTTP header value for code
 	 *
 	 * @praam string $$code
 	 * @return mixed
@@ -150,10 +152,10 @@ class Response implements ResponseInterface
 		if(isset($this->headers[$code])) {
 			return $this->headers[$code];
 		}
-		
+
 		return null;
 	}
-	
+
 	/**
 	 * Get response status code
 	 *
@@ -163,7 +165,7 @@ class Response implements ResponseInterface
 	{
 		return (int) $this->status;
 	}
-	
+
 	/**
 	 * Get page content from respone
 	 *
@@ -173,7 +175,7 @@ class Response implements ResponseInterface
 	{
 		return $this->content;
 	}
-	
+
 	/**
 	 * Get content type header
 	 *
@@ -183,8 +185,8 @@ class Response implements ResponseInterface
 	{
 		return $this->contentType;
 	}
-	
-	/** 
+
+	/**
 	 * Get request URL
 	 *
 	 * @return string
@@ -193,7 +195,7 @@ class Response implements ResponseInterface
 	{
 		return $this->url;
 	}
-	
+
 	/**
 	 * Get redirect URL (if redirected)
 	 *
@@ -203,7 +205,7 @@ class Response implements ResponseInterface
 	{
 		return $this->redirectUrl;
 	}
-	
+
 	/**
 	 * Is response a redirect
 	 *  - Checks status codes
@@ -213,10 +215,10 @@ class Response implements ResponseInterface
 	public function isRedirect()
 	{
 		$status = $this->getStatus();
-	
+
 		return (bool) ($status >= 300 && $status < 307);
 	}
-	
+
 	/**
 	 * Get time string
 	 *

+ 22 - 15
src/JonnyW/PhantomJs/ResponseInterface.php → src/JonnyW/PhantomJs/Message/ResponseInterface.php

@@ -2,12 +2,12 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
- 
-namespace JonnyW\PhantomJs;
+
+namespace JonnyW\PhantomJs\Message;
 
 /**
  * PHP PhantomJs
@@ -15,57 +15,64 @@ namespace JonnyW\PhantomJs;
  * @author Jon Wenmoth <contact@jonnyw.me>
  */
 interface ResponseInterface
-{	
-	/** 
+{
+	/**
+	 * Set response data
+	 *
+	 * @return JonnyW\PhantomJs\Message\ResponseInterface
+	 */
+	public function setData(array $data);
+
+	/**
 	 * Get HTTP headers array
 	 *
 	 * @return array
 	 */
 	public function getHeaders();
-	
+
 	/**
-	 * Get HTTP header value for code 
+	 * Get HTTP header value for code
 	 *
 	 * @praam string $$code
 	 * @return mixed
 	 */
 	public function getHeader($code);
-	
+
 	/**
 	 * Get response status code
 	 *
 	 * @return int|null
 	 */
 	public function getStatus();
-	
+
 	/**
 	 * Get page content from respone
 	 *
 	 * @return string
 	 */
 	public function getContent();
-	
+
 	/**
 	 * Get content type header
 	 *
 	 * @return string
 	 */
 	public function getContentType();
-	
-	/** 
+
+	/**
 	 * Get request URL
 	 *
 	 * @return string
 	 */
 	public function getUrl();
-	
+
 	/**
 	 * Get redirect URL (if redirected)
 	 *
 	 * @return string
 	 */
 	public function getRedirectUrl();
-	
+
 	/**
 	 * Is response a redirect
 	 *  - Checks status codes
@@ -73,7 +80,7 @@ interface ResponseInterface
 	 * @return boolean
 	 */
 	public function isRedirect();
-	
+
 	/**
 	 * Get time string
 	 *

+ 82 - 81
test/JonnyW/PhantomJs/Test/ClientTest.php

@@ -2,7 +2,7 @@
 
 /*
  * This file is part of the php-phantomjs.
- * 
+ *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
@@ -17,71 +17,65 @@ use JonnyW\PhantomJs\Client;
  */
 class ClientTest extends \PHPUnit_Framework_TestCase
 {
+	/**
+	 * Client instance
+	 *
+	 * @var JonnyW\PhantomJs\Client
+	 */
+	protected $client;
+
 	/**
 	 * Setup tests
 	 *
 	 * @return void
 	 */
 	protected function setUp()
-    {
-        parent::setUp();
-        
-        $this->client = Client::getInstance();
-    }
+	{
+		parent::setUp();
+
+		$this->client = Client::getInstance();
+	}
 
 	/**
-	 * Test invalid URL's throw exception
+	 * Test get factory instance
 	 *
-     * @dataProvider provideInvalidHosts
-     *
-     * @param string $host
 	 * @return void
-     */
-    public function testInvalidUrl($host)
-    {
-        $this->setExpectedException('JonnyW\\PhantomJs\\Exception\\InvalidUrlException');
-
-        $this->client->open($host);
-    }
-	
+	 */
+	public function testMessageFactoryInstance()
+	{
+		$factory = $this->client->getMessageFactory();
+
+		$this->assertInstanceOf('JonnyW\PhantomJs\Message\FactoryInterface', $factory);
+	}
+
 	/**
-	 * Invalid host data providers
+	 * Test exception is thrown when
+	 * PhantomJS executable cannot be run
 	 *
-	 * @return array
+	 * @return void
 	 */
-    public function provideInvalidHosts()
-    {
-        return array(
-            array('invalid_url'),
-            array('invalid_url.test')
-        );
-    }
-    
-    /**
-     * Test exception is thrown when 
-     * PhantomJS executable cannot be run
-     *
-     * @return void
-     */
-    public function testBinNotExecutable()
-    {
-	    $this->setExpectedException('JonnyW\\PhantomJs\\Exception\\NoPhantomJsException');
-
-        $this->client->setPhantomJs('/path/does/not/exist/phantomjs');
-    }
+	public function testBinNotExecutable()
+	{
+		$this->setExpectedException('JonnyW\PhantomJs\Exception\NoPhantomJsException');
+
+		$this->client->setPhantomJs('/path/does/not/exist/phantomjs');
+	}
 
 	/**
-     * Test exception is thrown when capture
-     * path is not writeable
-     *
-     * @return void
-     */
-    public function testPathNotWriteable()
-    {
-	    $this->setExpectedException('JonnyW\\PhantomJs\\Exception\\NotWriteableException');
-
-        $this->client->capture('http://google.com', 'path/does/not/exist/phantoms.png');
-    }
+	 * Test exception is thrown when capture
+	 * path is not writeable
+	 *
+	 * @return void
+	 */
+	public function testPathNotWriteable()
+	{
+		$this->setExpectedException('JonnyW\PhantomJs\Exception\NotWriteableException');
+
+		$request  = $this->getMock('JonnyW\PhantomJs\Message\Request', null, array('method' => 'GET', 'url' => 'http://jonnyw.me'));
+		$response  = $this->getMock('JonnyW\PhantomJs\Message\Response', null);
+
+		$this->client->send($request, $response, 'path/does/not/exist/phantoms.png');
+	}
 
 	/**
 	 * Test open page
@@ -90,18 +84,19 @@ class ClientTest extends \PHPUnit_Framework_TestCase
 	 */
 	public function testOpenPage()
 	{
-		$client 	= $this->getMock('JonnyW\PhantomJs\Client', array('request'));
-		$response 	= $this->getMock('JonnyW\PhantomJs\Response', null, array(array('content' => 'test')));
-		
-        $client->expects($this->once())
-            ->method('request')
-            ->will($this->returnValue($response));
-		
-		$actual = $client->open('http://jonnyw.me');
-		
+		$client  = $this->getMock('JonnyW\PhantomJs\Client', array('request'));
+		$request  = $this->getMock('JonnyW\PhantomJs\Message\Request', null, array('method' => 'GET', 'url' => 'http://jonnyw.me'));
+		$response  = $this->getMock('JonnyW\PhantomJs\Message\Response', null);
+
+		$client->expects($this->once())
+		->method('request')
+		->will($this->returnValue($response));
+
+		$actual = $client->send($request, $response);
+
 		$this->assertSame($response, $actual);
 	}
-	
+
 	/**
 	 * Test screen capture page
 	 *
@@ -109,18 +104,19 @@ class ClientTest extends \PHPUnit_Framework_TestCase
 	 */
 	public function testCapturePage()
 	{
-		$client 	= $this->getMock('JonnyW\PhantomJs\Client', array('request'));
-		$response 	= $this->getMock('JonnyW\PhantomJs\Response', null, array(array('content' => 'test')));
-		
-        $client->expects($this->once())
-            ->method('request')
-            ->will($this->returnValue($response));
-		
-		$actual = $client->capture('http://jonnyw.me', '/tmp/testing.png');
-		
+		$client  = $this->getMock('JonnyW\PhantomJs\Client', array('request'));
+		$request  = $this->getMock('JonnyW\PhantomJs\Message\Request', null, array('method' => 'GET', 'url' => 'http://jonnyw.me'));
+		$response  = $this->getMock('JonnyW\PhantomJs\Message\Response', null);
+
+		$client->expects($this->once())
+		->method('request')
+		->will($this->returnValue($response));
+
+		$actual = $client->send($request, $response, '/tmp/testing.png');
+
 		$this->assertSame($response, $actual);
 	}
-	
+
 	/**
 	 * Test page is redirect
 	 *
@@ -128,15 +124,20 @@ class ClientTest extends \PHPUnit_Framework_TestCase
 	 */
 	public function testRedirectPage()
 	{
-		$client 	= $this->getMock('JonnyW\PhantomJs\Client', array('request'));
-		$response 	= $this->getMock('JonnyW\PhantomJs\Response', null, array(array('content' => 'test', 'status' => 301, 'redirectUrl' => 'http://google.com')));
-		
-        $client->expects($this->once())
-            ->method('request')
-            ->will($this->returnValue($response));
-		
-		$actual = $client->open('http://jonnyw.me');
-		
-		$this->assertTrue($response->isRedirect());
+		$client  = $this->getMock('JonnyW\PhantomJs\Client', array('request'));
+		$request  = $this->getMock('JonnyW\PhantomJs\Message\Request', null, array('method' => 'GET', 'url' => 'http://jonnyw.me'));
+		$response  = $this->getMock('JonnyW\PhantomJs\Message\Response', array('getStatus'));
+
+		$client->expects($this->once())
+		->method('request')
+		->will($this->returnValue($response));
+
+		$response->expects($this->once())
+		->method('getStatus')
+		->will($this->returnValue(301));
+
+		$actual = $client->send($request, $response);
+
+		$this->assertTrue($actual->isRedirect());
 	}
 }

+ 62 - 0
test/JonnyW/PhantomJs/Test/Message/FactoryTest.php

@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Test\Message;
+
+use JonnyW\PhantomJs\Message\Factory;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class FactoryTest extends \PHPUnit_Framework_TestCase
+{
+	/**
+	 * Factory instance
+	 *
+	 * @var JonnyW\PhantomJs\Message\Factory
+	 */
+	protected $factory;
+
+	/**
+	 * Setup tests
+	 *
+	 * @return void
+	 */
+	protected function setUp()
+	{
+		parent::setUp();
+
+		$this->factory = Factory::getInstance();
+	}
+
+	/**
+	 * Test create request instance
+	 *
+	 * @return void
+	 */
+	public function testRequestInstance()
+	{
+		$request = $this->factory->createRequest();
+
+		$this->assertInstanceOf('JonnyW\PhantomJs\Message\RequestInterface', $request);
+	}
+
+	/**
+	 * Test create response instance
+	 *
+	 * @return void
+	 */
+	public function testResponseInstance()
+	{
+		$response = $this->factory->createResponse();
+
+		$this->assertInstanceOf('JonnyW\PhantomJs\Message\ResponseInterface', $response);
+	}
+}

+ 139 - 0
test/JonnyW/PhantomJs/Test/Message/RequestTest.php

@@ -0,0 +1,139 @@
+<?php
+
+/*
+ * This file is part of the php-phantomjs.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace JonnyW\PhantomJs\Test\Message;
+
+use JonnyW\PhantomJs\Message\Request;
+
+/**
+ * PHP PhantomJs
+ *
+ * @author Jon Wenmoth <contact@jonnyw.me>
+ */
+class RequestTest extends \PHPUnit_Framework_TestCase
+{
+	/**
+	 * Test invalid URL's throw exception
+	 *
+	 * @dataProvider provideInvalidHosts
+	 *
+	 * @param string $url
+	 * @return void
+	 */
+	public function testInvalidUrl($url)
+	{
+		$this->setExpectedException('JonnyW\\PhantomJs\\Exception\\InvalidUrlException');
+
+		$request = new Request();
+		$request->setUrl($url);
+	}
+
+	/**
+	 * Invalid host data providers
+	 *
+	 * @return array
+	 */
+	public function provideInvalidHosts()
+	{
+		return array(
+			array('invalid_url'),
+			array('invalid_url.test')
+		);
+	}
+
+	/**
+	 * Test invalid methods throw exception
+	 *
+	 * @dataProvider provideInvalidMethods
+	 *
+	 * @param string $method
+	 * @return void
+	 */
+	public function testInvalidMethod($method)
+	{
+		$this->setExpectedException('JonnyW\\PhantomJs\\Exception\\InvalidMethodException');
+
+		$request = new Request();
+		$request->setMethod($method);
+	}
+
+	/**
+	 * Invalid method data providers
+	 *
+	 * @return array
+	 */
+	public function provideInvalidMethods()
+	{
+		return array(
+			array('GOT'),
+			array('FIND')
+		);
+	}
+
+	/**
+	 * Test invalid methods throw exception
+	 *
+	 * @return void
+	 */
+	public function testGetRequestBody()
+	{
+		$data = array('name' => 'jonnyw', 'email' => 'contact@jonnyw.me');
+
+		$request = new Request('GET', 'http://jonnyw.me');
+		$request->setRequestData($data);
+
+		$this->assertEmpty($request->getBody());
+	}
+
+	/**
+	 * Test GET URL parameters when URL
+	 * does not have existing parameters
+	 *
+	 * @return void
+	 */
+	public function testGetUrlQuery()
+	{
+		$data = array('name' => 'jonnyw', 'email' => 'contact@jonnyw.me');
+
+		$request = new Request('GET', 'http://jonnyw.me?query=true');
+		$request->setRequestData($data);
+
+		$this->assertEquals($request->getUrl(), 'http://jonnyw.me?query=true&' . urldecode(http_build_query($data)));
+	}
+
+	/**
+	 * Test GET URL parameters when URL
+	 * does not have existing parameters
+	 *
+	 * @return void
+	 */
+	public function testGetUrlQueryClean()
+	{
+		$data = array('name' => 'jonnyw', 'email' => 'contact@jonnyw.me');
+
+		$request = new Request('GET', 'http://jonnyw.me');
+		$request->setRequestData($data);
+
+		$this->assertEquals($request->getUrl(), 'http://jonnyw.me?' . urldecode(http_build_query($data)));
+	}
+
+	/**
+	 * Test invalid methods throw exception
+	 *
+	 * @return void
+	 */
+	public function testPostRequestBody()
+	{
+		$data = array('name' => 'jonnyw', 'email' => 'contact@jonnyw.me');
+
+		$request = new Request('POST', 'http://jonnyw.me');
+		$request->setRequestData($data);
+
+		$this->assertEquals($request->getBody(), urldecode(http_build_query($data)));
+	}
+}

+ 2 - 2
test/bootstrap.php

@@ -1,7 +1,7 @@
 <?php
 
 if(!$loader = @include __DIR__.'/../vendor/autoload.php') {
-    echo <<<EOM
+	echo <<<EOM
 You must set up the project dependencies by running the following commands:
 
     curl -s http://getcomposer.org/installer | php
@@ -9,7 +9,7 @@ You must set up the project dependencies by running the following commands:
 
 EOM;
 
-    exit(1);
+	exit(1);
 }
 
 $loader->add('JonnyW\PhantomJs\Test', __DIR__);