<?php
/*
Copyright (C) 2011-2012 Codernity.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class Codernitydb
{
/**
* @var string CodernityDB-HTTP host address
*/
private $_host = 'localhost';
/**
* @var string CodernityDB-HTTP port number
*/
private $_port = 9876;
/**
* @var string CodernityDB-HTTP username
*/
private $_username = null;
/**
* @var string CodernityDB-HTTP password
*/
private $_password = null;
/**
* @var boolean tell if curl PHP extension has been detected
*/
protected $_curl = false;
/**
* @var boolean tell if msgpack PHP extension has been detected
*/
protected $_msgpack = false;
/**
* @var string default request content type
*/
protected $_contentType = 'application/json';
/**
* class constructor
*
* @param array $config cofiguration data
*/
public function __construct($config = array())
{
if (isset($config['host']))
$this->_host = $config['host'];
if (isset($config['port']))
$this->_host = $config['port'];
$this->_username = $config['username'];
$this->_password = $config['password'];
if (function_exists('curl_init'))
$this->_curl = true;
if (function_exists('msgpack_pack'))
$this->_msgpack = true;
if($this->_msgpack)
$this->_contentType = 'application/msgpack';
}
/**
* getting data
*
* @param string $docId document ID
* @return stdClass { status: [int] response code,
* doc: [array] decoded response body |
* details: [array] decoded response body }
*/
public function get($docId)
{
$response = $this->_makeRequest('GET', '/get/id/' . $docId);
$output = new StdClass;
$output->status = $response->status;
if ($response->status == 200) {
$output->doc = $response->content;
} else {
$output->details = $response->content;
}
return $output;
}
/**
* inserting new document
*
* @param array $doc document
* @return stdClass { status: [int] response code,
* doc: [array] decoded response body |
* details: [array] decoded response body }
*/
public function insert($doc)
{
$response = $this->_makeRequest('POST', '/insert', $doc);
$output = new StdClass;
$output->status = $response->status;
if ($response->status == 200) {
$output->doc = $response->content;
} else {
$output->details = $response->content;
}
return $output;
}
/**
* update existing document
*
* @param array $doc document
* @return stdClass { status: [int] response code,
* doc: [array] decoded response body |
* details: [array] decoded response body }
*/
public function update($doc)
{
$response = $this->_makeRequest('POST', '/update', $doc);
$output = new StdClass;
$output->status = $response->status;
if ($response->status == 200) {
$output->doc = $response->content;
} else {
$output->details = $response->content;
}
return $output;
}
/**
* purging data
*
* @param string $docId document id
* @param string $revId revision id
* @return stdClass { true | status: [int] response code,
* details: [array] decoded response body }
*/
public function delete($docId, $revId)
{
$data = array(
'_id' => $docId,
'_rev' => $revId
);
$response = $this->_makeRequest('POST', '/delete', $data);
if ($response->status == 200) {
return $response->content;
}
$output = new StdClass;
$output->details = $response->content;
return $output;
}
/**
* prepares HTTP request
*/
private function _makeRequest($method, $url, $data = false)
{
if ($this->_curl)
return $this->_makeCurlRequest($method, $url, $data);
else
return $this->_makeSocketRequest($method, $url, $data);
}
/**
* makes CURL request
*/
private function _makeCurlRequest($method, $url, $data = false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->_host . $url);
curl_setopt($ch, CURLOPT_PORT, $this->_port);
curl_setopt(
$ch,
CURLOPT_USERPWD,
$this->_username . ":" . $this->_password
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array('Content-type: ' . $this->_contentType)
);
if (is_object($data) || is_array($data)) {
$data = $this->_prepareInputData($data);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
$content = curl_exec($ch);
if ($content === false) {
throw new Exception(
'Could not open connection to '
. $this->_host . ':' . $this->_port . ': '
. curl_error($ch)
);
return false;
}
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$output = new StdClass;
$output->status = $status;
$output->content = $this->_prepareOutputData($content);
return $output;
}
/**
* makes Socket request
*/
private function _makeSocketRequest($method, $url, $data = false)
{
$request = $this->_prepareSocketRequest($method, $url, $data);
$socket = @fsockopen(
$this->_host,
$this->_port,
$errNum,
$errString
);
if (!$socket) {
throw new Exception(
'Could not open connection to '
. $this->_host . ':' . $this->_port . ': '
. $errString.' (' . $errNum . ')'
);
return false;
}
fwrite($socket, $request);
$response = '';
while (!feof($socket))
$response .= fgets($socket);
@fclose($socket);
return $this->_parseSocketResponse($response);
}
/**
* encodes input data to JSON
*/
private function _prepareInputData($data)
{
if($this->_msgpack)
$func = 'msgpack_pack';
else
$func = 'json_encode';
return $func(
array(
'data' => $data
)
);
}
/**
* decodes output data from JSON
*/
private function _prepareOutputData($data)
{
if ($this->_msgpack)
return msgpack_unpack($data);
else
return get_object_vars(json_decode($data));
}
/**
* prepares socket request content
*/
private function _prepareSocketRequest($method, $url, $data)
{
$request = "$method $url HTTP/1.0\r\n";
$request .= "Host: $this->_host \r\n";
$request .= 'Authorization: Basic ' . base64_encode(
$this->_username . ':' . $this->_password
) . "\r\n";
$request .= "Accept: ' . $this->_contentType
. ',text/html,text/plain,*/*\r\n";
if (is_object($data) || is_array($data)) {
$data = $this->_prepareInputData($data);
$request .= "Content-Type: $this->_contentType\r\n";
$request .= "Content-Length: strlen($data)\r\n\r\n";
$request .= $data."\r\n";
} else {
$request .= "\r\n";
}
return $request;
}
/**
* returns body and status from socket response
*/
private function _parseSocketResponse($rawData, $jsonAsArray = FALSE)
{
if (!strlen($rawData))
throw new InvalidArgumentException("no data to parse");
while (
!substr_compare(
$rawData, "HTTP/1.1 100 Continue\r\n\r\n",
0,
25
)
) {
$rawData = substr($rawData, 25);
}
$response = new StdClass;
list($headers, $body) = explode("\r\n\r\n", $rawData, 2);
$headersArray = explode("\n", $headers);
$statusLine = reset($headersArray);
$statusArray = explode(' ', $statusLine, 3);
$response->status = trim($statusArray[1]);
if (strlen($body)) {
$response->content =
preg_match(
'@Content-Type:\s+' . $this->_contentType.'@i',
$headers
)
? json_decode($body, $jsonAsArray)
: $body;
}
return $response;
}
}