Include composer dependencies in repo

This commit is contained in:
Frederic Guillot
2017-10-25 16:22:10 -07:00
parent c507c54162
commit 9e2b2a32fd
1787 changed files with 123616 additions and 45 deletions

21
vendor/fguillot/json-rpc/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Frederic Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,194 @@
<?php
namespace JsonRPC;
use Exception;
use JsonRPC\Request\RequestBuilder;
use JsonRPC\Response\ResponseParser;
/**
* JsonRPC client class
*
* @package JsonRPC
* @author Frederic Guillot
*/
class Client
{
/**
* If the only argument passed to a function is an array
* assume it contains named arguments
*
* @access private
* @var boolean
*/
private $isNamedArguments = true;
/**
* Do not immediately throw an exception on error. Return it instead.
*
* @access public
* @var boolean
*/
private $returnException = false;
/**
* True for a batch request
*
* @access private
* @var boolean
*/
private $isBatch = false;
/**
* Batch payload
*
* @access private
* @var array
*/
private $batch = array();
/**
* Http Client
*
* @access private
* @var HttpClient
*/
private $httpClient;
/**
* Constructor
*
* @access public
* @param string $url Server URL
* @param bool $returnException Return exceptions
* @param HttpClient $httpClient HTTP client object
*/
public function __construct($url = '', $returnException = false, HttpClient $httpClient = null)
{
$this->httpClient = $httpClient ?: new HttpClient($url);
$this->returnException = $returnException;
}
/**
* Arguments passed are always positional
*
* @access public
* @return $this
*/
public function withPositionalArguments()
{
$this->isNamedArguments = false;
return $this;
}
/**
* Get HTTP Client
*
* @access public
* @return HttpClient
*/
public function getHttpClient()
{
return $this->httpClient;
}
/**
* Set username and password
*
* @access public
* @param string $username
* @param string $password
* @return $this
*/
public function authentication($username, $password)
{
$this->httpClient
->withUsername($username)
->withPassword($password);
return $this;
}
/**
* Automatic mapping of procedures
*
* @access public
* @param string $method Procedure name
* @param array $params Procedure arguments
* @return mixed
*/
public function __call($method, array $params)
{
if ($this->isNamedArguments && count($params) === 1 && is_array($params[0])) {
$params = $params[0];
}
return $this->execute($method, $params);
}
/**
* Start a batch request
*
* @access public
* @return Client
*/
public function batch()
{
$this->isBatch = true;
$this->batch = array();
return $this;
}
/**
* Send a batch request
*
* @access public
* @return array
*/
public function send()
{
$this->isBatch = false;
return $this->sendPayload('['.implode(', ', $this->batch).']');
}
/**
* Execute a procedure
*
* @access public
* @param string $procedure Procedure name
* @param array $params Procedure arguments
* @param array $reqattrs
* @return mixed
*/
public function execute($procedure, array $params = array(), array $reqattrs = array())
{
$payload = RequestBuilder::create()
->withProcedure($procedure)
->withParams($params)
->withRequestAttributes($reqattrs)
->build();
if ($this->isBatch) {
$this->batch[] = $payload;
return $this;
}
return $this->sendPayload($payload);
}
/**
* Send payload
*
* @access private
* @throws Exception
* @param string $payload
* @return Exception|Client
*/
private function sendPayload($payload)
{
return ResponseParser::create()
->withReturnException($this->returnException)
->withPayload($this->httpClient->execute($payload))
->parse();
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class AccessDeniedException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class AccessDeniedException extends Exception
{
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class AuthenticationFailureException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class AuthenticationFailureException extends Exception
{
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class ConnectionFailureException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ConnectionFailureException extends Exception
{
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class InvalidJsonFormatException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class InvalidJsonFormatException extends Exception
{
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class InvalidJsonRpcFormatException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class InvalidJsonRpcFormatException extends Exception
{
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class ResponseEncodingFailureException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ResponseEncodingFailureException extends Exception
{
}

View File

@@ -0,0 +1,62 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class ResponseException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ResponseException extends Exception
{
/**
* A value that contains additional information about the error.
*
* @access protected
* @link http://www.jsonrpc.org/specification#error_object
* @var mixed
*/
protected $data;
/**
* Constructor
*
* @access public
* @param string $message [optional] The Exception message to throw.
* @param int $code [optional] The Exception code.
* @param Exception $previous [optional] The previous exception used for the exception chaining. Since 5.3.0
* @param mixed $data [optional] A value that contains additional information about the error.
*/
public function __construct($message = '', $code = 0, Exception $previous = null, $data = null)
{
parent::__construct($message, $code, $previous);
$this->setData($data);
}
/**
* Attach additional information
*
* @access public
* @param mixed $data [optional] A value that contains additional information about the error.
* @return \JsonRPC\Exception\ResponseException
*/
public function setData($data = null)
{
$this->data = $data;
return $this;
}
/**
* Get additional information
*
* @access public
* @return mixed|null
*/
public function getData()
{
return $this->data;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class ServerErrorException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ServerErrorException extends Exception
{
}

View File

@@ -0,0 +1,365 @@
<?php
namespace JsonRPC;
use Closure;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\ConnectionFailureException;
use JsonRPC\Exception\ServerErrorException;
/**
* Class HttpClient
*
* @package JsonRPC
* @author Frederic Guillot
*/
class HttpClient
{
/**
* URL of the server
*
* @access private
* @var string
*/
private $url;
/**
* HTTP client timeout
*
* @access private
* @var integer
*/
private $timeout = 5;
/**
* Default HTTP headers to send to the server
*
* @access private
* @var array
*/
private $headers = array(
'User-Agent: JSON-RPC PHP Client <https://github.com/fguillot/JsonRPC>',
'Content-Type: application/json',
'Accept: application/json',
'Connection: close',
);
/**
* Username for authentication
*
* @access private
* @var string
*/
private $username;
/**
* Password for authentication
*
* @access private
* @var string
*/
private $password;
/**
* Enable debug output to the php error log
*
* @access private
* @var boolean
*/
private $debug = false;
/**
* Cookies
*
* @access private
* @var array
*/
private $cookies = array();
/**
* SSL certificates verification
*
* @access private
* @var boolean
*/
private $verifySslCertificate = true;
/**
* Callback called before the doing the request
*
* @access private
* @var Closure
*/
private $beforeRequest;
/**
* HttpClient constructor
*
* @access public
* @param string $url
*/
public function __construct($url = '')
{
$this->url = $url;
}
/**
* Set URL
*
* @access public
* @param string $url
* @return $this
*/
public function withUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Set username
*
* @access public
* @param string $username
* @return $this
*/
public function withUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Set password
*
* @access public
* @param string $password
* @return $this
*/
public function withPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Set timeout
*
* @access public
* @param integer $timeout
* @return $this
*/
public function withTimeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Set timeout
*
* @access public
* @param array $headers
* @return $this
*/
public function withHeaders(array $headers)
{
$this->headers = array_merge($this->headers, $headers);
return $this;
}
/**
* Set cookies
*
* @access public
* @param array $cookies
* @param boolean $replace
*/
public function withCookies(array $cookies, $replace = false)
{
if ($replace) {
$this->cookies = $cookies;
} else {
$this->cookies = array_merge($this->cookies, $cookies);
}
}
/**
* Enable debug mode
*
* @access public
* @return $this
*/
public function withDebug()
{
$this->debug = true;
return $this;
}
/**
* Disable SSL verification
*
* @access public
* @return $this
*/
public function withoutSslVerification()
{
$this->verifySslCertificate = false;
return $this;
}
/**
* Assign a callback before the request
*
* @access public
* @param Closure $closure
* @return $this
*/
public function withBeforeRequestCallback(Closure $closure)
{
$this->beforeRequest = $closure;
return $this;
}
/**
* Get cookies
*
* @access public
* @return array
*/
public function getCookies()
{
return $this->cookies;
}
/**
* Do the HTTP request
*
* @access public
* @throws ConnectionFailureException
* @param string $payload
* @return array
*/
public function execute($payload)
{
if (is_callable($this->beforeRequest)) {
call_user_func_array($this->beforeRequest, array($this, $payload));
}
$stream = fopen(trim($this->url), 'r', false, $this->buildContext($payload));
if (! is_resource($stream)) {
throw new ConnectionFailureException('Unable to establish a connection');
}
$metadata = stream_get_meta_data($stream);
$headers = $metadata['wrapper_data'];
$response = json_decode(stream_get_contents($stream), true);
if ($this->debug) {
error_log('==> Request: '.PHP_EOL.(is_string($payload) ? $payload : json_encode($payload, JSON_PRETTY_PRINT)));
error_log('==> Headers: '.PHP_EOL.var_export($headers, true));
error_log('==> Response: '.PHP_EOL.json_encode($response, JSON_PRETTY_PRINT));
}
$this->handleExceptions($headers);
$this->parseCookies($headers);
return $response;
}
/**
* Prepare stream context
*
* @access private
* @param string $payload
* @return resource
*/
private function buildContext($payload)
{
$headers = $this->headers;
if (! empty($this->username) && ! empty($this->password)) {
$headers[] = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password);
}
if (! empty($this->cookies)){
$cookies = array();
foreach ($this->cookies as $key => $value) {
$cookies[] = $key.'='.$value;
}
$headers[] = 'Cookie: '.implode('; ', $cookies);
}
return stream_context_create(array(
'http' => array(
'method' => 'POST',
'protocol_version' => 1.1,
'timeout' => $this->timeout,
'max_redirects' => 2,
'header' => implode("\r\n", $headers),
'content' => $payload,
'ignore_errors' => true,
),
'ssl' => array(
'verify_peer' => $this->verifySslCertificate,
'verify_peer_name' => $this->verifySslCertificate,
)
));
}
/**
* Parse cookies from response
*
* @access private
* @param array $headers
*/
private function parseCookies(array $headers)
{
foreach ($headers as $header) {
$pos = stripos($header, 'Set-Cookie:');
if ($pos !== false) {
$cookies = explode(';', substr($header, $pos + 11));
foreach ($cookies as $cookie) {
$item = explode('=', $cookie);
if (count($item) === 2) {
$name = trim($item[0]);
$value = $item[1];
$this->cookies[$name] = $value;
}
}
}
}
}
/**
* Throw an exception according the HTTP response
*
* @access public
* @param array $headers
* @throws AccessDeniedException
* @throws ServerErrorException
*/
public function handleExceptions(array $headers)
{
$exceptions = array(
'401' => '\JsonRPC\Exception\AccessDeniedException',
'403' => '\JsonRPC\Exception\AccessDeniedException',
'404' => '\JsonRPC\Exception\ConnectionFailureException',
'500' => '\JsonRPC\Exception\ServerErrorException',
);
foreach ($headers as $header) {
foreach ($exceptions as $code => $exception) {
if (strpos($header, 'HTTP/1.0 '.$code) !== false || strpos($header, 'HTTP/1.1 '.$code) !== false) {
throw new $exception('Response: '.$header);
}
}
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace JsonRPC;
/**
* Class MiddlewareHandler
*
* @package JsonRPC
* @author Frederic Guillot
*/
class MiddlewareHandler
{
/**
* Procedure Name
*
* @access protected
* @var string
*/
protected $procedureName = '';
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* List of middleware to execute before to call the method
*
* @access protected
* @var MiddlewareInterface[]
*/
protected $middleware = array();
/**
* Set username
*
* @access public
* @param string $username
* @return $this
*/
public function withUsername($username)
{
if (! empty($username)) {
$this->username = $username;
}
return $this;
}
/**
* Set password
*
* @access public
* @param string $password
* @return $this
*/
public function withPassword($password)
{
if (! empty($password)) {
$this->password = $password;
}
return $this;
}
/**
* Set procedure name
*
* @access public
* @param string $procedureName
* @return $this
*/
public function withProcedure($procedureName)
{
$this->procedureName = $procedureName;
return $this;
}
/**
* Add a new middleware
*
* @access public
* @param MiddlewareInterface $middleware
* @return MiddlewareHandler
*/
public function withMiddleware(MiddlewareInterface $middleware)
{
$this->middleware[] = $middleware;
return $this;
}
/**
* Execute all middleware
*
* @access public
*/
public function execute()
{
foreach ($this->middleware as $middleware) {
$middleware->execute($this->username, $this->password, $this->procedureName);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace JsonRPC;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
/**
* Interface MiddlewareInterface
*
* @package JsonRPC
* @author Frederic Guillot
*/
interface MiddlewareInterface
{
/**
* Execute Middleware
*
* @access public
* @param string $username
* @param string $password
* @param string $procedureName
* @throws AccessDeniedException
* @throws AuthenticationFailureException
*/
public function execute($username, $password, $procedureName);
}

View File

@@ -0,0 +1,264 @@
<?php
namespace JsonRPC;
use BadFunctionCallException;
use Closure;
use InvalidArgumentException;
use ReflectionFunction;
use ReflectionMethod;
/**
* Class ProcedureHandler
*
* @package JsonRPC
* @author Frederic Guillot
*/
class ProcedureHandler
{
/**
* List of procedures
*
* @access protected
* @var array
*/
protected $callbacks = array();
/**
* List of classes
*
* @access protected
* @var array
*/
protected $classes = array();
/**
* List of instances
*
* @access protected
* @var array
*/
protected $instances = array();
/**
* Before method name to call
*
* @access protected
* @var string
*/
protected $beforeMethodName = '';
/**
* Register a new procedure
*
* @access public
* @param string $procedure Procedure name
* @param closure $callback Callback
* @return $this
*/
public function withCallback($procedure, Closure $callback)
{
$this->callbacks[$procedure] = $callback;
return $this;
}
/**
* Bind a procedure to a class
*
* @access public
* @param string $procedure Procedure name
* @param mixed $class Class name or instance
* @param string $method Procedure name
* @return $this
*/
public function withClassAndMethod($procedure, $class, $method = '')
{
if ($method === '') {
$method = $procedure;
}
$this->classes[$procedure] = array($class, $method);
return $this;
}
/**
* Bind a class instance
*
* @access public
* @param mixed $instance
* @return $this
*/
public function withObject($instance)
{
$this->instances[] = $instance;
return $this;
}
/**
* Set a before method to call
*
* @access public
* @param string $methodName
* @return $this
*/
public function withBeforeMethod($methodName)
{
$this->beforeMethodName = $methodName;
return $this;
}
/**
* Execute the procedure
*
* @access public
* @param string $procedure Procedure name
* @param array $params Procedure params
* @return mixed
*/
public function executeProcedure($procedure, array $params = array())
{
if (isset($this->callbacks[$procedure])) {
return $this->executeCallback($this->callbacks[$procedure], $params);
} elseif (isset($this->classes[$procedure]) && method_exists($this->classes[$procedure][0], $this->classes[$procedure][1])) {
return $this->executeMethod($this->classes[$procedure][0], $this->classes[$procedure][1], $params);
}
foreach ($this->instances as $instance) {
if (method_exists($instance, $procedure)) {
return $this->executeMethod($instance, $procedure, $params);
}
}
throw new BadFunctionCallException('Unable to find the procedure');
}
/**
* Execute a callback
*
* @access public
* @param Closure $callback Callback
* @param array $params Procedure params
* @return mixed
*/
public function executeCallback(Closure $callback, $params)
{
$reflection = new ReflectionFunction($callback);
$arguments = $this->getArguments(
$params,
$reflection->getParameters(),
$reflection->getNumberOfRequiredParameters(),
$reflection->getNumberOfParameters()
);
return $reflection->invokeArgs($arguments);
}
/**
* Execute a method
*
* @access public
* @param mixed $class Class name or instance
* @param string $method Method name
* @param array $params Procedure params
* @return mixed
*/
public function executeMethod($class, $method, $params)
{
$instance = is_string($class) ? new $class : $class;
$reflection = new ReflectionMethod($class, $method);
$this->executeBeforeMethod($instance, $method);
$arguments = $this->getArguments(
$params,
$reflection->getParameters(),
$reflection->getNumberOfRequiredParameters(),
$reflection->getNumberOfParameters()
);
return $reflection->invokeArgs($instance, $arguments);
}
/**
* Execute before method if defined
*
* @access public
* @param mixed $object
* @param string $method
*/
public function executeBeforeMethod($object, $method)
{
if ($this->beforeMethodName !== '' && method_exists($object, $this->beforeMethodName)) {
call_user_func_array(array($object, $this->beforeMethodName), array($method));
}
}
/**
* Get procedure arguments
*
* @access public
* @param array $requestParams Incoming arguments
* @param array $methodParams Procedure arguments
* @param integer $nbRequiredParams Number of required parameters
* @param integer $nbMaxParams Maximum number of parameters
* @return array
*/
public function getArguments(array $requestParams, array $methodParams, $nbRequiredParams, $nbMaxParams)
{
$nbParams = count($requestParams);
if ($nbParams < $nbRequiredParams) {
throw new InvalidArgumentException('Wrong number of arguments');
}
if ($nbParams > $nbMaxParams) {
throw new InvalidArgumentException('Too many arguments');
}
if ($this->isPositionalArguments($requestParams)) {
return $requestParams;
}
return $this->getNamedArguments($requestParams, $methodParams);
}
/**
* Return true if we have positional parameters
*
* @access public
* @param array $request_params Incoming arguments
* @return bool
*/
public function isPositionalArguments(array $request_params)
{
return array_keys($request_params) === range(0, count($request_params) - 1);
}
/**
* Get named arguments
*
* @access public
* @param array $requestParams Incoming arguments
* @param array $methodParams Procedure arguments
* @return array
*/
public function getNamedArguments(array $requestParams, array $methodParams)
{
$params = array();
foreach ($methodParams as $p) {
$name = $p->getName();
if (isset($requestParams[$name])) {
$params[$name] = $requestParams[$name];
} elseif ($p->isDefaultValueAvailable()) {
$params[$name] = $p->getDefaultValue();
} else {
throw new InvalidArgumentException('Missing argument: '.$name);
}
}
return $params;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace JsonRPC\Request;
/**
* Class BatchRequestParser
*
* @package JsonRPC\Request
* @author Frederic Guillot
*/
class BatchRequestParser extends RequestParser
{
/**
* Parse incoming request
*
* @access public
* @return string
*/
public function parse()
{
$responses = array();
foreach ($this->payload as $payload) {
$responses[] = RequestParser::create()
->withPayload($payload)
->withProcedureHandler($this->procedureHandler)
->withMiddlewareHandler($this->middlewareHandler)
->withLocalException($this->localExceptions)
->parse();
}
$responses = array_filter($responses);
return empty($responses) ? '' : '['.implode(',', $responses).']';
}
/**
* Return true if we have a batch request
*
* ex : [
* 0 => '...',
* 1 => '...',
* 2 => '...',
* 3 => '...',
* ]
*
* @static
* @access public
* @param array $payload
* @return bool
*/
public static function isBatchRequest(array $payload)
{
return array_keys($payload) === range(0, count($payload) - 1);
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace JsonRPC\Request;
/**
* Class RequestBuilder
*
* @package JsonRPC\Request
* @author Frederic Guillot
*/
class RequestBuilder
{
/**
* Request ID
*
* @access private
* @var mixed
*/
private $id = null;
/**
* Method name
*
* @access private
* @var string
*/
private $procedure = '';
/**
* Method arguments
*
* @access private
* @var array
*/
private $params = array();
/**
* Additional request attributes
*
* @access private
* @var array
*/
private $reqattrs = array();
/**
* Get new object instance
*
* @static
* @access public
* @return RequestBuilder
*/
public static function create()
{
return new static();
}
/**
* Set id
*
* @access public
* @param null $id
* @return RequestBuilder
*/
public function withId($id)
{
$this->id = $id;
return $this;
}
/**
* Set method
*
* @access public
* @param string $procedure
* @return RequestBuilder
*/
public function withProcedure($procedure)
{
$this->procedure = $procedure;
return $this;
}
/**
* Set parameters
*
* @access public
* @param array $params
* @return RequestBuilder
*/
public function withParams(array $params)
{
$this->params = $params;
return $this;
}
/**
* Set additional request attributes
*
* @access public
* @param array $reqattrs
* @return RequestBuilder
*/
public function withRequestAttributes(array $reqattrs)
{
$this->reqattrs = $reqattrs;
return $this;
}
/**
* Build the payload
*
* @access public
* @return string
*/
public function build()
{
$payload = array_merge_recursive($this->reqattrs, array(
'jsonrpc' => '2.0',
'method' => $this->procedure,
'id' => $this->id ?: mt_rand(),
));
if (! empty($this->params)) {
$payload['params'] = $this->params;
}
return json_encode($payload);
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace JsonRPC\Request;
use Exception;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\MiddlewareHandler;
use JsonRPC\ProcedureHandler;
use JsonRPC\Response\ResponseBuilder;
use JsonRPC\Validator\JsonFormatValidator;
use JsonRPC\Validator\RpcFormatValidator;
/**
* Class RequestParser
*
* @package JsonRPC
* @author Frederic Guillot
*/
class RequestParser
{
/**
* Request payload
*
* @access protected
* @var mixed
*/
protected $payload;
/**
* List of exceptions that should not be relayed to the client
*
* @access protected
* @var array()
*/
protected $localExceptions = array(
'JsonRPC\Exception\AuthenticationFailureException',
'JsonRPC\Exception\AccessDeniedException',
);
/**
* ProcedureHandler
*
* @access protected
* @var ProcedureHandler
*/
protected $procedureHandler;
/**
* MiddlewareHandler
*
* @access protected
* @var MiddlewareHandler
*/
protected $middlewareHandler;
/**
* Get new object instance
*
* @static
* @access public
* @return RequestParser
*/
public static function create()
{
return new static();
}
/**
* Set payload
*
* @access public
* @param mixed $payload
* @return $this
*/
public function withPayload($payload)
{
$this->payload = $payload;
return $this;
}
/**
* Exception classes that should not be relayed to the client
*
* @access public
* @param mixed $exception
* @return $this
*/
public function withLocalException($exception)
{
if (is_array($exception)) {
$this->localExceptions = array_merge($this->localExceptions, $exception);
} else {
$this->localExceptions[] = $exception;
}
return $this;
}
/**
* Set procedure handler
*
* @access public
* @param ProcedureHandler $procedureHandler
* @return $this
*/
public function withProcedureHandler(ProcedureHandler $procedureHandler)
{
$this->procedureHandler = $procedureHandler;
return $this;
}
/**
* Set middleware handler
*
* @access public
* @param MiddlewareHandler $middlewareHandler
* @return $this
*/
public function withMiddlewareHandler(MiddlewareHandler $middlewareHandler)
{
$this->middlewareHandler = $middlewareHandler;
return $this;
}
/**
* Parse incoming request
*
* @access public
* @return string
* @throws AccessDeniedException
* @throws AuthenticationFailureException
*/
public function parse()
{
try {
JsonFormatValidator::validate($this->payload);
RpcFormatValidator::validate($this->payload);
$this->middlewareHandler
->withProcedure($this->payload['method'])
->execute();
$result = $this->procedureHandler->executeProcedure(
$this->payload['method'],
empty($this->payload['params']) ? array() : $this->payload['params']
);
if (! $this->isNotification()) {
return ResponseBuilder::create()
->withId($this->payload['id'])
->withResult($result)
->build();
}
} catch (Exception $e) {
return $this->handleExceptions($e);
}
return '';
}
/**
* Handle exceptions
*
* @access protected
* @param Exception $e
* @return string
* @throws Exception
*/
protected function handleExceptions(Exception $e)
{
foreach ($this->localExceptions as $exception) {
if ($e instanceof $exception) {
throw $e;
}
}
if ($e instanceof InvalidJsonRpcFormatException || ! $this->isNotification()) {
return ResponseBuilder::create()
->withId(isset($this->payload['id']) ? $this->payload['id'] : null)
->withException($e)
->build();
}
return '';
}
/**
* Return true if the message is a notification
*
* @access protected
* @return bool
*/
protected function isNotification()
{
return is_array($this->payload) && !isset($this->payload['id']);
}
}

View File

@@ -0,0 +1,324 @@
<?php
namespace JsonRPC\Response;
use BadFunctionCallException;
use Exception;
use InvalidArgumentException;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\Exception\InvalidJsonFormatException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\Exception\ResponseEncodingFailureException;
use JsonRPC\Exception\ResponseException;
use JsonRPC\Validator\JsonEncodingValidator;
/**
* Class ResponseBuilder
*
* @package JsonRPC
* @author Frederic Guillot
*/
class ResponseBuilder
{
/**
* Payload ID
*
* @access private
* @var mixed
*/
private $id;
/**
* Payload ID
*
* @access private
* @var mixed
*/
private $result;
/**
* Payload error code
*
* @access private
* @var integer
*/
private $errorCode;
/**
* Payload error message
*
* @access private
* @var string
*/
private $errorMessage;
/**
* Payload error data
*
* @access private
* @var mixed
*/
private $errorData;
/**
* HTTP Headers
*
* @access private
* @var array
*/
private $headers = array(
'Content-Type' => 'application/json',
);
/**
* HTTP status
*
* @access private
* @var string
*/
private $status;
/**
* Exception
*
* @access private
* @var ResponseException
*/
private $exception;
/**
* Get new object instance
*
* @static
* @access public
* @return ResponseBuilder
*/
public static function create()
{
return new static();
}
/**
* Set id
*
* @access public
* @param mixed $id
* @return $this
*/
public function withId($id)
{
$this->id = $id;
return $this;
}
/**
* Set result
*
* @access public
* @param mixed $result
* @return $this
*/
public function withResult($result)
{
$this->result = $result;
return $this;
}
/**
* Set error
*
* @access public
* @param integer $code
* @param string $message
* @param string $data
* @return $this
*/
public function withError($code, $message, $data = '')
{
$this->errorCode = $code;
$this->errorMessage = $message;
$this->errorData = $data;
return $this;
}
/**
* Set exception
*
* @access public
* @param Exception $exception
* @return $this
*/
public function withException(Exception $exception)
{
$this->exception = $exception;
return $this;
}
/**
* Add HTTP header
*
* @access public
* @param string $name
* @param string $value
* @return $this
*/
public function withHeader($name, $value)
{
$this->headers[$name] = $value;
return $this;
}
/**
* Add HTTP Status
*
* @access public
* @param string $status
* @return $this
*/
public function withStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Get status
*
* @access public
* @return string
*/
public function getStatus()
{
return $this->status;
}
/**
* Get headers
*
* @access public
* @return string[]
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Build response
*
* @access public
* @return string
*/
public function build()
{
$encodedResponse = json_encode($this->buildResponse());
JsonEncodingValidator::validate();
return $encodedResponse;
}
/**
* Send HTTP headers
*
* @access public
* @return $this
*/
public function sendHeaders()
{
if (! empty($this->status)) {
header($this->status);
}
foreach ($this->headers as $name => $value) {
header($name.': '.$value);
}
return $this;
}
/**
* Build response payload
*
* @access private
* @return array
*/
private function buildResponse()
{
$response = array('jsonrpc' => '2.0');
$this->handleExceptions();
if (! empty($this->errorMessage)) {
$response['error'] = $this->buildErrorResponse();
} else {
$response['result'] = $this->result;
}
$response['id'] = $this->id;
return $response;
}
/**
* Build response error payload
*
* @access private
* @return array
*/
private function buildErrorResponse()
{
$response = array(
'code' => $this->errorCode,
'message' => $this->errorMessage,
);
if (! empty($this->errorData)) {
$response['data'] = $this->errorData;
}
return $response;
}
/**
* Transform exceptions to JSON-RPC errors
*
* @access private
*/
private function handleExceptions()
{
if ($this->exception instanceof InvalidJsonFormatException) {
$this->errorCode = -32700;
$this->errorMessage = 'Parse error';
$this->id = null;
} elseif ($this->exception instanceof InvalidJsonRpcFormatException) {
$this->errorCode = -32600;
$this->errorMessage = 'Invalid Request';
$this->id = null;
} elseif ($this->exception instanceof BadFunctionCallException) {
$this->errorCode = -32601;
$this->errorMessage = 'Method not found';
} elseif ($this->exception instanceof InvalidArgumentException) {
$this->errorCode = -32602;
$this->errorMessage = 'Invalid params';
} elseif ($this->exception instanceof ResponseEncodingFailureException) {
$this->errorCode = -32603;
$this->errorMessage = 'Internal error';
$this->errorData = $this->exception->getMessage();
} elseif ($this->exception instanceof AuthenticationFailureException) {
$this->errorCode = 401;
$this->errorMessage = 'Unauthorized';
$this->status = 'HTTP/1.0 401 Unauthorized';
$this->withHeader('WWW-Authenticate', 'Basic realm="JsonRPC"');
} elseif ($this->exception instanceof AccessDeniedException) {
$this->errorCode = 403;
$this->errorMessage = 'Forbidden';
$this->status = 'HTTP/1.0 403 Forbidden';
} elseif ($this->exception instanceof ResponseException) {
$this->errorCode = $this->exception->getCode();
$this->errorMessage = $this->exception->getMessage();
$this->errorData = $this->exception->getData();
} elseif ($this->exception instanceof Exception) {
$this->errorCode = $this->exception->getCode();
$this->errorMessage = $this->exception->getMessage();
}
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace JsonRPC\Response;
use BadFunctionCallException;
use InvalidArgumentException;
use Exception;
use JsonRPC\Exception\InvalidJsonFormatException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\Exception\ResponseException;
use JsonRPC\Validator\JsonFormatValidator;
/**
* Class ResponseParser
*
* @package JsonRPC\Request
* @author Frederic Guillot
*/
class ResponseParser
{
/**
* Payload
*
* @access private
* @var mixed
*/
private $payload;
/**
* Do not immediately throw an exception on error. Return it instead.
*
* @var bool
*/
private $returnException = false;
/**
* Get new object instance
*
* @static
* @access public
* @return ResponseParser
*/
public static function create()
{
return new static();
}
/**
* Set Return Exception Or Throw It
*
* @param $returnException
* @return ResponseParser
*/
public function withReturnException($returnException)
{
$this->returnException = $returnException;
return $this;
}
/**
* Set payload
*
* @access public
* @param mixed $payload
* @return $this
*/
public function withPayload($payload)
{
$this->payload = $payload;
return $this;
}
/**
* Parse response
*
* @return array|Exception|null
* @throws InvalidJsonFormatException
* @throws BadFunctionCallException
* @throws InvalidJsonRpcFormatException
* @throws InvalidArgumentException
* @throws Exception
* @throws ResponseException
*/
public function parse()
{
JsonFormatValidator::validate($this->payload);
if ($this->isBatchResponse()) {
$results = array();
foreach ($this->payload as $response) {
$results[] = self::create()
->withReturnException($this->returnException)
->withPayload($response)
->parse();
}
return $results;
}
if (isset($this->payload['error']['code'])) {
try {
$this->handleExceptions();
} catch (Exception $e) {
if ($this->returnException) {
return $e;
}
throw $e;
}
}
return isset($this->payload['result']) ? $this->payload['result'] : null;
}
/**
* Handle exceptions
*
* @access private
* @throws InvalidJsonFormatException
* @throws InvalidJsonRpcFormatException
* @throws ResponseException
*/
private function handleExceptions()
{
switch ($this->payload['error']['code']) {
case -32700:
throw new InvalidJsonFormatException('Parse error: '.$this->payload['error']['message']);
case -32600:
throw new InvalidJsonRpcFormatException('Invalid Request: '.$this->payload['error']['message']);
case -32601:
throw new BadFunctionCallException('Procedure not found: '.$this->payload['error']['message']);
case -32602:
throw new InvalidArgumentException('Invalid arguments: '.$this->payload['error']['message']);
default:
throw new ResponseException(
$this->payload['error']['message'],
$this->payload['error']['code'],
null,
isset($this->payload['error']['data']) ? $this->payload['error']['data'] : null
);
}
}
/**
* Return true if we have a batch response
*
* @access private
* @return boolean
*/
private function isBatchResponse()
{
return array_keys($this->payload) === range(0, count($this->payload) - 1);
}
}

View File

@@ -0,0 +1,386 @@
<?php
namespace JsonRPC;
use Closure;
use Exception;
use JsonRPC\Request\BatchRequestParser;
use JsonRPC\Request\RequestParser;
use JsonRPC\Response\ResponseBuilder;
use JsonRPC\Validator\HostValidator;
use JsonRPC\Validator\JsonFormatValidator;
use JsonRPC\Validator\UserValidator;
/**
* JsonRPC server class
*
* @package JsonRPC
* @author Frederic Guillot
*/
class Server
{
/**
* Allowed hosts
*
* @access protected
* @var array
*/
protected $hosts = array();
/**
* Data received from the client
*
* @access protected
* @var array
*/
protected $payload = array();
/**
* List of exceptions that should not be relayed to the client
*
* @access protected
* @var array()
*/
protected $localExceptions = array();
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* Allowed users
*
* @access protected
* @var array
*/
protected $users = array();
/**
* $_SERVER
*
* @access protected
* @var array
*/
protected $serverVariable;
/**
* ProcedureHandler object
*
* @access protected
* @var ProcedureHandler
*/
protected $procedureHandler;
/**
* MiddlewareHandler object
*
* @access protected
* @var MiddlewareHandler
*/
protected $middlewareHandler;
/**
* Response builder
*
* @access protected
* @var ResponseBuilder
*/
protected $responseBuilder;
/**
* Response builder
*
* @access protected
* @var RequestParser
*/
protected $requestParser;
/**
*
* Batch request parser
*
* @access protected
* @var BatchRequestParser
*/
protected $batchRequestParser;
/**
* Constructor
*
* @access public
* @param string $request
* @param array $server
* @param ResponseBuilder $responseBuilder
* @param RequestParser $requestParser
* @param BatchRequestParser $batchRequestParser
* @param ProcedureHandler $procedureHandler
* @param MiddlewareHandler $middlewareHandler
*/
public function __construct(
$request = '',
array $server = array(),
ResponseBuilder $responseBuilder = null,
RequestParser $requestParser = null,
BatchRequestParser $batchRequestParser = null,
ProcedureHandler $procedureHandler = null,
MiddlewareHandler $middlewareHandler = null
) {
if ($request !== '') {
$this->payload = json_decode($request, true);
} else {
$this->payload = json_decode(file_get_contents('php://input'), true);
}
$this->serverVariable = $server ?: $_SERVER;
$this->responseBuilder = $responseBuilder ?: ResponseBuilder::create();
$this->requestParser = $requestParser ?: RequestParser::create();
$this->batchRequestParser = $batchRequestParser ?: BatchRequestParser::create();
$this->procedureHandler = $procedureHandler ?: new ProcedureHandler();
$this->middlewareHandler = $middlewareHandler ?: new MiddlewareHandler();
}
/**
* Define alternative authentication header
*
* @access public
* @param string $header Header name
* @return $this
*/
public function setAuthenticationHeader($header)
{
if (! empty($header)) {
$header = 'HTTP_'.str_replace('-', '_', strtoupper($header));
$value = $this->getServerVariable($header);
if (! empty($value)) {
list($this->username, $this->password) = explode(':', base64_decode($value));
}
}
return $this;
}
/**
* Get ProcedureHandler
*
* @access public
* @return ProcedureHandler
*/
public function getProcedureHandler()
{
return $this->procedureHandler;
}
/**
* Get MiddlewareHandler
*
* @access public
* @return MiddlewareHandler
*/
public function getMiddlewareHandler()
{
return $this->middlewareHandler;
}
/**
* Get username
*
* @access public
* @return string
*/
public function getUsername()
{
return $this->username ?: $this->getServerVariable('PHP_AUTH_USER');
}
/**
* Get password
*
* @access public
* @return string
*/
public function getPassword()
{
return $this->password ?: $this->getServerVariable('PHP_AUTH_PW');
}
/**
* IP based client restrictions
*
* @access public
* @param array $hosts List of hosts
* @return $this
*/
public function allowHosts(array $hosts)
{
$this->hosts = $hosts;
return $this;
}
/**
* HTTP Basic authentication
*
* @access public
* @param array $users Dictionary of username/password
* @return $this
*/
public function authentication(array $users)
{
$this->users = $users;
return $this;
}
/**
* Register a new procedure
*
* @access public
* @deprecated Use $server->getProcedureHandler()->withCallback($procedure, $callback)
* @param string $procedure Procedure name
* @param closure $callback Callback
* @return $this
*/
public function register($procedure, Closure $callback)
{
$this->procedureHandler->withCallback($procedure, $callback);
return $this;
}
/**
* Bind a procedure to a class
*
* @access public
* @deprecated Use $server->getProcedureHandler()->withClassAndMethod($procedure, $class, $method);
* @param string $procedure Procedure name
* @param mixed $class Class name or instance
* @param string $method Procedure name
* @return $this
*/
public function bind($procedure, $class, $method = '')
{
$this->procedureHandler->withClassAndMethod($procedure, $class, $method);
return $this;
}
/**
* Bind a class instance
*
* @access public
* @deprecated Use $server->getProcedureHandler()->withObject($instance);
* @param mixed $instance Instance name
* @return $this
*/
public function attach($instance)
{
$this->procedureHandler->withObject($instance);
return $this;
}
/**
* Exception classes that should not be relayed to the client
*
* @access public
* @param Exception|string $exception
* @return $this
*/
public function withLocalException($exception)
{
$this->localExceptions[] = $exception;
return $this;
}
/**
* Parse incoming requests
*
* @access public
* @return string
*/
public function execute()
{
try {
JsonFormatValidator::validate($this->payload);
HostValidator::validate($this->hosts, $this->getServerVariable('REMOTE_ADDR'));
UserValidator::validate($this->users, $this->getUsername(), $this->getPassword());
$this->middlewareHandler
->withUsername($this->getUsername())
->withPassword($this->getPassword())
;
$response = $this->parseRequest();
} catch (Exception $e) {
$response = $this->handleExceptions($e);
}
$this->responseBuilder->sendHeaders();
return $response;
}
/**
* Handle exceptions
*
* @access protected
* @param Exception $e
* @return string
* @throws Exception
*/
protected function handleExceptions(Exception $e)
{
foreach ($this->localExceptions as $exception) {
if ($e instanceof $exception) {
throw $e;
}
}
return $this->responseBuilder->withException($e)->build();
}
/**
* Parse incoming request
*
* @access protected
* @return string
*/
protected function parseRequest()
{
if (BatchRequestParser::isBatchRequest($this->payload)) {
return $this->batchRequestParser
->withPayload($this->payload)
->withProcedureHandler($this->procedureHandler)
->withMiddlewareHandler($this->middlewareHandler)
->withLocalException($this->localExceptions)
->parse();
}
return $this->requestParser
->withPayload($this->payload)
->withProcedureHandler($this->procedureHandler)
->withMiddlewareHandler($this->middlewareHandler)
->withLocalException($this->localExceptions)
->parse();
}
/**
* Check existence and get value of server variable
*
* @access protected
* @param string $variable
* @return string|null
*/
protected function getServerVariable($variable)
{
return isset($this->serverVariable[$variable]) ? $this->serverVariable[$variable] : null;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\AccessDeniedException;
/**
* Class HostValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class HostValidator
{
/**
* Validate
*
* @static
* @access public
* @param array $hosts
* @param string $remoteAddress
* @throws AccessDeniedException
*/
public static function validate(array $hosts, $remoteAddress)
{
if (! empty($hosts) && ! in_array($remoteAddress, $hosts)) {
throw new AccessDeniedException('Access Forbidden');
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\ResponseEncodingFailureException;
/**
* Class JsonEncodingValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class JsonEncodingValidator
{
public static function validate()
{
$jsonError = json_last_error();
if ($jsonError !== JSON_ERROR_NONE) {
switch ($jsonError) {
case JSON_ERROR_DEPTH:
$errorMessage = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$errorMessage = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$errorMessage = 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$errorMessage = 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$errorMessage = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$errorMessage = 'Unknown error';
break;
}
throw new ResponseEncodingFailureException($errorMessage, $jsonError);
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\InvalidJsonFormatException;
/**
* Class JsonFormatValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class JsonFormatValidator
{
/**
* Validate
*
* @static
* @access public
* @param mixed $payload
* @throws InvalidJsonFormatException
*/
public static function validate($payload)
{
if (! is_array($payload)) {
throw new InvalidJsonFormatException('Malformed payload');
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
/**
* Class RpcFormatValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class RpcFormatValidator
{
/**
* Validate
*
* @static
* @access public
* @param array $payload
* @throws InvalidJsonRpcFormatException
*/
public static function validate(array $payload)
{
if (! isset($payload['jsonrpc']) ||
! isset($payload['method']) ||
! is_string($payload['method']) ||
$payload['jsonrpc'] !== '2.0' ||
(isset($payload['params']) && ! is_array($payload['params']))) {
throw new InvalidJsonRpcFormatException('Invalid JSON RPC payload');
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\AuthenticationFailureException;
/**
* Class UserValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class UserValidator
{
public static function validate(array $users, $username, $password)
{
if (! empty($users) && (! isset($users[$username]) || $users[$username] !== $password)) {
throw new AuthenticationFailureException('Access not allowed');
}
}
}

21
vendor/fguillot/picodb/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Frederic Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,86 @@
<?php
namespace PicoDb\Builder;
use PicoDb\Database;
/**
* Class InsertBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
abstract class BaseBuilder
{
/**
* @var Database
*/
protected $db;
/**
* @var ConditionBuilder
*/
protected $conditionBuilder;
/**
* @var string
*/
protected $table = '';
/**
* @var string[]
*/
protected $columns = array();
/**
* InsertBuilder constructor
*
* @param Database $db
* @param ConditionBuilder $condition
*/
public function __construct(Database $db, ConditionBuilder $condition)
{
$this->db = $db;
$this->conditionBuilder = $condition;
}
/**
* Get object instance
*
* @static
* @access public
* @param Database $db
* @param ConditionBuilder $condition
* @return static
*/
public static function getInstance(Database $db, ConditionBuilder $condition)
{
return new static($db, $condition);
}
/**
* Set table name
*
* @access public
* @param string $table
* @return $this
*/
public function withTable($table)
{
$this->table = $table;
return $this;
}
/**
* Set columns name
*
* @access public
* @param string[] $columns
* @return $this
*/
public function withColumns(array $columns)
{
$this->columns = $columns;
return $this;
}
}

View File

@@ -0,0 +1,377 @@
<?php
namespace PicoDb\Builder;
use PicoDb\Database;
use PicoDb\Table;
/**
* Handle SQL conditions
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class ConditionBuilder
{
/**
* Database instance
*
* @access private
* @var Database
*/
private $db;
/**
* Condition values
*
* @access private
* @var array
*/
private $values = array();
/**
* SQL AND conditions
*
* @access private
* @var string[]
*/
private $conditions = array();
/**
* SQL OR conditions
*
* @access private
* @var OrConditionBuilder[]
*/
private $orConditions = array();
/**
* SQL condition offset
*
* @access private
* @var int
*/
private $orConditionOffset = 0;
/**
* Constructor
*
* @access public
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Build the SQL condition
*
* @access public
* @return string
*/
public function build()
{
return empty($this->conditions) ? '' : ' WHERE '.implode(' AND ', $this->conditions);
}
/**
* Get condition values
*
* @access public
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Returns true if there is some conditions
*
* @access public
* @return boolean
*/
public function hasCondition()
{
return ! empty($this->conditions);
}
/**
* Add custom condition
*
* @access public
* @param string $sql
*/
public function addCondition($sql)
{
if ($this->orConditionOffset > 0) {
$this->orConditions[$this->orConditionOffset]->withCondition($sql);
}
else {
$this->conditions[] = $sql;
}
}
/**
* Start OR condition
*
* @access public
*/
public function beginOr()
{
$this->orConditionOffset++;
$this->orConditions[$this->orConditionOffset] = new OrConditionBuilder();
}
/**
* Close OR condition
*
* @access public
*/
public function closeOr()
{
$condition = $this->orConditions[$this->orConditionOffset]->build();
$this->orConditionOffset--;
if ($this->orConditionOffset > 0) {
$this->orConditions[$this->orConditionOffset]->withCondition($condition);
} else {
$this->conditions[] = $condition;
}
}
/**
* Equal condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function eq($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' = ?');
$this->values[] = $value;
}
/**
* Not equal condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function neq($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' != ?');
$this->values[] = $value;
}
/**
* IN condition
*
* @access public
* @param string $column
* @param array $values
*/
public function in($column, array $values)
{
if (! empty($values)) {
$this->addCondition($this->db->escapeIdentifier($column).' IN ('.implode(', ', array_fill(0, count($values), '?')).')');
$this->values = array_merge($this->values, $values);
}
}
/**
* IN condition with a subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function inSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' IN ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* NOT IN condition
*
* @access public
* @param string $column
* @param array $values
*/
public function notIn($column, array $values)
{
if (! empty($values)) {
$this->addCondition($this->db->escapeIdentifier($column).' NOT IN ('.implode(', ', array_fill(0, count($values), '?')).')');
$this->values = array_merge($this->values, $values);
}
}
/**
* NOT IN condition with a subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function notInSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' NOT IN ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* LIKE condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function like($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' '.$this->db->getDriver()->getOperator('LIKE').' ?');
$this->values[] = $value;
}
/**
* ILIKE condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function ilike($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' '.$this->db->getDriver()->getOperator('ILIKE').' ?');
$this->values[] = $value;
}
/**
* Greater than condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function gt($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' > ?');
$this->values[] = $value;
}
/**
* Greater than condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function gtSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' > ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* Lower than condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function lt($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' < ?');
$this->values[] = $value;
}
/**
* Lower than condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function ltSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' < ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* Greater than or equals condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function gte($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' >= ?');
$this->values[] = $value;
}
/**
* Greater than or equal condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function gteSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' >= ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* Lower than or equals condition
*
* @access public
* @param string $column
* @param mixed $value
*/
public function lte($column, $value)
{
$this->addCondition($this->db->escapeIdentifier($column).' <= ?');
$this->values[] = $value;
}
/**
* Lower than or equal condition with subquery
*
* @access public
* @param string $column
* @param Table $subquery
*/
public function lteSubquery($column, Table $subquery)
{
$this->addCondition($this->db->escapeIdentifier($column).' <= ('.$subquery->buildSelectQuery().')');
$this->values = array_merge($this->values, $subquery->getConditionBuilder()->getValues());
}
/**
* IS NULL condition
*
* @access public
* @param string $column
*/
public function isNull($column)
{
$this->addCondition($this->db->escapeIdentifier($column).' IS NULL');
}
/**
* IS NOT NULL condition
*
* @access public
* @param string $column
*/
public function notNull($column)
{
$this->addCondition($this->db->escapeIdentifier($column).' IS NOT NULL');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace PicoDb\Builder;
/**
* Class InsertBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class InsertBuilder extends BaseBuilder
{
/**
* Build SQL
*
* @access public
* @return string
*/
public function build()
{
$columns = array();
$placeholders = array();
foreach ($this->columns as $column) {
$columns[] = $this->db->escapeIdentifier($column);
$placeholders[] = ':'.$column;
}
return sprintf(
'INSERT INTO %s (%s) VALUES (%s)',
$this->db->escapeIdentifier($this->table),
implode(', ', $columns),
implode(', ', $placeholders)
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace PicoDb\Builder;
/**
* Class OrConditionBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class OrConditionBuilder
{
/**
* List of SQL conditions
*
* @access protected
* @var string[]
*/
protected $conditions = array();
/**
* Add new condition
*
* @access public
* @param string $condition
* @return $this
*/
public function withCondition($condition) {
$this->conditions[] = $condition;
return $this;
}
/**
* Build SQL
*
* @access public
* @return string
*/
public function build()
{
return '('.implode(' OR ', $this->conditions).')';
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace PicoDb\Builder;
/**
* Class UpdateBuilder
*
* @package PicoDb\Builder
* @author Frederic Guillot
*/
class UpdateBuilder extends BaseBuilder
{
/**
* @var string[]
*/
protected $sumColumns = array();
/**
* Set columns name
*
* @access public
* @param string[] $columns
* @return $this
*/
public function withSumColumns(array $columns)
{
$this->sumColumns = $columns;
return $this;
}
/**
* Build SQL
*
* @access public
* @return string
*/
public function build()
{
$columns = array();
foreach ($this->columns as $column) {
$columns[] = $this->db->escapeIdentifier($column).'=?';
}
foreach ($this->sumColumns as $column) {
$columns[] = $this->db->escapeIdentifier($column).'='.$this->db->escapeIdentifier($column).' + ?';
}
return sprintf(
'UPDATE %s SET %s %s',
$this->db->escapeIdentifier($this->table),
implode(', ', $columns),
$this->conditionBuilder->build()
);
}
}

View File

@@ -0,0 +1,370 @@
<?php
namespace PicoDb;
use Closure;
use PDOException;
use LogicException;
use PicoDb\Driver\Mssql;
use PicoDb\Driver\Sqlite;
use PicoDb\Driver\Mysql;
use PicoDb\Driver\Postgres;
/**
* Database
*
* @package PicoDb
* @author Frederic Guillot
*/
class Database
{
/**
* Database instances
*
* @static
* @access private
* @var array
*/
private static $instances = array();
/**
* Statement object
*
* @access protected
* @var StatementHandler
*/
protected $statementHandler;
/**
* Queries logs
*
* @access private
* @var array
*/
private $logs = array();
/**
* Driver instance
*
* @access private
*/
private $driver;
/**
* Initialize the driver
*
* @access public
* @param array $settings
*/
public function __construct(array $settings = array())
{
$this->driver = DriverFactory::getDriver($settings);
$this->statementHandler = new StatementHandler($this);
}
/**
* Destructor
*
* @access public
*/
public function __destruct()
{
$this->closeConnection();
}
/**
* Register a new database instance
*
* @static
* @access public
* @param string $name Instance name
* @param Closure $callback Callback
*/
public static function setInstance($name, Closure $callback)
{
self::$instances[$name] = $callback;
}
/**
* Get a database instance
*
* @static
* @access public
* @param string $name Instance name
* @return Database
*/
public static function getInstance($name)
{
if (! isset(self::$instances[$name])) {
throw new LogicException('No database instance created with that name');
}
if (is_callable(self::$instances[$name])) {
self::$instances[$name] = call_user_func(self::$instances[$name]);
}
return self::$instances[$name];
}
/**
* Add a log message
*
* @access public
* @param mixed $message
* @return Database
*/
public function setLogMessage($message)
{
$this->logs[] = is_array($message) ? var_export($message, true) : $message;
return $this;
}
/**
* Add many log messages
*
* @access public
* @param array $messages
* @return Database
*/
public function setLogMessages(array $messages)
{
foreach ($messages as $message) {
$this->setLogMessage($message);
}
return $this;
}
/**
* Get all queries logs
*
* @access public
* @return array
*/
public function getLogMessages()
{
return $this->logs;
}
/**
* Get the PDO connection
*
* @access public
* @return \PDO
*/
public function getConnection()
{
return $this->driver->getConnection();
}
/**
* Get the Driver instance
*
* @access public
* @return Mssql|Sqlite|Postgres|Mysql
*/
public function getDriver()
{
return $this->driver;
}
/**
* Get the last inserted id
*
* @access public
* @return integer
*/
public function getLastId()
{
return (int) $this->driver->getLastId();
}
/**
* Get statement object
*
* @access public
* @return StatementHandler
*/
public function getStatementHandler()
{
return $this->statementHandler;
}
/**
* Release the PDO connection
*
* @access public
*/
public function closeConnection()
{
$this->driver->closeConnection();
}
/**
* Escape an identifier (column, table name...)
*
* @access public
* @param string $value Value
* @param string $table Table name
* @return string
*/
public function escapeIdentifier($value, $table = '')
{
// Do not escape custom query
if (strpos($value, '.') !== false || strpos($value, ' ') !== false) {
return $value;
}
if (! empty($table)) {
return $this->driver->escape($table).'.'.$this->driver->escape($value);
}
return $this->driver->escape($value);
}
/**
* Escape an identifier list
*
* @access public
* @param array $identifiers List of identifiers
* @param string $table Table name
* @return string[]
*/
public function escapeIdentifierList(array $identifiers, $table = '')
{
foreach ($identifiers as $key => $value) {
$identifiers[$key] = $this->escapeIdentifier($value, $table);
}
return $identifiers;
}
/**
* Execute a prepared statement
*
* Note: returns false on duplicate keys instead of SQLException
*
* @access public
* @param string $sql SQL query
* @param array $values Values
* @return \PDOStatement|false
*/
public function execute($sql, array $values = array())
{
return $this->statementHandler
->withSql($sql)
->withPositionalParams($values)
->execute();
}
/**
* Run a transaction
*
* @access public
* @param Closure $callback Callback
* @return mixed
*/
public function transaction(Closure $callback)
{
try {
$this->startTransaction();
$result = $callback($this);
$this->closeTransaction();
return $result === null ? true : $result;
} catch (PDOException $e) {
return $this->statementHandler->handleSqlError($e);
}
}
/**
* Begin a transaction
*
* @access public
*/
public function startTransaction()
{
if (! $this->getConnection()->inTransaction()) {
$this->getConnection()->beginTransaction();
}
}
/**
* Commit a transaction
*
* @access public
*/
public function closeTransaction()
{
if ($this->getConnection()->inTransaction()) {
$this->getConnection()->commit();
}
}
/**
* Rollback a transaction
*
* @access public
*/
public function cancelTransaction()
{
if ($this->getConnection()->inTransaction()) {
$this->getConnection()->rollBack();
}
}
/**
* Get a table object
*
* @access public
* @param string $table
* @return Table
*/
public function table($table)
{
return new Table($this, $table);
}
/**
* Get a hashtable object
*
* @access public
* @param string $table
* @return Hashtable
*/
public function hashtable($table)
{
return new Hashtable($this, $table);
}
/**
* Get a LOB object
*
* @access public
* @param string $table
* @return LargeObject
*/
public function largeObject($table)
{
return new LargeObject($this, $table);
}
/**
* Get a schema object
*
* @access public
* @param string $namespace
* @return Schema
*/
public function schema($namespace = null)
{
$schema = new Schema($this);
if ($namespace !== null) {
$schema->setNamespace($namespace);
}
return $schema;
}
}

View File

@@ -0,0 +1,234 @@
<?php
namespace PicoDb\Driver;
use PDO;
use LogicException;
use PDOException;
/**
* Base Driver class
*
* @package PicoDb\Driver
* @author Frederic Guillot
*/
abstract class Base
{
/**
* List of required settings options
*
* @access protected
* @var array
*/
protected $requiredAttributes = array();
/**
* PDO connection
*
* @access protected
* @var PDO
*/
protected $pdo = null;
/**
* Create a new PDO connection
*
* @abstract
* @access public
* @param array $settings
*/
abstract public function createConnection(array $settings);
/**
* Enable foreign keys
*
* @abstract
* @access public
*/
abstract public function enableForeignKeys();
/**
* Disable foreign keys
*
* @abstract
* @access public
*/
abstract public function disableForeignKeys();
/**
* Return true if the error code is a duplicate key
*
* @abstract
* @access public
* @param integer $code
* @return boolean
*/
abstract public function isDuplicateKeyError($code);
/**
* Escape identifier
*
* @abstract
* @access public
* @param string $identifier
* @return string
*/
abstract public function escape($identifier);
/**
* Get non standard operator
*
* @abstract
* @access public
* @param string $operator
* @return string
*/
abstract public function getOperator($operator);
/**
* Get last inserted id
*
* @abstract
* @access public
* @return integer
*/
abstract public function getLastId();
/**
* Get current schema version
*
* @abstract
* @access public
* @return integer
*/
abstract public function getSchemaVersion();
/**
* Set current schema version
*
* @abstract
* @access public
* @param integer $version
*/
abstract public function setSchemaVersion($version);
/**
* Constructor
*
* @access public
* @param array $settings
*/
public function __construct(array $settings)
{
foreach ($this->requiredAttributes as $attribute) {
if (! isset($settings[$attribute])) {
throw new LogicException('This configuration parameter is missing: "'.$attribute.'"');
}
}
$this->createConnection($settings);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
/**
* Get the PDO connection
*
* @access public
* @return PDO
*/
public function getConnection()
{
return $this->pdo;
}
/**
* Release the PDO connection
*
* @access public
*/
public function closeConnection()
{
$this->pdo = null;
}
/**
* Upsert for a key/value variable
*
* @access public
* @param string $table
* @param string $keyColumn
* @param string $valueColumn
* @param array $dictionary
* @return bool False on failure
*/
public function upsert($table, $keyColumn, $valueColumn, array $dictionary)
{
try {
$this->pdo->beginTransaction();
foreach ($dictionary as $key => $value) {
$rq = $this->pdo->prepare('SELECT 1 FROM '.$this->escape($table).' WHERE '.$this->escape($keyColumn).'=?');
$rq->execute(array($key));
if ($rq->fetchColumn()) {
$rq = $this->pdo->prepare('UPDATE '.$this->escape($table).' SET '.$this->escape($valueColumn).'=? WHERE '.$this->escape($keyColumn).'=?');
$rq->execute(array($value, $key));
}
else {
$rq = $this->pdo->prepare('INSERT INTO '.$this->escape($table).' ('.$this->escape($keyColumn).', '.$this->escape($valueColumn).') VALUES (?, ?)');
$rq->execute(array($key, $value));
}
}
$this->pdo->commit();
return true;
}
catch (PDOException $e) {
$this->pdo->rollBack();
return false;
}
}
/**
* Run EXPLAIN command
*
* @access public
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
return $this->getConnection()->query('EXPLAIN '.$this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Replace placeholder with values in prepared statement
*
* @access protected
* @param string $sql
* @param array $values
* @return string
*/
protected function getSqlFromPreparedStatement($sql, array $values)
{
foreach ($values as $value) {
$sql = substr_replace($sql, "'$value'", strpos($sql, '?'), 1);
}
return $sql;
}
/**
* Get database version
*
* @access public
* @return array
*/
public function getDatabaseVersion()
{
return $this->getConnection()->query('SELECT VERSION()')->fetchColumn();
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace PicoDb\Driver;
use PDO;
/**
* Microsoft SQL Server Driver
*
* @package PicoDb\Driver
* @author Algy Taylor <thomas.taylor@cmft.nhs.uk>
*/
class Mssql extends Base
{
/**
* List of required settings options
*
* @access protected
* @var array
*/
protected $requiredAttributes = array(
'hostname',
'username',
'password',
'database',
);
/**
* Table to store the schema version
*
* @access private
* @var array
*/
private $schemaTable = 'schema_version';
/**
* Create a new PDO connection
*
* @access public
* @param array $settings
*/
public function createConnection(array $settings)
{
$dsn = 'sqlsrv:Server=' . $settings['hostname'] . ';Database=' . $settings['database'];
if (! empty($settings['port'])) {
$dsn .= ';port=' . $settings['port'];
}
$this->pdo = new PDO($dsn, $settings['username'], $settings['password']);
if (isset($settings['schema_table'])) {
$this->schemaTable = $settings['schema_table'];
}
}
/**
* Enable foreign keys
*
* @access public
*/
public function enableForeignKeys()
{
$this->pdo->exec('EXEC sp_MSforeachtable @command1="ALTER TABLE ? CHECK CONSTRAINT ALL"; GO;');
}
/**
* Disable foreign keys
*
* @access public
*/
public function disableForeignKeys()
{
$this->pdo->exec('EXEC sp_MSforeachtable @command1="ALTER TABLE ? NOCHECK CONSTRAINT ALL"; GO;');
}
/**
* Return true if the error code is a duplicate key
*
* @access public
* @param integer $code
* @return boolean
*/
public function isDuplicateKeyError($code)
{
return $code == 2601;
}
/**
* Escape identifier
*
* https://msdn.microsoft.com/en-us/library/ms175874.aspx
*
* @access public
* @param string $identifier
* @return string
*/
public function escape($identifier)
{
return '['.$identifier.']';
}
/**
* Get non standard operator
*
* @access public
* @param string $operator
* @return string
*/
public function getOperator($operator)
{
if ($operator === 'LIKE' || $operator === 'ILIKE') {
return 'LIKE';
}
return '';
}
/**
* Get last inserted id
*
* @access public
* @return integer
*/
public function getLastId()
{
return $this->pdo->lastInsertId();
}
/**
* Get current schema version
*
* @access public
* @return integer
*/
public function getSchemaVersion()
{
$this->pdo->exec("CREATE TABLE IF NOT EXISTS [".$this->schemaTable."] ([version] INT DEFAULT '0')");
$rq = $this->pdo->prepare('SELECT [version] FROM ['.$this->schemaTable.']');
$rq->execute();
$result = $rq->fetchColumn();
if ($result !== false) {
return (int) $result;
}
else {
$this->pdo->exec('INSERT INTO ['.$this->schemaTable.'] VALUES(0)');
}
return 0;
}
/**
* Set current schema version
*
* @access public
* @param integer $version
*/
public function setSchemaVersion($version)
{
$rq = $this->pdo->prepare('UPDATE ['.$this->schemaTable.'] SET [version]=?');
$rq->execute(array($version));
}
/**
* Run EXPLAIN command
*
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
$this->getConnection()->exec('SET SHOWPLAN_ALL ON');
return $this->getConnection()->query($this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
}

View File

@@ -0,0 +1,252 @@
<?php
namespace PicoDb\Driver;
use PDO;
use PDOException;
/**
* Mysql Driver
*
* @package PicoDb\Driver
* @author Frederic Guillot
*/
class Mysql extends Base
{
/**
* List of required settings options
*
* @access protected
* @var array
*/
protected $requiredAttributes = array(
'hostname',
'username',
'password',
'database',
);
/**
* Table to store the schema version
*
* @access private
* @var array
*/
private $schemaTable = 'schema_version';
/**
* Create a new PDO connection
*
* @access public
* @param array $settings
*/
public function createConnection(array $settings)
{
$this->pdo = new PDO(
$this->buildDsn($settings),
$settings['username'],
$settings['password'],
$this->buildOptions($settings)
);
if (isset($settings['schema_table'])) {
$this->schemaTable = $settings['schema_table'];
}
}
/**
* Build connection DSN
*
* @access protected
* @param array $settings
* @return string
*/
protected function buildDsn(array $settings)
{
$charset = empty($settings['charset']) ? 'utf8' : $settings['charset'];
$dsn = 'mysql:host='.$settings['hostname'].';dbname='.$settings['database'].';charset='.$charset;
if (! empty($settings['port'])) {
$dsn .= ';port='.$settings['port'];
}
return $dsn;
}
/**
* Build connection options
*
* @access protected
* @param array $settings
* @return array
*/
protected function buildOptions(array $settings)
{
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET sql_mode = STRICT_ALL_TABLES',
);
if (! empty($settings['ssl_key'])) {
$options[PDO::MYSQL_ATTR_SSL_KEY] = $settings['ssl_key'];
}
if (! empty($settings['ssl_cert'])) {
$options[PDO::MYSQL_ATTR_SSL_CERT] = $settings['ssl_cert'];
}
if (! empty($settings['ssl_ca'])) {
$options[PDO::MYSQL_ATTR_SSL_CA] = $settings['ssl_ca'];
}
return $options;
}
/**
* Enable foreign keys
*
* @access public
*/
public function enableForeignKeys()
{
$this->pdo->exec('SET FOREIGN_KEY_CHECKS=1');
}
/**
* Disable foreign keys
*
* @access public
*/
public function disableForeignKeys()
{
$this->pdo->exec('SET FOREIGN_KEY_CHECKS=0');
}
/**
* Return true if the error code is a duplicate key
*
* @access public
* @param integer $code
* @return boolean
*/
public function isDuplicateKeyError($code)
{
return $code == 23000;
}
/**
* Escape identifier
*
* @access public
* @param string $identifier
* @return string
*/
public function escape($identifier)
{
return '`'.$identifier.'`';
}
/**
* Get non standard operator
*
* @access public
* @param string $operator
* @return string
*/
public function getOperator($operator)
{
if ($operator === 'LIKE') {
return 'LIKE BINARY';
}
else if ($operator === 'ILIKE') {
return 'LIKE';
}
return '';
}
/**
* Get last inserted id
*
* @access public
* @return integer
*/
public function getLastId()
{
return $this->pdo->lastInsertId();
}
/**
* Get current schema version
*
* @access public
* @return integer
*/
public function getSchemaVersion()
{
$this->pdo->exec("CREATE TABLE IF NOT EXISTS `".$this->schemaTable."` (`version` INT DEFAULT '0') ENGINE=InnoDB CHARSET=utf8");
$rq = $this->pdo->prepare('SELECT `version` FROM `'.$this->schemaTable.'`');
$rq->execute();
$result = $rq->fetchColumn();
if ($result !== false) {
return (int) $result;
}
else {
$this->pdo->exec('INSERT INTO `'.$this->schemaTable.'` VALUES(0)');
}
return 0;
}
/**
* Set current schema version
*
* @access public
* @param integer $version
*/
public function setSchemaVersion($version)
{
$rq = $this->pdo->prepare('UPDATE `'.$this->schemaTable.'` SET `version`=?');
$rq->execute(array($version));
}
/**
* Upsert for a key/value variable
*
* @access public
* @param string $table
* @param string $keyColumn
* @param string $valueColumn
* @param array $dictionary
* @return bool False on failure
*/
public function upsert($table, $keyColumn, $valueColumn, array $dictionary)
{
try {
$sql = sprintf(
'REPLACE INTO %s (%s, %s) VALUES %s',
$this->escape($table),
$this->escape($keyColumn),
$this->escape($valueColumn),
implode(', ', array_fill(0, count($dictionary), '(?, ?)'))
);
$values = array();
foreach ($dictionary as $key => $value) {
$values[] = $key;
$values[] = $value;
}
$rq = $this->pdo->prepare($sql);
$rq->execute($values);
return true;
}
catch (PDOException $e) {
return false;
}
}
}

View File

@@ -0,0 +1,196 @@
<?php
namespace PicoDb\Driver;
use PDO;
use PDOException;
/**
* Postgres Driver
*
* @package PicoDb\Driver
* @author Frederic Guillot
*/
class Postgres extends Base
{
/**
* List of required settings options
*
* @access protected
* @var array
*/
protected $requiredAttributes = array(
'hostname',
'username',
'password',
'database',
);
/**
* Table to store the schema version
*
* @access private
* @var array
*/
private $schemaTable = 'schema_version';
/**
* Create a new PDO connection
*
* @access public
* @param array $settings
*/
public function createConnection(array $settings)
{
$dsn = 'pgsql:host='.$settings['hostname'].';dbname='.$settings['database'];
if (! empty($settings['port'])) {
$dsn .= ';port='.$settings['port'];
}
$this->pdo = new PDO($dsn, $settings['username'], $settings['password']);
if (isset($settings['schema_table'])) {
$this->schemaTable = $settings['schema_table'];
}
}
/**
* Enable foreign keys
*
* @access public
*/
public function enableForeignKeys()
{
}
/**
* Disable foreign keys
*
* @access public
*/
public function disableForeignKeys()
{
}
/**
* Return true if the error code is a duplicate key
*
* @access public
* @param integer $code
* @return boolean
*/
public function isDuplicateKeyError($code)
{
return $code == 23505 || $code == 23503;
}
/**
* Escape identifier
*
* @access public
* @param string $identifier
* @return string
*/
public function escape($identifier)
{
return '"'.$identifier.'"';
}
/**
* Get non standard operator
*
* @access public
* @param string $operator
* @return string
*/
public function getOperator($operator)
{
if ($operator === 'LIKE') {
return 'LIKE';
}
else if ($operator === 'ILIKE') {
return 'ILIKE';
}
return '';
}
/**
* Get last inserted id
*
* @access public
* @return integer
*/
public function getLastId()
{
try {
$rq = $this->pdo->prepare('SELECT LASTVAL()');
$rq->execute();
return $rq->fetchColumn();
}
catch (PDOException $e) {
return 0;
}
}
/**
* Get current schema version
*
* @access public
* @return integer
*/
public function getSchemaVersion()
{
$this->pdo->exec("CREATE TABLE IF NOT EXISTS ".$this->schemaTable." (version INTEGER DEFAULT 0)");
$rq = $this->pdo->prepare('SELECT "version" FROM "'.$this->schemaTable.'"');
$rq->execute();
$result = $rq->fetchColumn();
if ($result !== false) {
return (int) $result;
}
else {
$this->pdo->exec('INSERT INTO '.$this->schemaTable.' VALUES(0)');
}
return 0;
}
/**
* Set current schema version
*
* @access public
* @param integer $version
*/
public function setSchemaVersion($version)
{
$rq = $this->pdo->prepare('UPDATE '.$this->schemaTable.' SET version=?');
$rq->execute(array($version));
}
/**
* Run EXPLAIN command
*
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
return $this->getConnection()->query('EXPLAIN (FORMAT YAML) '.$this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get database version
*
* @access public
* @return array
*/
public function getDatabaseVersion()
{
return $this->getConnection()->query('SHOW server_version')->fetchColumn();
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace PicoDb\Driver;
use PDO;
use PDOException;
/**
* Sqlite Driver
*
* @package PicoDb\Driver
* @author Frederic Guillot
*/
class Sqlite extends Base
{
/**
* List of required settings options
*
* @access protected
* @var array
*/
protected $requiredAttributes = array('filename');
/**
* Create a new PDO connection
*
* @access public
* @param array $settings
*/
public function createConnection(array $settings)
{
$this->pdo = new PDO('sqlite:'.$settings['filename']);
$this->enableForeignKeys();
}
/**
* Enable foreign keys
*
* @access public
*/
public function enableForeignKeys()
{
$this->pdo->exec('PRAGMA foreign_keys = ON');
}
/**
* Disable foreign keys
*
* @access public
*/
public function disableForeignKeys()
{
$this->pdo->exec('PRAGMA foreign_keys = OFF');
}
/**
* Return true if the error code is a duplicate key
*
* @access public
* @param integer $code
* @return boolean
*/
public function isDuplicateKeyError($code)
{
return $code == 23000;
}
/**
* Escape identifier
*
* @access public
* @param string $identifier
* @return string
*/
public function escape($identifier)
{
return '"'.$identifier.'"';
}
/**
* Get non standard operator
*
* @access public
* @param string $operator
* @return string
*/
public function getOperator($operator)
{
if ($operator === 'LIKE' || $operator === 'ILIKE') {
return 'LIKE';
}
return '';
}
/**
* Get last inserted id
*
* @access public
* @return integer
*/
public function getLastId()
{
return $this->pdo->lastInsertId();
}
/**
* Get current schema version
*
* @access public
* @return integer
*/
public function getSchemaVersion()
{
$rq = $this->pdo->prepare('PRAGMA user_version');
$rq->execute();
return (int) $rq->fetchColumn();
}
/**
* Set current schema version
*
* @access public
* @param integer $version
*/
public function setSchemaVersion($version)
{
$this->pdo->exec('PRAGMA user_version='.$version);
}
/**
* Upsert for a key/value variable
*
* @access public
* @param string $table
* @param string $keyColumn
* @param string $valueColumn
* @param array $dictionary
* @return bool False on failure
*/
public function upsert($table, $keyColumn, $valueColumn, array $dictionary)
{
try {
$this->pdo->beginTransaction();
foreach ($dictionary as $key => $value) {
$sql = sprintf(
'INSERT OR REPLACE INTO %s (%s, %s) VALUES (?, ?)',
$this->escape($table),
$this->escape($keyColumn),
$this->escape($valueColumn)
);
$rq = $this->pdo->prepare($sql);
$rq->execute(array($key, $value));
}
$this->pdo->commit();
return true;
}
catch (PDOException $e) {
$this->pdo->rollBack();
return false;
}
}
/**
* Run EXPLAIN command
*
* @access public
* @param string $sql
* @param array $values
* @return array
*/
public function explain($sql, array $values)
{
return $this->getConnection()->query('EXPLAIN QUERY PLAN '.$this->getSqlFromPreparedStatement($sql, $values))->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get database version
*
* @access public
* @return array
*/
public function getDatabaseVersion()
{
return $this->getConnection()->query('SELECT sqlite_version()')->fetchColumn();
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace PicoDb;
use LogicException;
use PicoDb\Driver\Mssql;
use PicoDb\Driver\Mysql;
use PicoDb\Driver\Postgres;
use PicoDb\Driver\Sqlite;
/**
* Class DriverFactory
*
* @package PicoDb
* @author Frederic Guillot
*/
class DriverFactory
{
/**
* Get database driver from settings or environment URL
*
* @access public
* @param array $settings
* @return Mssql|Mysql|Postgres|Sqlite
*/
public static function getDriver(array $settings)
{
if (! isset($settings['driver'])) {
throw new LogicException('You must define a database driver');
}
switch ($settings['driver']) {
case 'sqlite':
return new Sqlite($settings);
case 'mssql':
return new Mssql($settings);
case 'mysql':
return new Mysql($settings);
case 'postgres':
return new Postgres($settings);
default:
throw new LogicException('This database driver is not supported');
}
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace PicoDb;
use PDO;
/**
* HashTable (key/value)
*
* @package PicoDb
* @author Frederic Guillot
* @author Mathias Kresin
*/
class Hashtable extends Table
{
/**
* Column for the key
*
* @access private
* @var string
*/
private $keyColumn = 'key';
/**
* Column for the value
*
* @access private
* @var string
*/
private $valueColumn = 'value';
/**
* Set the key column
*
* @access public
* @param string $column
* @return $this
*/
public function columnKey($column)
{
$this->keyColumn = $column;
return $this;
}
/**
* Set the value column
*
* @access public
* @param string $column
* @return $this
*/
public function columnValue($column)
{
$this->valueColumn = $column;
return $this;
}
/**
* Insert or update
*
* @access public
* @param array $hashmap
* @return boolean
*/
public function put(array $hashmap)
{
return $this->db->getDriver()->upsert($this->getName(), $this->keyColumn, $this->valueColumn, $hashmap);
}
/**
* Hashmap result [ [column1 => column2], [], ...]
*
* @access public
* @return array
*/
public function get()
{
$hashmap = array();
// setup where condition
if (func_num_args() > 0) {
$this->in($this->keyColumn, func_get_args());
}
// setup to select columns in case that there are more than two
$this->columns($this->keyColumn, $this->valueColumn);
$rq = $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues());
$rows = $rq->fetchAll(PDO::FETCH_NUM);
foreach ($rows as $row) {
$hashmap[$row[0]] = $row[1];
}
return $hashmap;
}
/**
* Shortcut method to get a hashmap result
*
* @access public
* @param string $key Key column
* @param string $value Value column
* @return array
*/
public function getAll($key, $value)
{
$this->keyColumn = $key;
$this->valueColumn = $value;
return $this->get();
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace PicoDb;
use PDO;
use PicoDb\Builder\InsertBuilder;
use PicoDb\Builder\UpdateBuilder;
/**
* Handle Large Objects (LOBs)
*
* @package PicoDb
* @author Frederic Guillot
*/
class LargeObject extends Table
{
/**
* Fetch large object as file descriptor
*
* This method is not compatible with Sqlite and Mysql (return a string instead of resource)
*
* @access public
* @param string $column
* @return resource
*/
public function findOneColumnAsStream($column)
{
$this->limit(1);
$this->columns($column);
$rq = $this->db->getStatementHandler()
->withSql($this->buildSelectQuery())
->withPositionalParams($this->conditionBuilder->getValues())
->execute();
$rq->bindColumn($column, $fd, PDO::PARAM_LOB);
$rq->fetch(PDO::FETCH_BOUND);
return $fd;
}
/**
* Fetch large object as string
*
* @access public
* @param string $column
* @return string
*/
public function findOneColumnAsString($column)
{
$fd = $this->findOneColumnAsStream($column);
if (is_string($fd)) {
return $fd;
}
return stream_get_contents($fd);
}
/**
* Insert large object from stream
*
* @access public
* @param string $blobColumn
* @param resource|string $blobDescriptor
* @param array $data
* @return bool
*/
public function insertFromStream($blobColumn, &$blobDescriptor, array $data = array())
{
$columns = array_merge(array($blobColumn), array_keys($data));
$this->db->startTransaction();
$result = $this->db->getStatementHandler()
->withSql(InsertBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns($columns)
->build()
)
->withNamedParams($data)
->withLobParam($blobColumn, $blobDescriptor)
->execute();
$this->db->closeTransaction();
return $result !== false;
}
/**
* Insert large object from file
*
* @access public
* @param string $blobColumn
* @param string $filename
* @param array $data
* @return bool
*/
public function insertFromFile($blobColumn, $filename, array $data = array())
{
$fp = fopen($filename, 'rb');
$result = $this->insertFromStream($blobColumn, $fp, $data);
fclose($fp);
return $result;
}
/**
* Insert large object from string
*
* @access public
* @param string $blobColumn
* @param string $blobData
* @param array $data
* @return bool
*/
public function insertFromString($blobColumn, &$blobData, array $data = array())
{
return $this->insertFromStream($blobColumn, $blobData, $data);
}
/**
* Update large object from stream
*
* @access public
* @param string $blobColumn
* @param resource $blobDescriptor
* @param array $data
* @return bool
*/
public function updateFromStream($blobColumn, &$blobDescriptor, array $data = array())
{
$values = array_merge(array_values($data), $this->conditionBuilder->getValues());
$columns = array_merge(array($blobColumn), array_keys($data));
$this->db->startTransaction();
$result = $this->db->getStatementHandler()
->withSql(UpdateBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns($columns)
->build()
)
->withPositionalParams($values)
->withLobParam($blobColumn, $blobDescriptor)
->execute();
$this->db->closeTransaction();
return $result !== false;
}
/**
* Update large object from file
*
* @access public
* @param string $blobColumn
* @param string $filename
* @param array $data
* @return bool
*/
public function updateFromFile($blobColumn, $filename, array $data = array())
{
$fp = fopen($filename, 'r');
$result = $this->updateFromStream($blobColumn, $fp, $data);
fclose($fp);
return $result;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace PicoDb;
use Exception;
/**
* SQLException
*
* @package PicoDb
* @author Frederic Guillot
*/
class SQLException extends Exception
{
}

View File

@@ -0,0 +1,119 @@
<?php
namespace PicoDb;
use PDOException;
/**
* Schema migration class
*
* @package PicoDb
* @author Frederic Guillot
*/
class Schema
{
/**
* Database instance
*
* @access protected
* @var Database
*/
protected $db = null;
/**
* Schema namespace
*
* @access protected
* @var string
*/
protected $namespace = '\Schema';
/**
* Constructor
*
* @access public
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Set another namespace
*
* @access public
* @param string $namespace
* @return Schema
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
return $this;
}
/**
* Get schema namespace
*
* @access public
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Check the schema version and run the migrations
*
* @access public
* @param integer $last_version
* @return boolean
*/
public function check($last_version = 1)
{
$current_version = $this->db->getDriver()->getSchemaVersion();
if ($current_version < $last_version) {
return $this->migrateTo($current_version, $last_version);
}
return true;
}
/**
* Migrate the schema to one version to another
*
* @access public
* @param integer $current_version
* @param integer $next_version
* @return boolean
*/
public function migrateTo($current_version, $next_version)
{
try {
for ($i = $current_version + 1; $i <= $next_version; $i++) {
$this->db->startTransaction();
$this->db->getDriver()->disableForeignKeys();
$function_name = $this->getNamespace().'\version_'.$i;
if (function_exists($function_name)) {
$this->db->setLogMessage('Running migration '.$function_name);
call_user_func($function_name, $this->db->getConnection());
}
$this->db->getDriver()->setSchemaVersion($i);
$this->db->getDriver()->enableForeignKeys();
$this->db->closeTransaction();
}
} catch (PDOException $e) {
$this->db->setLogMessage($e->getMessage());
$this->db->cancelTransaction();
$this->db->getDriver()->enableForeignKeys();
return false;
}
return true;
}
}

View File

@@ -0,0 +1,353 @@
<?php
namespace PicoDb;
use PDO;
use PDOException;
use PDOStatement;
/**
* Statement Handler
*
* @package PicoDb
* @author Frederic Guillot
*/
class StatementHandler
{
/**
* Database instance
*
* @access protected
* @var Database
*/
protected $db = null;
/**
* Flag to calculate query time
*
* @access protected
* @var boolean
*/
protected $stopwatch = false;
/**
* Start time
*
* @access protected
* @var float
*/
protected $startTime = 0;
/**
* Execution time of all queries
*
* @access protected
* @var float
*/
protected $executionTime = 0;
/**
* Flag to log generated SQL queries
*
* @access protected
* @var boolean
*/
protected $logQueries = false;
/**
* Run explain command on each query
*
* @access protected
* @var boolean
*/
protected $explain = false;
/**
* Number of SQL queries executed
*
* @access protected
* @var integer
*/
protected $nbQueries = 0;
/**
* SQL query
*
* @access protected
* @var string
*/
protected $sql = '';
/**
* Positional SQL parameters
*
* @access protected
* @var array
*/
protected $positionalParams = array();
/**
* Named SQL parameters
*
* @access protected
* @var array
*/
protected $namedParams = array();
/**
* Flag to use named params
*
* @access protected
* @var boolean
*/
protected $useNamedParams = false;
/**
* LOB params
*
* @access protected
* @var array
*/
protected $lobParams = array();
/**
* Constructor
*
* @access public
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Enable query logging
*
* @access public
* @return $this
*/
public function withLogging()
{
$this->logQueries = true;
return $this;
}
/**
* Record query execution time
*
* @access public
* @return $this
*/
public function withStopWatch()
{
$this->stopwatch = true;
return $this;
}
/**
* Execute explain command on query
*
* @access public
* @return $this
*/
public function withExplain()
{
$this->explain = true;
return $this;
}
/**
* Set SQL query
*
* @access public
* @param string $sql
* @return $this
*/
public function withSql($sql)
{
$this->sql = $sql;
return $this;
}
/**
* Set positional parameters
*
* @access public
* @param array $params
* @return $this
*/
public function withPositionalParams(array $params)
{
$this->positionalParams = $params;
return $this;
}
/**
* Set named parameters
*
* @access public
* @param array $params
* @return $this
*/
public function withNamedParams(array $params)
{
$this->namedParams = $params;
$this->useNamedParams = true;
return $this;
}
/**
* Bind large object parameter
*
* @access public
* @param $name
* @param $fp
* @return $this
*/
public function withLobParam($name, &$fp)
{
$this->lobParams[$name] =& $fp;
return $this;
}
/**
* Get number of queries executed
*
* @access public
* @return int
*/
public function getNbQueries()
{
return $this->nbQueries;
}
/**
* Execute a prepared statement
*
* Note: returns false on duplicate keys instead of SQLException
*
* @access public
* @return PDOStatement|false
*/
public function execute()
{
try {
$this->beforeExecute();
$pdoStatement = $this->db->getConnection()->prepare($this->sql);
$this->bindParams($pdoStatement);
$pdoStatement->execute();
$this->afterExecute();
return $pdoStatement;
} catch (PDOException $e) {
return $this->handleSqlError($e);
}
}
/**
* Bind parameters to PDOStatement
*
* @access protected
* @param PDOStatement $pdoStatement
*/
protected function bindParams(PDOStatement $pdoStatement)
{
$i = 1;
foreach ($this->lobParams as $name => $variable) {
if (! $this->useNamedParams) {
$parameter = $i;
$i++;
} else {
$parameter = $name;
}
$pdoStatement->bindParam($parameter, $variable, PDO::PARAM_LOB);
}
foreach ($this->positionalParams as $value) {
$pdoStatement->bindValue($i, $value, PDO::PARAM_STR);
$i++;
}
foreach ($this->namedParams as $name => $value) {
$pdoStatement->bindValue($name, $value, PDO::PARAM_STR);
}
}
/**
* Method executed before query execution
*
* @access protected
*/
protected function beforeExecute()
{
if ($this->logQueries) {
$this->db->setLogMessage($this->sql);
}
if ($this->stopwatch) {
$this->startTime = microtime(true);
}
}
/**
* Method executed after query execution
*
* @access protected
*/
protected function afterExecute()
{
if ($this->stopwatch) {
$duration = microtime(true) - $this->startTime;
$this->executionTime += $duration;
$this->db->setLogMessage('query_duration='.$duration);
$this->db->setLogMessage('total_execution_time='.$this->executionTime);
}
if ($this->explain) {
$this->db->setLogMessages($this->db->getDriver()->explain($this->sql, $this->positionalParams));
}
$this->nbQueries++;
$this->cleanup();
}
/**
* Reset internal properties after execution
* The same object instance is used
*
* @access protected
*/
protected function cleanup()
{
$this->sql = '';
$this->useNamedParams = false;
$this->positionalParams = array();
$this->namedParams = array();
$this->lobParams = array();
}
/**
* Handle PDOException
*
* @access public
* @param PDOException $e
* @return bool
* @throws SQLException
*/
public function handleSqlError(PDOException $e)
{
$this->cleanup();
$this->db->cancelTransaction();
$this->db->setLogMessage($e->getMessage());
if ($this->db->getDriver()->isDuplicateKeyError($e->getCode())) {
return false;
}
throw new SQLException('SQL error'.($this->logQueries ? ': '.$e->getMessage() : ''));
}
}

View File

@@ -0,0 +1,721 @@
<?php
namespace PicoDb;
use PDO;
use Closure;
use PicoDb\Builder\ConditionBuilder;
use PicoDb\Builder\InsertBuilder;
use PicoDb\Builder\UpdateBuilder;
/**
* Table
*
* @package PicoDb
* @author Frederic Guillot
*
* @method $this addCondition($sql)
* @method $this beginOr()
* @method $this closeOr()
* @method $this eq($column, $value)
* @method $this neq($column, $value)
* @method $this in($column, array $values)
* @method $this inSubquery($column, Table $subquery)
* @method $this notIn($column, array $values)
* @method $this notInSubquery($column, Table $subquery)
* @method $this like($column, $value)
* @method $this ilike($column, $value)
* @method $this gt($column, $value)
* @method $this gtSubquery($column, Table $subquery)
* @method $this lt($column, $value)
* @method $this ltSubquery($column, Table $subquery)
* @method $this gte($column, $value)
* @method $this gteSubquery($column, Table $subquery)
* @method $this lte($column, $value)
* @method $this lteSubquery($column, Table $subquery)
* @method $this isNull($column)
* @method $this notNull($column)
*/
class Table
{
/**
* Sorting direction
*
* @access public
* @var string
*/
const SORT_ASC = 'ASC';
const SORT_DESC = 'DESC';
/**
* Condition instance
*
* @access protected
* @var ConditionBuilder
*/
protected $conditionBuilder;
/**
* Database instance
*
* @access protected
* @var Database
*/
protected $db;
/**
* Table name
*
* @access protected
* @var string
*/
protected $name = '';
/**
* Columns list for SELECT query
*
* @access private
* @var array
*/
private $columns = array();
/**
* Columns to sum during update
*
* @access private
* @var array
*/
private $sumColumns = array();
/**
* SQL limit
*
* @access private
* @var string
*/
private $sqlLimit = '';
/**
* SQL offset
*
* @access private
* @var string
*/
private $sqlOffset = '';
/**
* SQL order
*
* @access private
* @var string
*/
private $sqlOrder = '';
/**
* SQL custom SELECT value
*
* @access private
* @var string
*/
private $sqlSelect = '';
/**
* SQL joins
*
* @access private
* @var array
*/
private $joins = array();
/**
* Use DISTINCT or not?
*
* @access private
* @var boolean
*/
private $distinct = false;
/**
* Group by those columns
*
* @access private
* @var array
*/
private $groupBy = array();
/**
* Callback for result filtering
*
* @access private
* @var Closure
*/
private $callback = null;
/**
* Constructor
*
* @access public
* @param Database $db
* @param string $name
*/
public function __construct(Database $db, $name)
{
$this->db = $db;
$this->name = $name;
$this->conditionBuilder = new ConditionBuilder($db);
}
/**
* Return the table name
*
* @access public
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Return ConditionBuilder object
*
* @access public
* @return ConditionBuilder
*/
public function getConditionBuilder()
{
return $this->conditionBuilder;
}
/**
* Insert or update
*
* @access public
* @param array $data
* @return boolean
*/
public function save(array $data)
{
return $this->conditionBuilder->hasCondition() ? $this->update($data) : $this->insert($data);
}
/**
* Update
*
* @access public
* @param array $data
* @return boolean
*/
public function update(array $data = array())
{
$values = array_merge(array_values($data), array_values($this->sumColumns), $this->conditionBuilder->getValues());
$sql = UpdateBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns(array_keys($data))
->withSumColumns(array_keys($this->sumColumns))
->build();
return $this->db->execute($sql, $values) !== false;
}
/**
* Insert
*
* @access public
* @param array $data
* @return boolean
*/
public function insert(array $data)
{
return $this->db->getStatementHandler()
->withSql(InsertBuilder::getInstance($this->db, $this->conditionBuilder)
->withTable($this->name)
->withColumns(array_keys($data))
->build()
)
->withNamedParams($data)
->execute() !== false;
}
/**
* Insert a new row and return the ID of the primary key
*
* @access public
* @param array $data
* @return bool|int
*/
public function persist(array $data)
{
if ($this->insert($data)) {
return $this->db->getLastId();
}
return false;
}
/**
* Remove
*
* @access public
* @return boolean
*/
public function remove()
{
$sql = sprintf(
'DELETE FROM %s %s',
$this->db->escapeIdentifier($this->name),
$this->conditionBuilder->build()
);
$result = $this->db->execute($sql, $this->conditionBuilder->getValues());
return $result->rowCount() > 0;
}
/**
* Fetch all rows
*
* @access public
* @return array
*/
public function findAll()
{
$rq = $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues());
$results = $rq->fetchAll(PDO::FETCH_ASSOC);
if (is_callable($this->callback) && ! empty($results)) {
return call_user_func($this->callback, $results);
}
return $results;
}
/**
* Find all with a single column
*
* @access public
* @param string $column
* @return mixed
*/
public function findAllByColumn($column)
{
$this->columns = array($column);
$rq = $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues());
return $rq->fetchAll(PDO::FETCH_COLUMN, 0);
}
/**
* Fetch one row
*
* @access public
* @return array|null
*/
public function findOne()
{
$this->limit(1);
$result = $this->findAll();
return isset($result[0]) ? $result[0] : null;
}
/**
* Fetch one column, first row
*
* @access public
* @param string $column
* @return string
*/
public function findOneColumn($column)
{
$this->limit(1);
$this->columns = array($column);
return $this->db->execute($this->buildSelectQuery(), $this->conditionBuilder->getValues())->fetchColumn();
}
/**
* Build a subquery with an alias
*
* @access public
* @param string $sql
* @param string $alias
* @return $this
*/
public function subquery($sql, $alias)
{
$this->columns[] = '('.$sql.') AS '.$this->db->escapeIdentifier($alias);
return $this;
}
/**
* Exists
*
* @access public
* @return integer
*/
public function exists()
{
$sql = sprintf(
'SELECT 1 FROM %s '.implode(' ', $this->joins).$this->conditionBuilder->build(),
$this->db->escapeIdentifier($this->name)
);
$rq = $this->db->execute($sql, $this->conditionBuilder->getValues());
$result = $rq->fetchColumn();
return $result ? true : false;
}
/**
* Count
*
* @access public
* @return integer
*/
public function count()
{
$sql = sprintf(
'SELECT COUNT(*) FROM %s '.implode(' ', $this->joins).$this->conditionBuilder->build().$this->sqlOrder.$this->sqlLimit.$this->sqlOffset,
$this->db->escapeIdentifier($this->name)
);
$rq = $this->db->execute($sql, $this->conditionBuilder->getValues());
$result = $rq->fetchColumn();
return $result ? (int) $result : 0;
}
/**
* Sum
*
* @access public
* @param string $column
* @return float
*/
public function sum($column)
{
$sql = sprintf(
'SELECT SUM(%s) FROM %s '.implode(' ', $this->joins).$this->conditionBuilder->build().$this->sqlOrder.$this->sqlLimit.$this->sqlOffset,
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($this->name)
);
$rq = $this->db->execute($sql, $this->conditionBuilder->getValues());
$result = $rq->fetchColumn();
return $result ? (float) $result : 0;
}
/**
* Increment column value
*
* @access public
* @param string $column
* @param string $value
* @return boolean
*/
public function increment($column, $value)
{
$sql = sprintf(
'UPDATE %s SET %s=%s+%d '.$this->conditionBuilder->build(),
$this->db->escapeIdentifier($this->name),
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($column),
$value
);
return $this->db->execute($sql, $this->conditionBuilder->getValues()) !== false;
}
/**
* Decrement column value
*
* @access public
* @param string $column
* @param string $value
* @return boolean
*/
public function decrement($column, $value)
{
$sql = sprintf(
'UPDATE %s SET %s=%s-%d '.$this->conditionBuilder->build(),
$this->db->escapeIdentifier($this->name),
$this->db->escapeIdentifier($column),
$this->db->escapeIdentifier($column),
$value
);
return $this->db->execute($sql, $this->conditionBuilder->getValues()) !== false;
}
/**
* Left join
*
* @access public
* @param string $table Join table
* @param string $foreign_column Foreign key on the join table
* @param string $local_column Local column
* @param string $local_table Local table
* @param string $alias Join table alias
* @return $this
*/
public function join($table, $foreign_column, $local_column, $local_table = '', $alias = '')
{
$this->joins[] = sprintf(
'LEFT JOIN %s ON %s=%s',
$this->db->escapeIdentifier($table),
$this->db->escapeIdentifier($alias ?: $table).'.'.$this->db->escapeIdentifier($foreign_column),
$this->db->escapeIdentifier($local_table ?: $this->name).'.'.$this->db->escapeIdentifier($local_column)
);
return $this;
}
/**
* Left join
*
* @access public
* @param string $table1
* @param string $alias1
* @param string $column1
* @param string $table2
* @param string $column2
* @return $this
*/
public function left($table1, $alias1, $column1, $table2, $column2)
{
$this->joins[] = sprintf(
'LEFT JOIN %s AS %s ON %s=%s',
$this->db->escapeIdentifier($table1),
$this->db->escapeIdentifier($alias1),
$this->db->escapeIdentifier($alias1).'.'.$this->db->escapeIdentifier($column1),
$this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2)
);
return $this;
}
/**
* Inner join
*
* @access public
* @param string $table1
* @param string $alias1
* @param string $column1
* @param string $table2
* @param string $column2
* @return $this
*/
public function inner($table1, $alias1, $column1, $table2, $column2)
{
$this->joins[] = sprintf(
'JOIN %s AS %s ON %s=%s',
$this->db->escapeIdentifier($table1),
$this->db->escapeIdentifier($alias1),
$this->db->escapeIdentifier($alias1).'.'.$this->db->escapeIdentifier($column1),
$this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2)
);
return $this;
}
/**
* Order by
*
* @access public
* @param string $column Column name
* @param string $order Direction ASC or DESC
* @return $this
*/
public function orderBy($column, $order = self::SORT_ASC)
{
$order = strtoupper($order);
$order = $order === self::SORT_ASC || $order === self::SORT_DESC ? $order : self::SORT_ASC;
if ($this->sqlOrder === '') {
$this->sqlOrder = ' ORDER BY '.$this->db->escapeIdentifier($column).' '.$order;
}
else {
$this->sqlOrder .= ', '.$this->db->escapeIdentifier($column).' '.$order;
}
return $this;
}
/**
* Ascending sort
*
* @access public
* @param string $column
* @return $this
*/
public function asc($column)
{
$this->orderBy($column, self::SORT_ASC);
return $this;
}
/**
* Descending sort
*
* @access public
* @param string $column
* @return $this
*/
public function desc($column)
{
$this->orderBy($column, self::SORT_DESC);
return $this;
}
/**
* Limit
*
* @access public
* @param integer $value
* @return $this
*/
public function limit($value)
{
if (! is_null($value)) {
$this->sqlLimit = ' LIMIT '.(int) $value;
}
return $this;
}
/**
* Offset
*
* @access public
* @param integer $value
* @return $this
*/
public function offset($value)
{
if (! is_null($value)) {
$this->sqlOffset = ' OFFSET '.(int) $value;
}
return $this;
}
/**
* Group by
*
* @access public
* @return $this
*/
public function groupBy()
{
$this->groupBy = func_get_args();
return $this;
}
/**
* Custom select
*
* @access public
* @param string $select
* @return $this
*/
public function select($select)
{
$this->sqlSelect = $select;
return $this;
}
/**
* Define the columns for the select
*
* @access public
* @return $this
*/
public function columns()
{
$this->columns = func_get_args();
return $this;
}
/**
* Sum column
*
* @access public
* @param string $column
* @param mixed $value
* @return $this
*/
public function sumColumn($column, $value)
{
$this->sumColumns[$column] = $value;
return $this;
}
/**
* Distinct
*
* @access public
* @return $this
*/
public function distinct()
{
$this->columns = func_get_args();
$this->distinct = true;
return $this;
}
/**
* Add callback to alter the resultset
*
* @access public
* @param Closure|array $callback
* @return $this
*/
public function callback($callback)
{
$this->callback = $callback;
return $this;
}
/**
* Build a select query
*
* @access public
* @return string
*/
public function buildSelectQuery()
{
if (empty($this->sqlSelect)) {
$this->columns = $this->db->escapeIdentifierList($this->columns);
$this->sqlSelect = ($this->distinct ? 'DISTINCT ' : '').(empty($this->columns) ? '*' : implode(', ', $this->columns));
}
$this->groupBy = $this->db->escapeIdentifierList($this->groupBy);
return trim(sprintf(
'SELECT %s FROM %s %s %s %s %s %s %s',
$this->sqlSelect,
$this->db->escapeIdentifier($this->name),
implode(' ', $this->joins),
$this->conditionBuilder->build(),
empty($this->groupBy) ? '' : 'GROUP BY '.implode(', ', $this->groupBy),
$this->sqlOrder,
$this->sqlLimit,
$this->sqlOffset
));
}
/**
* Magic method for sql conditions
*
* @access public
* @param string $name
* @param array $arguments
* @return $this
*/
public function __call($name, array $arguments)
{
call_user_func_array(array($this->conditionBuilder, $name), $arguments);
return $this;
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace PicoDb;
/**
* Class UrlParser
*
* @package PicoDb
* @author Frederic Guillot
*/
class UrlParser
{
/**
* URL
*
* @access private
* @var string
*/
private $url;
/**
* Constructor
*
* @access public
* @param string $environmentVariable
*/
public function __construct($environmentVariable = 'DATABASE_URL')
{
$this->url = getenv($environmentVariable);
}
/**
* Get object instance
*
* @access public
* @param string $environmentVariable
* @return static
*/
public static function getInstance($environmentVariable = 'DATABASE_URL')
{
return new static($environmentVariable);
}
/**
* Return true if the variable is defined
*
* @access public
* @return bool
*/
public function isEnvironmentVariableDefined()
{
return ! empty($this->url);
}
/**
* Get settings from URL
*
* @access public
* @param string $url
* @return array
*/
public function getSettings($url = '')
{
$url = $url ?: $this->url;
$components = parse_url($url);
if ($components === false) {
return array();
}
return array(
'driver' => $this->getUrlComponent($components, 'scheme'),
'username' => $this->getUrlComponent($components, 'user'),
'password' => $this->getUrlComponent($components, 'pass'),
'hostname' => $this->getUrlComponent($components, 'host'),
'port' => $this->getUrlComponent($components, 'port'),
'database' => ltrim($this->getUrlComponent($components, 'path'), '/'),
);
}
/**
* Get URL component
*
* @access private
* @param array $components
* @param string $component
* @return mixed|null
*/
private function getUrlComponent(array $components, $component)
{
return ! empty($components[$component]) ? $components[$component] : null;
}
}

21
vendor/fguillot/simple-queue/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Frédéric Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,138 @@
<?php
namespace SimpleQueue\Adapter;
use DateTime;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
use SimpleQueue\Job;
use SimpleQueue\QueueAdapterInterface;
/**
* Class AmqpQueueAdapter
*
* @package SimpleQueue\Adapter
*/
class AmqpQueueAdapter implements QueueAdapterInterface
{
/**
* @var AMQPChannel
*/
protected $channel;
/**
* @var string
*/
protected $exchange = '';
/**
* @var string
*/
protected $queue = '';
/**
* AmqpQueueAdapter constructor.
*
* @param AMQPChannel $channel
* @param string $queue
* @param string $exchange
*/
public function __construct(AMQPChannel $channel, $queue, $exchange)
{
$this->channel = $channel;
$this->exchange = $exchange;
$this->queue = $queue;
}
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job)
{
$message = new AMQPMessage($job->serialize(), array('content_type' => 'text/plain'));
$this->channel->basic_publish($message, $this->exchange);
return $this;
}
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return $this
*/
public function schedule(Job $job, DateTime $dateTime)
{
$now = new DateTime();
$when = clone($dateTime);
$delay = $when->getTimestamp() - $now->getTimestamp();
$message = new AMQPMessage($job->serialize(), array('delivery_mode' => 2));
$message->set('application_headers', new AMQPTable(array('x-delay' => $delay)));
$this->channel->basic_publish($message, $this->exchange);
return $this;
}
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull()
{
$message = null;
$this->channel->basic_consume($this->queue, 'test', false, false, false, false, function ($msg) use (&$message) {
$message = $msg;
$message->delivery_info['channel']->basic_cancel($message->delivery_info['consumer_tag']);
});
while (count($this->channel->callbacks)) {
$this->channel->wait();
}
if ($message === null) {
return null;
}
$job = new Job();
$job->setId($message->get('delivery_tag'));
$job->unserialize($message->getBody());
return $job;
}
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job)
{
$this->channel->basic_ack($job->getId());
return $this;
}
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job)
{
$this->channel->basic_nack($job->getId());
return $this;
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace SimpleQueue\Adapter;
use Aws\Sqs\SqsClient;
use SimpleQueue\Job;
use SimpleQueue\QueueAdapterInterface;
use DateTime;
/**
* Class AwsSqsQueueAdapter
*
* @package SimpleQueue\Adapter
* @author George Webb <george@webb.uno>
*/
class AwsSqsQueueAdapter implements QueueAdapterInterface
{
/**
* @var string
*/
private $queueName;
/**
* @var SqsClient
*/
private $sqsClient;
/**
* @var string
*/
private $sqsUrl;
/**
* @var array
*/
private $config;
/**
* AwsSqsQueueAdapter constructor.
*
* @param string $queueName The name of the SQS queue
* @param SqsClient $sqsClient An SQS client
* @param array $config Array of config values
*/
public function __construct($queueName, SqsClient $sqsClient, $config = array())
{
$this->queueName = $queueName;
$this->sqsClient = $sqsClient;
$this->sqsUrl = $this->sqsClient->getQueueUrl(array('QueueName' => $this->queueName))->get('QueueUrl');
$this->config = $config;
}
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job)
{
$this->sqsClient->sendMessage(array(
'QueueUrl' => $this->sqsUrl,
'MessageBody' => $job->serialize()
));
return $this;
}
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return $this
*/
public function schedule(Job $job, DateTime $dateTime)
{
$now = new DateTime();
$when = clone($dateTime);
$delay = $when->getTimestamp() - $now->getTimestamp();
$this->sqsClient->sendMessage(array(
'QueueUrl' => $this->sqsUrl,
'MessageBody' => $job->serialize(),
'VisibilityTimeout' => $delay
));
return $this;
}
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull()
{
$result = $this->sqsClient->receiveMessage(array(
'QueueUrl' => $this->sqsUrl,
'WaitTimeSeconds' => empty($this->config['LongPollingTime']) ? 0 : (int) $this->config['LongPollingTime']
));
if ($result['Messages'] == null) {
return null;
}
$resultMessage = array_pop($result['Messages']);
$job = new Job();
$job->setId($resultMessage['ReceiptHandle']);
$job->unserialize($resultMessage['Body']);
return $job;
}
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job)
{
$this->sqsClient->deleteMessage(array(
'QueueUrl' => $this->sqsUrl,
'ReceiptHandle' => $job->getId()
));
return $this;
}
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job)
{
$this->sqsClient->changeMessageVisibility(array(
'QueueUrl' => $this->sqsUrl,
'ReceiptHandle' => $job->getId(),
'VisibilityTimeout' => 0
));
return $this;
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace SimpleQueue\Adapter;
use DateTime;
use Pheanstalk\Job as BeanstalkJob;
use Pheanstalk\Pheanstalk;
use Pheanstalk\PheanstalkInterface;
use SimpleQueue\Job;
use SimpleQueue\QueueAdapterInterface;
/**
* Class BeanstalkQueueAdapter
*
* @package SimpleQueue\Adapter
*/
class BeanstalkQueueAdapter implements QueueAdapterInterface
{
/**
* @var PheanstalkInterface
*/
protected $beanstalk;
/**
* @var string
*/
protected $queueName = '';
/**
* BeanstalkQueueAdapter constructor.
*
* @param PheanstalkInterface $beanstalk
* @param string $queueName
*/
public function __construct(PheanstalkInterface $beanstalk, $queueName)
{
$this->beanstalk = $beanstalk;
$this->queueName = $queueName;
}
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job)
{
$this->beanstalk->putInTube($this->queueName, $job->serialize());
return $this;
}
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return $this
*/
public function schedule(Job $job, DateTime $dateTime)
{
$now = new DateTime();
$when = clone($dateTime);
$delay = $when->getTimestamp() - $now->getTimestamp();
$this->beanstalk->putInTube($this->queueName, $job->serialize(), Pheanstalk::DEFAULT_PRIORITY, $delay);
return $this;
}
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull()
{
$beanstalkJob = $this->beanstalk->reserveFromTube($this->queueName);
if ($beanstalkJob === false) {
return null;
}
$job = new Job();
$job->setId($beanstalkJob->getId());
$job->unserialize($beanstalkJob->getData());
return $job;
}
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job)
{
$beanstalkJob = new BeanstalkJob($job->getId(), $job->serialize());
$this->beanstalk->delete($beanstalkJob);
return $this;
}
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job)
{
$beanstalkJob = new BeanstalkJob($job->getId(), $job->serialize());
$this->beanstalk->bury($beanstalkJob);
return $this;
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace SimpleQueue\Adapter;
use DateTime;
use Disque\Client as DisqueClient;
use Disque\Queue\Job as DisqueJob;
use SimpleQueue\Job;
use SimpleQueue\QueueAdapterInterface;
/**
* Class DisqueQueueAdapter
*
* @package SimpleQueue\Adapter
*/
class DisqueQueueAdapter implements QueueAdapterInterface
{
/**
* @var DisqueClient
*/
protected $disque;
/**
* @var string
*/
protected $queueName;
/**
* DisqueQueueAdapter constructor.
*
* @param DisqueClient $disque
* @param string $queueName
*/
public function __construct(DisqueClient $disque, $queueName)
{
$this->disque = $disque;
$this->queueName = $queueName;
}
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job)
{
$this->disque->queue($this->queueName)->push(new DisqueJob($job->getBody()));
return $this;
}
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return $this
*/
public function schedule(Job $job, DateTime $dateTime)
{
$this->disque->queue($this->queueName)->schedule(new DisqueJob($job->serialize()), $dateTime);
return $this;
}
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull()
{
$disqueJob = $this->disque->queue($this->queueName)->pull();
if ($disqueJob === null) {
return null;
}
return new Job($disqueJob->getBody(), $disqueJob->getId());
}
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job)
{
$this->disque->queue($this->queueName)->processed(new DisqueJob($job->getBody(), $job->getId()));
return $this;
}
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job)
{
$this->disque->queue($this->queueName)->failed(new DisqueJob($job->getBody(), $job->getId()));
return $this;
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace SimpleQueue\Adapter;
use DateTime;
use Exception;
use SimpleQueue\Exception\NotSupportedException;
use SimpleQueue\QueueAdapterInterface;
use SimpleQueue\Job;
use SplQueue;
/**
* Class MemoryAdapter
*
* @package SimpleQueue\Adapter
*/
class MemoryQueueAdapter implements QueueAdapterInterface
{
/**
* @var SplQueue
*/
protected $queue;
/**
* MemoryAdapter constructor.
*/
public function __construct()
{
$this->queue = new SplQueue();
}
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job)
{
$this->queue->enqueue($job->serialize());
return $this;
}
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return bool
* @throws NotSupportedException
*/
public function schedule(Job $job, DateTime $dateTime)
{
throw new NotSupportedException('Job delay is not supported by MemoryQueue');
}
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull()
{
try {
$job = new Job();
$payload = $this->queue->dequeue();
return $job->unserialize($payload);
} catch (Exception $e) {
return null;
}
}
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job)
{
return $this;
}
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job)
{
$this->queue->enqueue($job->serialize());
return $this;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace SimpleQueue\Exception;
use Exception;
/**
* Class NotSupportedException
*
* @package SimpleQueue\Exception
*/
class NotSupportedException extends Exception
{
}

View File

@@ -0,0 +1,98 @@
<?php
namespace SimpleQueue;
/**
* Class Job
*
* @package SimpleQueue
*/
class Job
{
protected $id;
protected $body;
/**
* Job constructor.
*
* @param null $body
* @param null $id
*/
public function __construct($body = null, $id = null)
{
$this->body = $body;
$this->id = $id;
}
/**
* Unserialize a payload
*
* @param string $payload
* @return $this
*/
public function unserialize($payload)
{
$this->body = json_decode($payload, true);
return $this;
}
/**
* Serialize the body
*
* @return string
*/
public function serialize()
{
return json_encode($this->body);
}
/**
* Set body
*
* @param mixed $body
* @return Job
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
/**
* Get body
*
* @return mixed
*/
public function getBody()
{
return $this->body;
}
/**
* Set job ID
*
* @param mixed $jobId
* @return Job
*/
public function setId($jobId)
{
$this->id = $jobId;
return $this;
}
/**
* Get job ID
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* Execute job
*/
public function execute()
{
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace SimpleQueue;
use DateTime;
/**
* Class Queue
*
* @package SimpleQueue
*/
class Queue implements QueueAdapterInterface
{
/**
* @var QueueAdapterInterface
*/
protected $queueAdapter;
/**
* Queue constructor.
*
* @param QueueAdapterInterface $queueAdapter
*/
public function __construct(QueueAdapterInterface $queueAdapter)
{
$this->queueAdapter = $queueAdapter;
}
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job)
{
$this->queueAdapter->push($job);
return $this;
}
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return $this
*/
public function schedule(Job $job, DateTime $dateTime)
{
$this->queueAdapter->schedule($job, $dateTime);
return $this;
}
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull()
{
return $this->queueAdapter->pull();
}
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job)
{
$this->queueAdapter->completed($job);
return $this;
}
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job)
{
$this->queueAdapter->failed($job);
return $this;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace SimpleQueue;
use DateTime;
/**
* Interface AdapterInterface
*
* @package SimpleQueue\Adapter
*/
interface QueueAdapterInterface
{
/**
* Send a job
*
* @access public
* @param Job $job
* @return $this
*/
public function push(Job $job);
/**
* Schedule a job in the future
*
* @access public
* @param Job $job
* @param DateTime $dateTime
* @return $this
*/
public function schedule(Job $job, DateTime $dateTime);
/**
* Wait and get job from a queue
*
* @access public
* @return Job|null
*/
public function pull();
/**
* Acknowledge a job
*
* @access public
* @param Job $job
* @return $this
*/
public function completed(Job $job);
/**
* Mark a job as failed
*
* @access public
* @param Job $job
* @return $this
*/
public function failed(Job $job);
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Frederic Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,44 @@
<?php
namespace SimpleValidator;
class Validator
{
private $data = array();
private $errors = array();
private $validators = array();
public function __construct(array $data, array $validators)
{
$this->data = $data;
$this->validators = $validators;
}
public function execute()
{
$result = true;
foreach ($this->validators as $validator) {
if (! $validator->execute($this->data)) {
$this->addError($validator->getField(), $validator->getErrorMessage());
$result = false;
}
}
return $result;
}
public function addError($field, $message)
{
if (! isset($this->errors[$field])) {
$this->errors[$field] = array();
}
$this->errors[$field][] = $message;
}
public function getErrors()
{
return $this->errors;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace SimpleValidator\Validators;
class Alpha extends Base
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return ctype_alpha($data[$this->field]);
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace SimpleValidator\Validators;
class AlphaNumeric extends Base
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return ctype_alnum($data[$this->field]);
}
return true;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace SimpleValidator\Validators;
abstract class Base
{
protected $field = '';
protected $error_message = '';
protected $data = array();
abstract public function execute(array $data);
public function __construct($field, $error_message)
{
$this->field = $field;
$this->error_message = $error_message;
}
public function getErrorMessage()
{
return $this->error_message;
}
public function getField()
{
if (is_array($this->field)) {
return $this->field[0];
}
return $this->field;
}
public function isFieldNotEmpty(array $data)
{
return isset($data[$this->field]) && $data[$this->field] !== '';
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace SimpleValidator\Validators;
use DateTime;
class Date extends Base
{
private $formats = array();
public function __construct($field, $error_message, array $formats)
{
parent::__construct($field, $error_message);
$this->formats = $formats;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
foreach ($this->formats as $format) {
if ($this->isValidDate($data[$this->field], $format)) {
return true;
}
}
return false;
}
return true;
}
public function isValidDate($value, $format)
{
$date = DateTime::createFromFormat($format, $value);
if ($date !== false) {
$errors = DateTime::getLastErrors();
if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
return $date->getTimestamp() > 0;
}
}
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace SimpleValidator\Validators;
class Email extends Base
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
// I use the same validation method as Firefox
// http://hg.mozilla.org/mozilla-central/file/cf5da681d577/content/html/content/src/nsHTMLInputElement.cpp#l3967
$value = $data[$this->field];
$length = strlen($value);
// If the email address begins with a '@' or ends with a '.',
// we know it's invalid.
if ($value[0] === '@' || $value[$length - 1] === '.') {
return false;
}
// Check the username
for ($i = 0; $i < $length && $value[$i] !== '@'; ++$i) {
$c = $value[$i];
if (! (ctype_alnum($c) || $c === '.' || $c === '!' || $c === '#' || $c === '$' ||
$c === '%' || $c === '&' || $c === '\'' || $c === '*' || $c === '+' ||
$c === '-' || $c === '/' || $c === '=' || $c === '?' || $c === '^' ||
$c === '_' || $c === '`' || $c === '{' || $c === '|' || $c === '}' ||
$c === '~')) {
return false;
}
}
// There is no domain name (or it's one-character long),
// that's not a valid email address.
if (++$i >= $length) return false;
if (($i + 1) === $length) return false;
// The domain name can't begin with a dot.
if ($value[$i] === '.') return false;
// Parsing the domain name.
for (; $i < $length; ++$i) {
$c = $value[$i];
if ($c === '.') {
// A dot can't follow a dot.
if ($value[$i - 1] === '.') return false;
}
elseif (! (ctype_alnum($c) || $c === '-')) {
// The domain characters have to be in this list to be valid.
return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace SimpleValidator\Validators;
class Equals extends Base
{
private $field2;
public function __construct($field1, $field2, $error_message)
{
parent::__construct($field1, $error_message);
$this->field2 = $field2;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
if (! isset($data[$this->field2])) {
return false;
}
return $data[$this->field] === $data[$this->field2];
}
return true;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace SimpleValidator\Validators;
use PDO;
class Exists extends Base
{
private $pdo;
private $key;
private $table;
public function __construct($field, $error_message, PDO $pdo, $table, $key = '')
{
parent::__construct($field, $error_message);
$this->pdo = $pdo;
$this->table = $table;
$this->key = $key;
}
public function execute(array $data)
{
if (! $this->isFieldNotEmpty($data)) {
return true;
}
if ($this->key === '') {
$this->key = $this->field;
}
$rq = $this->pdo->prepare('SELECT 1 FROM '.$this->table.' WHERE '.$this->key.'=?');
$rq->execute(array($data[$this->field]));
return $rq->fetchColumn() == 1;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace SimpleValidator\Validators;
class GreaterThan extends Base
{
private $min;
public function __construct($field, $error_message, $min)
{
parent::__construct($field, $error_message);
$this->min = $min;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return $data[$this->field] > $this->min;
}
return true;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace SimpleValidator\Validators;
class InArray extends Base
{
protected $array;
public function __construct($field, array $array, $error_message)
{
parent::__construct($field, $error_message);
$this->array = $array;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return in_array($data[$this->field], $this->array);
}
return true;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace SimpleValidator\Validators;
class Integer extends Base
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
if (is_string($data[$this->field])) {
if ($data[$this->field][0] === '-') {
return ctype_digit(substr($data[$this->field], 1));
}
return ctype_digit($data[$this->field]);
}
else {
return is_int($data[$this->field]);
}
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace SimpleValidator\Validators;
class Ip extends Base
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return filter_var($data[$this->field], FILTER_VALIDATE_IP) !== false;
}
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace SimpleValidator\Validators;
class Length extends Base
{
private $min;
private $max;
public function __construct($field, $error_message, $min, $max)
{
parent::__construct($field, $error_message);
$this->min = $min;
$this->max = $max;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
$length = mb_strlen($data[$this->field], 'UTF-8');
return $length >= $this->min && $length <= $this->max;
}
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace SimpleValidator\Validators;
class MaxLength extends Base
{
private $max;
public function __construct($field, $error_message, $max)
{
parent::__construct($field, $error_message);
$this->max = $max;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
$length = mb_strlen($data[$this->field], 'UTF-8');
return $length <= $this->max;
}
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace SimpleValidator\Validators;
class MinLength extends Base
{
private $min;
public function __construct($field, $error_message, $min)
{
parent::__construct($field, $error_message);
$this->min = $min;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
$length = mb_strlen($data[$this->field], 'UTF-8');
return $length >= $this->min;
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace SimpleValidator\Validators;
class NotEmpty extends Base
{
public function execute(array $data)
{
if (array_key_exists($this->field, $data)) {
return $data[$this->field] !== null && $data[$this->field] !== '';
}
return true;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace SimpleValidator\Validators;
class NotEquals extends Base
{
private $field2;
public function __construct($field1, $field2, $error_message)
{
parent::__construct($field1, $error_message);
$this->field2 = $field2;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
if (! isset($data[$this->field2])) {
return true;
}
return $data[$this->field] !== $data[$this->field2];
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace SimpleValidator\Validators;
class NotInArray extends InArray
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return ! in_array($data[$this->field], $this->array);
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace SimpleValidator\Validators;
class Numeric extends Base
{
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
return is_numeric($data[$this->field]);
}
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace SimpleValidator\Validators;
class Range extends Base
{
private $min;
private $max;
public function __construct($field, $error_message, $min, $max)
{
parent::__construct($field, $error_message);
$this->min = $min;
$this->max = $max;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
if (! is_numeric($data[$this->field])) {
return false;
}
if ($data[$this->field] < $this->min || $data[$this->field] > $this->max) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace SimpleValidator\Validators;
class Required extends Base
{
public function execute(array $data)
{
return $this->isFieldNotEmpty($data);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace SimpleValidator\Validators;
use PDO;
class Unique extends Base
{
private $pdo;
private $primary_key;
private $table;
public function __construct($field, $error_message, PDO $pdo, $table, $primary_key = 'id')
{
parent::__construct($field, $error_message);
$this->pdo = $pdo;
$this->primary_key = $primary_key;
$this->table = $table;
}
public function execute(array $data)
{
if ($this->isFieldNotEmpty($data)) {
if (! isset($data[$this->primary_key])) {
$rq = $this->pdo->prepare('SELECT 1 FROM '.$this->table.' WHERE '.$this->field.'=?');
$rq->execute(array($data[$this->field]));
}
else {
$rq = $this->pdo->prepare(
'SELECT 1 FROM '.$this->table.'
WHERE '.$this->field.'=? AND '.$this->primary_key.' != ?'
);
$rq->execute(array($data[$this->field], $data[$this->primary_key]));
}
$result = $rq->fetchColumn();
if ($result == 1) { // Postgresql returns an integer but other database returns a string '1'
return false;
}
}
return true;
}
}

21
vendor/fguillot/simpleLogger/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Frederic Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,89 @@
<?php
namespace SimpleLogger;
use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;
/**
* Base class for loggers
*
* @package SimpleLogger
* @author Frédéric Guillot
*/
abstract class Base extends AbstractLogger
{
/**
* Minimum log level for the logger
*
* @access private
* @var string
*/
private $level = LogLevel::DEBUG;
/**
* Set minimum log level
*
* @access public
* @param string $level
*/
public function setLevel($level)
{
$this->level = $level;
}
/**
* Get minimum log level
*
* @access public
* @return string
*/
public function getLevel()
{
return $this->level;
}
/**
* Dump to log a variable (by example an array)
*
* @param mixed $variable
*/
public function dump($variable)
{
$this->log(LogLevel::DEBUG, var_export($variable, true));
}
/**
* Interpolates context values into the message placeholders.
*
* @access protected
* @param string $message
* @param array $context
* @return string
*/
protected function interpolate($message, array $context = array())
{
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
$replace['{' . $key . '}'] = $val;
}
// interpolate replacement values into the message and return
return strtr($message, $replace);
}
/**
* Format log message
*
* @param mixed $level
* @param string $message
* @param array $context
* @return string
*/
protected function formatMessage($level, $message, array $context = array())
{
return '['.date('Y-m-d H:i:s').'] ['.$level.'] '.$this->interpolate($message, $context).PHP_EOL;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace SimpleLogger;
use RuntimeException;
/**
* File logger
*
* @package SimpleLogger
* @author Frédéric Guillot
*/
class File extends Base
{
/**
* Filename
*
* @access protected
* @var string
*/
protected $filename = '';
/**
* Setup logger configuration
*
* @param string $filename Output file
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*/
public function log($level, $message, array $context = array())
{
$line = $this->formatMessage($level, $message, $context);
if (file_put_contents($this->filename, $line, FILE_APPEND | LOCK_EX) === false) {
throw new RuntimeException('Unable to write to the log file.');
}
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace SimpleLogger;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Handler for multiple loggers
*
* @package SimpleLogger
* @author Frédéric Guillot
*/
class Logger extends AbstractLogger implements LoggerAwareInterface
{
/**
* Logger instances
*
* @access private
*/
private $loggers = array();
/**
* Get level priority
*
* @param mixed $level
* @return integer
*/
public function getLevelPriority($level)
{
switch ($level) {
case LogLevel::EMERGENCY:
return 600;
case LogLevel::ALERT:
return 550;
case LogLevel::CRITICAL:
return 500;
case LogLevel::ERROR:
return 400;
case LogLevel::WARNING:
return 300;
case LogLevel::NOTICE:
return 250;
case LogLevel::INFO:
return 200;
}
return 100;
}
/**
* Sets a logger instance on the object
*
* @param LoggerInterface $logger
* @return null
*/
public function setLogger(LoggerInterface $logger)
{
$this->loggers[] = $logger;
}
/**
* Proxy method to the real loggers
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array())
{
foreach ($this->loggers as $logger) {
if ($this->getLevelPriority($level) >= $this->getLevelPriority($logger->getLevel())) {
$logger->log($level, $message, $context);
}
}
}
/**
* Dump variables for debugging
*
* @param mixed $variable
*/
public function dump($variable)
{
foreach ($this->loggers as $logger) {
if ($this->getLevelPriority(LogLevel::DEBUG) >= $this->getLevelPriority($logger->getLevel())) {
$logger->dump($variable);
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace SimpleLogger;
/**
* Stderr logger
*
* @package SimpleLogger
* @author Frédéric Guillot
*/
class Stderr extends Base
{
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array())
{
file_put_contents('php://stderr', $this->formatMessage($level, $message, $context), FILE_APPEND);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace SimpleLogger;
/**
* Stdout logger
*
* @package SimpleLogger
* @author Frédéric Guillot
*/
class Stdout extends Base
{
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array())
{
file_put_contents('php://stdout', $this->formatMessage($level, $message, $context), FILE_APPEND);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace SimpleLogger;
use RuntimeException;
use Psr\Log\LogLevel;
/**
* Syslog Logger
*
* @package SimpleLogger
* @author Frédéric Guillot
*/
class Syslog extends Base
{
/**
* Setup Syslog configuration
*
* @param string $ident Application name
* @param int $facility See http://php.net/manual/en/function.openlog.php
*/
public function __construct($ident = 'PHP', $facility = LOG_USER)
{
if (! openlog($ident, LOG_ODELAY | LOG_PID, $facility)) {
throw new RuntimeException('Unable to connect to syslog.');
}
}
/**
* Get syslog priority according to Psr\LogLevel
*
* @param mixed $level
* @return integer
*/
public function getSyslogPriority($level)
{
switch ($level) {
case LogLevel::EMERGENCY:
return LOG_EMERG;
case LogLevel::ALERT:
return LOG_ALERT;
case LogLevel::CRITICAL:
return LOG_CRIT;
case LogLevel::ERROR:
return LOG_ERR;
case LogLevel::WARNING:
return LOG_WARNING;
case LogLevel::NOTICE:
return LOG_NOTICE;
case LogLevel::INFO:
return LOG_INFO;
}
return LOG_DEBUG;
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array())
{
$syslogPriority = $this->getSyslogPriority($level);
$syslogMessage = $this->interpolate($message, $context);
syslog($syslogPriority, $syslogMessage);
}
}