private function getUri(array $parameters = array())
    {
        if ($this->apiKey) {
            $parameters = array_merge($parameters, array('key' => $this->apiKey));
        }

        if (0 === count($parameters)) {
            return;
        }

        return sprintf('?%s', http_build_query($parameters));
    }

private function getUri ( array $ parameters = array ( ) ) { if ( $ this -> apiKey ) { $ parameters = array_merge ( $ parameters , array ( 'key' => $ this -> apiKey ) ) ; } if ( 0 === count ( $ parameters ) ) { return ; } return sprintf ( '?%s' , http_build_query ( $ parameters ) ) ; }

Gets the URI.

@param array $parameters An array of parameters, optional

@return null|string


private function validate($apiRawResponse)
    {
        $response = json_decode($apiRawResponse);

        if (null === $response) {
            throw new InvalidApiResponseException('Wechat response is probably mal-formed because cannot be json-decoded.');
        }

        if (!property_exists($response, 'errcode')) {
            throw new InvalidApiResponseException('Property "errcode" does not exist within Wechat response.');
        }

        if (0 !== $response->errcode) {
            throw new InvalidApiResponseException(sprintf('Wechat returned status code "%s" with message "%s"',
                $response->errcode,
                property_exists($response, 'errmsg') ? $response->errmsg : ''
            ));
        }

        return $response;
    }

Validates the Wechat's response and returns it whether the status code is 200.

@param string $apiRawResponse

@return object

@throws InvalidApiResponseException

Validates the Wechat s response and returns it whether the status code is 200 .


private function validate($apiRawResponse)
    {
        $response = json_decode($apiRawResponse);

        if (null === $response) {
            throw new InvalidApiResponseException('Bit.ly response is probably mal-formed because cannot be json-decoded.');
        }

        if (!property_exists($response, 'status_code')) {
            throw new InvalidApiResponseException('Property "status_code" does not exist within Bit.ly response.');
        }

        if (200 !== $response->status_code) {
            throw new InvalidApiResponseException(sprintf('Bit.ly returned status code "%s" with message "%s"',
                $response->status_code,
                property_exists($response, 'status_txt') ? $response->status_txt : ''
            ));
        }

        return $response;
    }

Validates the Bit.ly's response and returns it whether the status code is 200.

@param string $apiRawResponse

@return object

@throws InvalidApiResponseException

Validates the Bit . ly s response and returns it whether the status code is 200 .


private function validate($apiRawResponse)
    {
        $response = json_decode($apiRawResponse);

        if (null === $response) {
            throw new InvalidApiResponseException('Sina response is probably mal-formed because cannot be json-decoded.');
        }

        if (property_exists($response, 'error')) {
            throw new InvalidApiResponseException(sprintf(
                'Sina returned status code "%s" with message "%s".',
                property_exists($response, 'error_code') ? $response->error_code : '',
                property_exists($response, 'error') ? $response->error : ''
            ));
        }

        if (property_exists($response, 'urls')) {
            if (empty($response->urls[0]->url_long)) {
                throw new InvalidApiResponseException('Property "longUrl" does not exist within Sina response.');
            }
        }

        return $response;
    }

Validates the Sina's response and returns it whether the status code is 200.

@param string $apiRawResponse An API response, as it returned

@return object

@throws InvalidApiResponseException

Validates the Sina s response and returns it whether the status code is 200 .


public function getProvider($name)
    {
        if (!$this->hasProvider($name)) {
            throw new \RuntimeException(sprintf('Unable to retrieve the provider named: "%s"', $name));
        }

        return $this->providers[$name];
    }

Gets a provider by name.

@param string $name A provider name

@return UrlShortenerProviderInterface

@throws \RuntimeException If the provider does not exist


public function handle(Request $request, Closure $next)
    {
        // If this was not a get or head request, just return
        if (!$request->isMethod('get') && !$request->isMethod('head')) {
            return $next($request);
        }

        // Get the initial method sent by client
        $initialMethod = $request->method();

        // Force to get in order to receive content
        $request->setMethod('get');

        // Get response
        $response = $next($request);

        // Generate Etag
        $etag = md5(json_encode($response->headers->get('origin')).$response->getContent());

        // Load the Etag sent by client
        $requestEtag = str_replace('"', '', $request->getETags());

        // Check to see if Etag has changed
        if ($requestEtag && $requestEtag[0] == $etag) {
            $response->setNotModified();
        }

        // Set Etag
        $response->setEtag($etag);

        // Set back to original method
        $request->setMethod($initialMethod); // set back to original method

        // Send response
        return $response;
    }

Implement Etag support.

@param \Illuminate\Http\Request $request The HTTP request.
@param \Closure                 $next    Closure for the response.

@return mixed


public function getDetails($ip_address = null)
    {
        $response_details = $this->getRequestDetails((string) $ip_address);

        return $this->formatDetailsObject($response_details);
    }

public function getDetails ( $ ip_address = null ) { $ response_details = $ this -> getRequestDetails ( ( string ) $ ip_address ) ; return $ this -> formatDetailsObject ( $ response_details ) ; }

Get formatted details for an IP address.
@param  string|null $ip_address IP address to look up.
@return Details Formatted IPinfo data.
@throws IPinfoException

Get formatted details for an IP address .


public function formatDetailsObject($details = [])
    {
      $country = $details['country'] ?? null;
      $details['country_name'] = $this->countries[$country] ?? null;

      if (array_key_exists('loc', $details)) {
        $coords = explode(',', $details['loc']);
        $details['latitude'] = $coords[0];
        $details['longitude'] = $coords[1];
      } else {
        $details['latitude'] = null;
        $details['longitude'] = null;
      }

      return new Details($details);
    }

public function formatDetailsObject ( $ details = [ ] ) { $ country = $ details [ 'country' ] ?? null ; $ details [ 'country_name' ] = $ this -> countries [ $ country ] ?? null ; if ( array_key_exists ( 'loc' , $ details ) ) { $ coords = explode ( ',' , $ details [ 'loc' ] ) ; $ details [ 'latitude' ] = $ coords [ 0 ] ; $ details [ 'longitude' ] = $ coords [ 1 ] ; } else { $ details [ 'latitude' ] = null ; $ details [ 'longitude' ] = null ; } return new Details ( $ details ) ; }

Format details and return as an object.
@param  array  $details IP address details.
@return Details Formatted IPinfo Details object.

Format details and return as an object .


public function getRequestDetails(string $ip_address)
    {
      if (!$this->cache->has($ip_address)) {
        $url = self::API_URL;
        if ($ip_address) {
          $url .= "/$ip_address";
        }

        try {
          $response = $this->http_client->request(
            self::REQUEST_TYPE_GET,
            $url,
            $this->buildHeaders()
          );
        } catch (GuzzleException $e) {
          throw new IPinfoException($e->getMessage());
        } catch (Exception $e) {
          throw new IPinfoException($e->getMessage());
        }

        if ($response->getStatusCode() == self::STATUS_CODE_QUOTA_EXCEEDED) {
          throw new IPinfoException('IPinfo request quota exceeded.');
        } elseif ($response->getStatusCode() >= 400) {
          throw new IPinfoException('Exception: ' . json_encode([
            'status' => $response->getStatusCode(),
            'reason' => $response->getReasonPhrase(),
          ]));
        }

        $raw_details = json_decode($response->getBody(), true);
        $this->cache->set($ip_address, $raw_details);
      }

      return $this->cache->get($ip_address);
    }

public
function
getRequestDetails
(
string
$
ip_address
)
{
if
(
!
$
this
->
cache
->
has
(
$
ip_address
)
)
{
$
url
=
self
::
API_URL
;
if
(
$
ip_address
)
{
$
url
.=
"/$ip_address"
;
}
try
{
$
response
=
$
this
->
http_client
->
request
(
self
::
REQUEST_TYPE_GET
,
$
url
,
$
this
->
buildHeaders
(
)
)
;
}
catch
(
GuzzleException
$
e
)
{
throw
new
IPinfoException
(
$
e
->
getMessage
(
)
)
;
}
catch
(
Exception
$
e
)
{
throw
new
IPinfoException
(
$
e
->
getMessage
(
)
)
;
}
if
(
$
response
->
getStatusCode
(
)
==
self
::
STATUS_CODE_QUOTA_EXCEEDED
)
{
throw
new
IPinfoException
(
'IPinfo request quota exceeded.'
)
;
}
elseif
(
$
response
->
getStatusCode
(
)
>=
400
)
{
throw
new
IPinfoException
(
'Exception: '
.
json_encode
(
[
'status'
=>
$
response
->
getStatusCode
(
)
,
'reason'
=>
$
response
->
getReasonPhrase
(
)
,
]
)
)
;
}
$
raw_details
=
json_decode
(
$
response
->
getBody
(
)
,
true
)
;
$
this
->
cache
->
set
(
$
ip_address
,
$
raw_details
)
;
}
return
$
this
->
cache
->
get
(
$
ip_address
)
;
}

Get details for a specific IP address.
@param  string $ip_address IP address to query API for.
@return array IP response data.
@throws IPinfoException


public function set(string $name, $value)
  {
      if (!$this->cache->has($name)) {
        $this->element_queue[] = $name;
      }

      $this->cache->set($name, $value, $this->ttl);

      $this->manageSize();
  }

public function set ( string $ name , $ value ) { if ( ! $ this -> cache -> has ( $ name ) ) { $ this -> element_queue [ ] = $ name ; } $ this -> cache -> set ( $ name , $ value , $ this -> ttl ) ; $ this -> manageSize ( ) ; }

Set the IP address key to the specified value.
@param string $ip_address IP address to cache data for.
@param mixed $value Data for specified IP address.

Set the IP address key to the specified value .


private function manageSize()
  {
    $overflow = count($this->element_queue) - $this->maxsize;
    if ($overflow > 0) {
        foreach (array_slice($this->element_queue, 0, $overflow) as $name) {
          if ($this->cache->has($name)) {
            $this->cache->delete($name);
          }
        }
        $this->element_queue = array_slice($this->element_queue, $overflow);
    }
  }

private function manageSize ( ) { $ overflow = count ( $ this -> element_queue ) - $ this -> maxsize ; if ( $ overflow > 0 ) { foreach ( array_slice ( $ this -> element_queue , 0 , $ overflow ) as $ name ) { if ( $ this -> cache -> has ( $ name ) ) { $ this -> cache -> delete ( $ name ) ; } } $ this -> element_queue = array_slice ( $ this -> element_queue , $ overflow ) ; } }

If cache maxsize has been reached, remove oldest elements until limit is reached.

If cache maxsize has been reached remove oldest elements until limit is reached .


public function post($params = array(), $headers = array())
	{
		if (empty($params)) {
			return null;
		}

		$this->httpAction->setApiParams($params);

		$this->httpAction->setApiHeaders(array_merge(array($this->getApiBaseHeader()), $headers));

		return (array) $this->preparePropertiesFromResponse(
			$this->httpAction->post(
				$this->getApiUrl()
			)
		);
	}

public function post ( $ params = array ( ) , $ headers = array ( ) ) { if ( empty ( $ params ) ) { return null ; } $ this -> httpAction -> setApiParams ( $ params ) ; $ this -> httpAction -> setApiHeaders ( array_merge ( array ( $ this -> getApiBaseHeader ( ) ) , $ headers ) ) ; return ( array ) $ this -> preparePropertiesFromResponse ( $ this -> httpAction -> post ( $ this -> getApiUrl ( ) ) ) ; }

Make post request

@param array $params
@param array $headers

@return array


public static function errorHtml($e, $header, $debug = TRUE)
    {
        $pattern = [
            '/\{\{title\}\}/',
            '/\{\{header\}\}/',
            '/\{\{exception\}\}/',
        ];

        $title = $header;
        $exception = $debug ? $e : 'something error...';
        $replacement = [$title, $header, $exception];

        return preg_replace($pattern, $replacement, self::$_html_blade);
    }

public
static
function
errorHtml
(
$
e
,
$
header
,
$
debug
=
TRUE
)
{
$
pattern
=
[
'/\{\{title\}\}/'
,
'/\{\{header\}\}/'
,
'/\{\{exception\}\}/'
,
]
;
$
title
=
$
header
;
$
exception
=
$
debug
$
e
:
'something error...'
;
$
replacement
=
[
$
title
,
$
header
,
$
exception
]
;
return
preg_replace
(
$
pattern
,
$
replacement
,
self
::
$
_html_blade
)
;
}

return error html.

@param  mixed $e
@param  int $header
@param  boolean $debug
@return string


public static function group(array $filter, Closure $routes)
    {
        // save sttribute
        $tmp_prefix    = self::$_filter['prefix'];
        $tmp_namespace = self::$_filter['namespace'];
        $middleware    = self::$_filter['middleware'];

        // set filter path prefix
        if (isset($filter['prefix'])) {
            self::$_filter['prefix'] .= '/'.$filter['prefix'].'/';
        }
        // set filter namespace prefix
        if (isset($filter['namespace'])) {
            self::$_filter['namespace'] .= '\\'.$filter['namespace'].'\\';
        }
        // set filter middleware
        if (isset($filter['middleware'])) {
            self::$_filter['middleware'][] = $filter['middleware'];
        }
        // call route setting
        call_user_func($routes);
        // recover sttribute
        self::$_filter['prefix']     = $tmp_prefix;
        self::$_filter['namespace']  = $tmp_namespace;
        self::$_filter['middleware'] = $middleware;
    }

public
static
function
group
(
array
$
filter
,
Closure
$
routes
)
{
// save sttribute
$
tmp_prefix
=
self
::
$
_filter
[
'prefix'
]
;
$
tmp_namespace
=
self
::
$
_filter
[
'namespace'
]
;
$
middleware
=
self
::
$
_filter
[
'middleware'
]
;
// set filter path prefix
if
(
isset
(
$
filter
[
'prefix'
]
)
)
{
self
::
$
_filter
[
'prefix'
]
.=
'/'
.
$
filter
[
'prefix'
]
.
'/'
;
}
// set filter namespace prefix
if
(
isset
(
$
filter
[
'namespace'
]
)
)
{
self
::
$
_filter
[
'namespace'
]
.=
'\\'
.
$
filter
[
'namespace'
]
.
'\\'
;
}
// set filter middleware
if
(
isset
(
$
filter
[
'middleware'
]
)
)
{
self
::
$
_filter
[
'middleware'
]
[
]
=
$
filter
[
'middleware'
]
;
}
// call route setting
call_user_func
(
$
routes
)
;
// recover sttribute
self
::
$
_filter
[
'prefix'
]
=
$
tmp_prefix
;
self
::
$
_filter
[
'namespace'
]
=
$
tmp_namespace
;
self
::
$
_filter
[
'middleware'
]
=
$
middleware
;
}

set group route.

@param  array    $filter
@param  \Closure  $routes
@return void


protected static function _pathParse($path)
    {
        // make path as /a/b/c mode
        $path = ($path == '/') ? $path : '/'.rtrim($path, '/');
        $path = preg_replace('/\/+/', '/', $path);

        return $path;
    }

protected
static
function
_pathParse
(
$
path
)
{
// make path as /a/b/c mode
$
path
=
(
$
path
==
'/'
)
$
path
:
'/'
.
rtrim
(
$
path
,
'/'
)
;
$
path
=
preg_replace
(
'/\/+/'
,
'/'
,
$
path
)
;
return
$
path
;
}

Parse path.

@param  string  $path
@return string


protected static function _isVariableRoute($path)
    {
        $matched = [];

        preg_match_all(self::$_variable_regexp, $path, $matched);

        if (empty($matched[0])) {
            return FALSE;
        }

        return TRUE;
    }

protected static function _isVariableRoute ( $ path ) { $ matched = [ ] ; preg_match_all ( self :: $ _variable_regexp , $ path , $ matched ) ; if ( empty ( $ matched [ 0 ] ) ) { return FALSE ; } return TRUE ; }

check route is variable route or not.

@param  string  $path
@return boolean


protected static function _variableRouteCacheControl($value)
    {
        // is value in cache index list?
        if (FALSE !== ($index = array_search($value, self::$_variable_route_cache_index))) {
            // if value is tail, do nothing
            if ($index == (count(self::$_variable_route_cache_index) - 1)) {
                return;
            }
            // unset old value
            unset(self::$_variable_route_cache_index[$index]);
            // reset array index
            self::$_variable_route_cache_index = array_values(self::$_variable_route_cache_index);
        }
        // push value to tail
        array_push(self::$_variable_route_cache_index, $value);
        // cache index list out of range?
        if (count(self::$_variable_route_cache_index) > self::$_variable_route_cache_limit) {
            // remove head value
            $remove_value = array_shift(self::$_variable_route_cache_index);
            // unset route cache, free memory
            unset(self::$_variable_route_cache[$remove_value]);
        }
    }

protected
static
function
_variableRouteCacheControl
(
$
value
)
{
// is value in cache index list?
if
(
FALSE
!==
(
$
index
=
array_search
(
$
value
,
self
::
$
_variable_route_cache_index
)
)
)
{
// if value is tail, do nothing
if
(
$
index
==
(
count
(
self
::
$
_variable_route_cache_index
)
1
)
)
{
return
;
}
// unset old value
unset
(
self
::
$
_variable_route_cache_index
[
$
index
]
)
;
// reset array index
self
::
$
_variable_route_cache_index
=
array_values
(
self
::
$
_variable_route_cache_index
)
;
}
// push value to tail
array_push
(
self
::
$
_variable_route_cache_index
,
$
value
)
;
// cache index list out of range?
if
(
count
(
self
::
$
_variable_route_cache_index
)
>
self
::
$
_variable_route_cache_limit
)
{
// remove head value
$
remove_value
=
array_shift
(
self
::
$
_variable_route_cache_index
)
;
// unset route cache, free memory
unset
(
self
::
$
_variable_route_cache
[
$
remove_value
]
)
;
}
}

To avoid memory leaks, clear variable route cache with LRU when data out of array length.

@param  string  $value
@return void

To avoid memory leaks clear variable route cache with LRU when data out of array length .


protected static function _setMapTree($method, $path, $content)
    {
        $path     = self::_pathParse(self::$_filter['prefix'].$path);
        $callback = is_string($content) ?
                    self::_namespaceParse('\\'.self::$_filter['namespace'].$content) : $content;

        if (self::_isVariableRoute($path)) { // is variable route
            $path = self::_variablePathReplace($path);
            self::$_variable_map_tree[$path][strtoupper($method)] = $callback;
        } else { // is usual route
            self::$_map_tree[$path][strtoupper($method)] = $callback;
        }

        self::$_middleware_map_tree[$path][strtoupper($method)] = self::$_filter['middleware'];
    }

protected
static
function
_setMapTree
(
$
method
,
$
path
,
$
content
)
{
$
path
=
self
::
_pathParse
(
self
::
$
_filter
[
'prefix'
]
.
$
path
)
;
$
callback
=
is_string
(
$
content
)
self
::
_namespaceParse
(
'\\'
.
self
::
$
_filter
[
'namespace'
]
.
$
content
)
:
$
content
;
if
(
self
::
_isVariableRoute
(
$
path
)
)
{
// is variable route
$
path
=
self
::
_variablePathReplace
(
$
path
)
;
self
::
$
_variable_map_tree
[
$
path
]
[
strtoupper
(
$
method
)
]
=
$
callback
;
}
else
{
// is usual route
self
::
$
_map_tree
[
$
path
]
[
strtoupper
(
$
method
)
]
=
$
callback
;
}
self
::
$
_middleware_map_tree
[
$
path
]
[
strtoupper
(
$
method
)
]
=
self
::
$
_filter
[
'middleware'
]
;
}

set map tree.

@param  string  $method
@param  string  $path
@param  mixed  $content string or Closure
@return void


protected static function _getRedirectUrl($path, $param)
    {
        $base_url = rtrim(Config::get('app.base_url'), '/');
        $path = self::_pathParse($path);
        $url = $base_url.$path.'?'.http_build_query($param);

        return $url;
    }

protected static function _getRedirectUrl ( $ path , $ param ) { $ base_url = rtrim ( Config :: get ( 'app.base_url' ) , '/' ) ; $ path = self :: _pathParse ( $ path ) ; $ url = $ base_url . $ path . '?' . http_build_query ( $ param ) ; return $ url ; }

get redirect url

@param  string  $path
@param  array  $param
@return string


protected static function _checkMiddleware(Requests $request, $middleware_symbols)
    {
        // get all route middlewares
        $route_middlewares = Config::get('middleware.route');
        // get current request route middlewares
        $request_middlewares = [];
        foreach ($middleware_symbols as $middleware_symbol) {
            // if middleware_symbol is not in route_middlewares
            if ( ! array_key_exists($middleware_symbol, $route_middlewares)) {
                throw new \InvalidArgumentException("route middleware $middleware_symbol is not exists!");
            }
            $request_middlewares[] = $route_middlewares[$middleware_symbol];
        }
        // run middleware flow
        return Middleware::run($request_middlewares, $request);
    }

check middlewares.

@param  WorkerF\Http\Requests $request
@param  array $middleware_symbols
@return mixed
@throws \InvalidArgumentException


protected static function _runDispatch(Requests $request, $callback, $middleware_symbols, $params = [])
    {
        // check route middlewares
        $request = self::_checkMiddleware($request, $middleware_symbols);
        // if middlewares check is not passed
        if ( ! ($request instanceof Requests)) {
            return $request;
        }

        // is class
        if (is_string($callback)) {
            // syntax check
            if ( ! preg_match('/^[a-zA-Z0-9_\\\\]+@[a-zA-Z0-9_]+$/', $callback)) {
                throw new \LogicException("Please use controller@method define callback");
            }
            // get controller method info
            $controller = explode('@', $callback);
            list($class, $method) = [$controller[0], $controller[1]];
            // class methods exist ?
            if ( ! class_exists($class) || ! method_exists($class, $method)) {
                $e = new \BadMethodCallException("Class@method: $callback is not found!");
                $e->httpCode = 404;
                throw $e;
            }
            // call method
            return IOCContainer::run($class, $method, $params);
        }
        // is callback
        if (is_callable($callback)) {
            // call function
            return call_user_func_array($callback, $params);
        }
    }

run dispatch.

@param WorkerF\Http\Requests $request
@param mixed $callback string or Closure
@param array $middleware_symbols
@param array $params
@return mixed
@throws \LogicException
@throws \BadMethodCallException
@throws \InvalidArgumentException


public static function init(array $db_confs)
    {
        // connect database
        foreach ($db_confs as $con_name => $db_conf) {
            try {
                switch (strtolower($db_conf['driver'])) {
                    case 'mysql':
                        self::$_connections[$con_name] = new Mysql($db_conf);
                        break;
                    case 'pgsql':
                        self::$_connections[$con_name] = new Pgsql($db_conf);
                        break;
                    case 'sqlite':
                        self::$_connections[$con_name] = new Sqlite($db_conf);
                        break;
                    default:
                        break;
                }

            } catch (\Exception $e) {
                $msg = "Database connect fail, check your database config for connection '$con_name' \n".$e->getMessage();
                throw new ConnectException($msg);
            }
        }
    }

init db connections.

@param  array $db_confs
@return void
@throws WorkerF\DB\ConnectException


public static function header($headers)
    {
        if (is_array($headers)) {
            // if pass array
            foreach ($headers as $header) {
                if (FALSE === Http::header($header)) {
                    throw new \InvalidArgumentException("Header $header is invalid!");
                }
            }
            return;
        }
        // pass string
        if (FALSE === Http::header($headers)) {
            throw new \InvalidArgumentException("Header $headers is invalid!");
        }
    }

public
static
function
header
(
$
headers
)
{
if
(
is_array
(
$
headers
)
)
{
// if pass array
foreach
(
$
headers
as
$
header
)
{
if
(
FALSE
===
Http
::
header
(
$
header
)
)
{
throw
new
\
InvalidArgumentException
(
"Header $header is invalid!"
)
;
}
}
return
;
}
// pass string
if
(
FALSE
===
Http
::
header
(
$
headers
)
)
{
throw
new
\
InvalidArgumentException
(
"Header $headers is invalid!"
)
;
}
}

create http response header.

@param  mixed  $header array or string
@return void


public static function getHeader($key)
    {
        if ( ! array_key_exists($key, HttpCache::$header)) {
            return NULL;
        }

        return HttpCache::$header[$key];
    }

public static function getHeader ( $ key ) { if ( ! array_key_exists ( $ key , HttpCache :: $ header ) ) { return NULL ; } return HttpCache :: $ header [ $ key ] ; }

get http response header.

@param  string  $key
@return string


public function insertGetLastId(array $data)
    {
        // create build str
        $field_str = '';
        $value_str = '';
        foreach ($data as $key => $value) {
            $field_str .= ' '.self::_wrapRow($key).',';
            $plh = self::_getPlh();
            $this->_bind_params[$plh] = $value;
            $value_str .= ' '.$plh.',';
        }

        $field_str = rtrim($field_str, ',');
        $value_str = rtrim($value_str, ',');

        $this->_insert_str = ' ('.$field_str.') VALUES ('.$value_str.') RETURNING id ';
        // execute
        $this->_buildInsert();
        $this->_execute();
        $result = $this->_pdoSt->fetch(PDO::FETCH_ASSOC);

        return $result['id'];
    }

public
function
insertGetLastId
(
array
$
data
)
{
// create build str
$
field_str
=
''
;
$
value_str
=
''
;
foreach
(
$
data
as
$
key
=>
$
value
)
{
$
field_str
.=
' '
.
self
::
_wrapRow
(
$
key
)
.
','
;
$
plh
=
self
::
_getPlh
(
)
;
$
this
->
_bind_params
[
$
plh
]
=
$
value
;
$
value_str
.=
' '
.
$
plh
.
','
;
}
$
field_str
=
rtrim
(
$
field_str
,
','
)
;
$
value_str
=
rtrim
(
$
value_str
,
','
)
;
$
this
->
_insert_str
=
' ('
.
$
field_str
.
') VALUES ('
.
$
value_str
.
') RETURNING id '
;
// execute
$
this
->
_buildInsert
(
)
;
$
this
->
_execute
(
)
;
$
result
=
$
this
->
_pdoSt
->
fetch
(
PDO
::
FETCH_ASSOC
)
;
return
$
result
[
'id'
]
;
}

get last insert ID for postgresql

@param  array $data
@return  null/int
@throws  \PDOException


protected static function _getDiParams(array $params)
    {
        $di_params = [];
        foreach ($params as $param) {
            $class = $param->getClass();
            if ($class) {
                // check dependency is a singleton instance or not
                $singleton = self::getSingleton($class->name);
                $di_params[] = $singleton ? $singleton : self::getInstance($class->name);
            }
        }

        return $di_params;
    }

create Dependency injection params.

@param  array $params
@return array


public static function singleton($instance, $name = NULL)
    {
        if ( ! is_object($instance)) {
            throw new \InvalidArgumentException("Object need!");
        }

        $class_name = $name == NULL ? get_class($instance) : $name;

        // singleton not exist, create
        if ( ! array_key_exists($class_name, self::$_singleton)) {
            self::$_singleton[$class_name] = $instance;
        }
    }

public
static
function
singleton
(
$
instance
,
$
name
=
)
{
if
(
!
is_object
(
$
instance
)
)
{
throw
new
\
InvalidArgumentException
(
"Object need!"
)
;
}
$
class_name
=
$
name
==
get_class
(
$
instance
)
:
$
name
;
// singleton not exist, create
if
(
!
array_key_exists
(
$
class_name
,
self
::
$
_singleton
)
)
{
self
::
$
_singleton
[
$
class_name
]
=
$
instance
;
}
}

set a singleton instance.

@param  object $instance
@param  string $name
@return void
@throws \InvalidArgumentException


public static function getSingleton($class_name)
    {
        return array_key_exists($class_name, self::$_singleton) ?
                self::$_singleton[$class_name] : NULL;
    }

public static function getSingleton ( $ class_name ) { return array_key_exists ( $ class_name , self :: $ _singleton ) ? self :: $ _singleton [ $ class_name ] : NULL ; }

get a singleton instance.

@param  string $class_name
@return mixed object or NULL


public static function register($abstract, $concrete = NULL)
    {
        if ($concrete == NULL) {
            $instance = self::getInstance($abstract);
            self::singleton($instance);
        } else {
            $instance = self::getInstance($concrete);
            self::singleton($instance, $abstract);
        }
    }

public static function register ( $ abstract , $ concrete = NULL ) { if ( $ concrete == NULL ) { $ instance = self :: getInstance ( $ abstract ) ; self :: singleton ( $ instance ) ; } else { $ instance = self :: getInstance ( $ concrete ) ; self :: singleton ( $ instance , $ abstract ) ; } }

register class, instantiate class, set instance to singleton.

@param  string $abstract abstract class name
@param  string $concrete concrete class name, if NULL, use abstract class name
@return void

register class instantiate class set instance to singleton .


public static function getInstance($class_name)
    {
        // get class reflector
        $reflector = new ReflectionClass($class_name);
        // get constructor
        $constructor = $reflector->getConstructor();
        // create di params
        $di_params = $constructor ? self::_getDiParams($constructor->getParameters()) : [];
        // create instance
        return $reflector->newInstanceArgs($di_params);
    }

public
static
function
getInstance
(
$
class_name
)
{
// get class reflector
$
reflector
=
new
ReflectionClass
(
$
class_name
)
;
// get constructor
$
constructor
=
$
reflector
->
getConstructor
(
)
;
// create di params
$
di_params
=
$
constructor
self
::
_getDiParams
(
$
constructor
->
getParameters
(
)
)
:
[
]
;
// create instance
return
$
reflector
->
newInstanceArgs
(
$
di_params
)
;
}

get Instance from reflection info.

@param  string  $class_name
@return object


public static function getInstanceWithSingleton($class_name)
    {
        // is a singleton instance?
        if (NULL != ($instance = self::getSingleton($class_name))) {
            return $instance;
        }

        $instance = self::getInstance($class_name);
        self::singleton($instance);

        return $instance;
    }

public
static
function
getInstanceWithSingleton
(
$
class_name
)
{
// is a singleton instance?
if
(
!=
(
$
instance
=
self
::
getSingleton
(
$
class_name
)
)
)
{
return
$
instance
;
}
$
instance
=
self
::
getInstance
(
$
class_name
)
;
self
::
singleton
(
$
instance
)
;
return
$
instance
;
}

get Instance, if instance is not singleton, set it to singleton.

@param  string  $class_name
@return object

get Instance if instance is not singleton set it to singleton .


public static function run($class_name, $method, $params = [])
    {
        // class exist ?
        if ( ! class_exists($class_name)) {
            throw new \BadMethodCallException("Class $class_name is not found!");
        }
        // method exist ?
        if ( ! method_exists($class_name, $method)) {
            throw new \BadMethodCallException("undefined method $method in $class_name !");
        }
        // create instance
        $instance = self::getInstance($class_name);
        /******* method Dependency injection *******/
        // get class reflector
        $reflector = new ReflectionClass($class_name);
        // get method
        $reflectorMethod = $reflector->getMethod($method);
        // create di params
        $di_params = self::_getDiParams($reflectorMethod->getParameters());
        // run method
        return call_user_func_array([$instance, $method], array_merge($di_params, $params));
    }

run class method.

@param  string $class_name
@param  string $method
@param  array  $params
@return mixed
@throws \BadMethodCallException


public static function run(TcpConnection $con)
    {
        try {
            // build config
            $conf = Config::get('app');
            // get request
            $request = new Requests();

            // check global middlewares
            $global_middlerwares = Config::get('middleware.global');
            $result = Middleware::run($global_middlerwares, $request);
            // middlewares check passed?
            if ($result instanceof Requests) {
                // run dispatch
                $result = Route::dispatch($result);
            }

        } catch (\Exception $e) {
            // Handle Exception
            $exceptionHandler = IOCContainer::getInstanceWithSingleton(ExceptionHandler::class);

            $result = $exceptionHandler->handle($e);

        } finally {
            // return Response data
            $response = Response::bulid($result, $conf);
            $con->send($response);
        }
    }

public
static
function
run
(
TcpConnection
$
con
)
{
try
{
// build config
$
conf
=
Config
::
get
(
'app'
)
;
// get request
$
request
=
new
Requests
(
)
;
// check global middlewares
$
global_middlerwares
=
Config
::
get
(
'middleware.global'
)
;
$
result
=
Middleware
::
run
(
$
global_middlerwares
,
$
request
)
;
// middlewares check passed?
if
(
$
result
instanceof
Requests
)
{
// run dispatch
$
result
=
Route
::
dispatch
(
$
result
)
;
}
}
catch
(
\
Exception
$
e
)
{
// Handle Exception
$
exceptionHandler
=
IOCContainer
::
getInstanceWithSingleton
(
ExceptionHandler
::
class
)
;
$
result
=
$
exceptionHandler
->
handle
(
$
e
)
;
}
finally
{
// return Response data
$
response
=
Response
::
bulid
(
$
result
,
$
conf
)
;
$
con
->
send
(
$
response
)
;
}
}

run http app.

@param  Workerman\Connection\TcpConnection $con
@return void
@throws \LogicException
@throws \BadMethodCallException
@throws \InvalidArgumentException
@throws \PDOException


public static function init()
    {
        try {
            // register class
            self::register();
            // init database
            DB::init(Config::get('database.db_con'));
            // init redis
            Redis::init(Config::get('database.redis'));

        } catch (\Exception $e) {
            Error::printError($e->getMessage());
        }
    }

public
static
function
init
(
)
{
try
{
// register class
self
::
register
(
)
;
// init database
DB
::
init
(
Config
::
get
(
'database.db_con'
)
)
;
// init redis
Redis
::
init
(
Config
::
get
(
'database.redis'
)
)
;
}
catch
(
\
Exception
$
e
)
{
Error
::
printError
(
$
e
->
getMessage
(
)
)
;
}
}

Initialize some devices like redis \ database ...

@return void
@throws WorkerF\DB\ConnectException


public function pipe($pipe)
    {
        if (FALSE === is_callable($pipe)) {
            throw new InvalidArgumentException('pipe should be callable.');
        }

        $this->_pipes[] = $pipe;

        return $this;
    }

public
function
pipe
(
$
pipe
)
{
if
(
FALSE
===
is_callable
(
$
pipe
)
)
{
throw
new
InvalidArgumentException
(
'pipe should be callable.'
)
;
}
$
this
->
_pipes
[
]
=
$
pipe
;
return
$
this
;
}

set pipes, single mode.

@param $pipe
@return self
@throws \InvalidArgumentException


public static function init(array $rd_confs)
    {
        // create redis init params
        $cluster = $rd_confs['cluster'];
        $options = (array) $rd_confs['options'];
        $servers = $rd_confs['rd_con'];
        // get clients
        self::$_clients = $cluster ?
            self::createAggregateClient($servers, $options) :
            self::createSingleClients($servers, $options);
        // check redis connect
        foreach (self::$_clients as $con_name => $client) {
            try {
                $client->connect();

            } catch (\Exception $e) {
                $msg = "Redis connect fail, check your redis config for connection '$con_name'. \n".$e->getMessage();
                throw new ConnectException($msg);
            }
        }
    }

init redis clients.

@param  array $rd_confs
@return void
@throws WorkerF\DB\ConnectException


public static function subscribe($channels, Closure $callback, $connection = 'default', $method = 'subscribe')
    {
        $loop = self::$_clients[$connection]->pubSubLoop();

        call_user_func_array([$loop, $method], (array) $channels);

        // loop blocking, start listen redis publish messages
        foreach ($loop as $message) {
            if ($message->kind === 'message' || $message->kind === 'pmessage') {
                // get publish message
                call_user_func($callback, $message->payload, $message->channel);
            }
        }
        // unset Predis\PubSub\Consumer iterator
        unset($loop);
    }

Subscribe to a set of given channels for messages.
'read_write_timeout' => 0 to solve the timeout problem

@param  array|string  $channels
@param  \Closure  $callback
@param  string  $connection
@param  string  $method
@return void

Subscribe to a set of given channels for messages . read_write_timeout = > 0 to solve the timeout problem


public static function psubscribe($channels, Closure $callback, $connection = 'default')
    {
        return self::subscribe($channels, $callback, $connection, 'psubscribe');
    }

public static function psubscribe ( $ channels , Closure $ callback , $ connection = 'default' ) { return self :: subscribe ( $ channels , $ callback , $ connection , 'psubscribe' ) ; }

Subscribe to a set of given channels with wildcards.

@param  array|string  $channels
@param  \Closure  $callback
@param  string  $connection
@return void

Subscribe to a set of given channels with wildcards .


protected function _reset()
    {
        $this->_table = '';
        $this->_prepare_sql = '';
        $this->_cols_str = ' * ';
        $this->_where_str = '';
        $this->_orderby_str = '';
        $this->_groupby_str = '';
        $this->_having_str = '';
        $this->_join_str = '';
        $this->_limit_str = '';
        $this->_insert_str = '';
        $this->_update_str = '';
        $this->_bind_params = [];
    }

protected
function
_reset
(
)
{
$
this
->
_table
=
''
;
$
this
->
_prepare_sql
=
''
;
$
this
->
_cols_str
=
' * '
;
$
this
->
_where_str
=
''
;
$
this
->
_orderby_str
=
''
;
$
this
->
_groupby_str
=
''
;
$
this
->
_having_str
=
''
;
$
this
->
_join_str
=
''
;
$
this
->
_limit_str
=
''
;
$
this
->
_insert_str
=
''
;
$
this
->
_update_str
=
''
;
$
this
->
_bind_params
=
[
]
;
}

reset all attribute
memory-resident mode , need manual reset attr

@return  void

reset all attribute memory - resident mode need manual reset attr


protected function _wrapPrepareSql()
    {
        // set table prefix
        $quote = static::$_quote_symbol;
        $prefix_pattern = '/'.$quote.'([a-zA-Z0-9_]+)'.$quote.'(\.)'.$quote.'([a-zA-Z0-9_]+)'.$quote.'/';
        $prefix_replace = self::_quote($this->_wrapTable('$1')).'$2'.self::_quote('$3');

        $this->_prepare_sql = preg_replace($prefix_pattern, $prefix_replace, $this->_prepare_sql);
    }

protected
function
_wrapPrepareSql
(
)
{
// set table prefix
$
quote
=
static
::
$
_quote_symbol
;
$
prefix_pattern
=
'/'
.
$
quote
.
'([a-zA-Z0-9_]+)'
.
$
quote
.
'(\.)'
.
$
quote
.
'([a-zA-Z0-9_]+)'
.
$
quote
.
'/'
;
$
prefix_replace
=
self
::
_quote
(
$
this
->
_wrapTable
(
'$1'
)
)
.
'$2'
.
self
::
_quote
(
'$3'
)
;
$
this
->
_prepare_sql
=
preg_replace
(
$
prefix_pattern
,
$
prefix_replace
,
$
this
->
_prepare_sql
)
;
}


protected function _execute()
    {
        try {
            $this->_wrapPrepareSql();
            $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql);
            $this->_bindParams();
            $this->_pdoSt->execute();
            $this->_reset();  // memory-resident mode, singleton pattern, need reset build attr
            // if debug mode, print sql and bind params to stdout
            if ($this->_debug) {
                $this->_pdoSt->debugDumpParams();
                $this->_debug = FALSE; // close debug
            }
        } catch (PDOException $e) {
            // when time out, reconnect
            if ($this->_isTimeout($e)) {
                $this->_closeConnection();
                $this->_connect();
                // retry
                try {
                    $this->_wrapPrepareSql();
                    $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql);
                    $this->_bindParams();
                    $this->_pdoSt->execute();
                    $this->_reset();
                    // if debug mode, print sql and bind params to stdout
                    if ($this->_debug) {
                        $this->_pdoSt->debugDumpParams();
                        $this->_debug = FALSE; // close debug
                    }
                } catch (PDOException $e) {
                    throw $e;
                }

            } else {
                throw $e;
            }
        }

    }

prepare and execute, if timeout, auto reconnect

@return  void
@throws  \PDOException

prepare and execute if timeout auto reconnect


protected function _bindParams()
    {
        if (is_array($this->_bind_params)) {

            foreach ($this->_bind_params as $plh => $param) {

                $data_type = PDO::PARAM_STR;

                if (is_numeric($param)) {
                    $data_type = PDO::PARAM_INT;
                }

                if (is_null($param)) {
                    $data_type = PDO::PARAM_NULL;
                }

                if (is_bool($param)) {
                    $data_type = PDO::PARAM_BOOL;
                }

                $this->_pdoSt->bindValue($plh, $param, $data_type);
            }
        }
    }

protected function _bindParams ( ) { if ( is_array ( $ this -> _bind_params ) ) { foreach ( $ this -> _bind_params as $ plh => $ param ) { $ data_type = PDO :: PARAM_STR ; if ( is_numeric ( $ param ) ) { $ data_type = PDO :: PARAM_INT ; } if ( is_null ( $ param ) ) { $ data_type = PDO :: PARAM_NULL ; } if ( is_bool ( $ param ) ) { $ data_type = PDO :: PARAM_BOOL ; } $ this -> _pdoSt -> bindValue ( $ plh , $ param , $ data_type ) ; } } }


protected function _wrapTable($table)
    {
        $prefix = array_key_exists('prefix', $this->_config) ?
                $this->_config['prefix'] : '';

        return $prefix.$table;
    }

protected function _wrapTable ( $ table ) { $ prefix = array_key_exists ( 'prefix' , $ this -> _config ) ? $ this -> _config [ 'prefix' ] : '' ; return $ prefix . $ table ; }

set table prefix

@param  string $table
@return  string


protected static function _wrapRow($str)
    {
        // match pattern
        $alias_pattern = '/([a-zA-Z0-9_\.]+)\s+(AS|as|As)\s+([a-zA-Z0-9_]+)/';
        $alias_replace = self::_quote('$1').' $2 '.self::_quote('$3');
        $prefix_pattern = '/([a-zA-Z0-9_]+\s*)(\.)(\s*[a-zA-Z0-9_]+)/';
        $prefix_replace = self::_quote('$1').'$2'.self::_quote('$3');
        $func_pattern = '/[a-zA-Z0-9_]+\([a-zA-Z0-9_\,\s\`\'\"\*]*\)/';
        // alias mode
        if (preg_match($alias_pattern, $str, $alias_match)) {
            // if field is aa.bb as cc mode
            if (preg_match($prefix_pattern, $alias_match[1])) {
                $pre_rst = preg_replace($prefix_pattern, $prefix_replace, $alias_match[1]);
                $alias_replace = $pre_rst.' $2 '.self::_quote('$3');
            }
            return preg_replace($alias_pattern, $alias_replace, $str);
        }
        // prefix mode
        if (preg_match($prefix_pattern, $str)) {
            return preg_replace($prefix_pattern, $prefix_replace, $str);
        }
        // func mode (do nothing)
        if (preg_match($func_pattern, $str)) {
            return $str;
        }
        // field mode
        return self::_quote($str);
    }

add backquote sto field, support alias mode, prefix mode, func mode

@param  string $str
@return  string

add backquote sto field support alias mode prefix mode func mode


protected function _condition_constructor($args_num, $params, &$construct_str)
    {
        // params dose not conform to specification
        if ( ! $args_num || $args_num > 3) {
            throw new \InvalidArgumentException("Error number of parameters");
        }
        // argurment mode
        switch ($args_num) {
          // assoc array mode
          case 1:
              if ( ! is_array($params[0])) {
                  throw new \InvalidArgumentException($params[0].' should be Array');
              }
              $construct_str .= '(';
              foreach ($params[0] as $field => $value) {
                  $plh = self::_getPlh();
                  $construct_str .= ' '.self::_wrapRow($field).' = '.$plh.' AND';
                  $this->_bind_params[$plh] = $value;
              }
              // remove last operator
              $construct_str = substr($construct_str, 0, strrpos($construct_str, 'AND'));
              $construct_str .= ')';
              break;
          // ('a', 10) : a = 10 mode or ('a', null) : a is null mode
          case 2:
              if (is_null($params[1])) {
                  $construct_str .= ' '.self::_wrapRow($params[0]).' IS NULL ';
              } else {
                  $plh = self::_getPlh();
                  $construct_str .= ' '.self::_wrapRow($params[0]).' = '.$plh.' ';
                  $this->_bind_params[$plh] = $params[1];
              }
              break;
          // ('a', '>', 10) : a > 10 mode \ ('name', 'like', '%adam%') : name like '%adam%' mode
          case 3:
              if ( ! in_array(strtolower($params[1]), $this->_operators)) {
                  throw new \InvalidArgumentException('Confusing Symbol '.$params[1]);
              }
              $plh = self::_getPlh();
              $construct_str .= ' '.self::_wrapRow($params[0]).' '.$params[1].' '.$plh.' ';
              $this->_bind_params[$plh] = $params[2];
              break;
        }
    }

parse argurment, create build attr, store bind param

@param  int $args_num
@param  array $params
@param  string &$construct_str
@return  void
@throws  \InvalidArgumentException

parse argurment create build attr store bind param


protected function _storeBuildAttr()
    {
        // attribute need to store
        $store = [];
        // store attr
        foreach ($this->_buildAttrs as $buildAttr) {
            $store[$buildAttr] = $this->$buildAttr;
        }

        return $store;
    }

protected
function
_storeBuildAttr
(
)
{
// attribute need to store
$
store
=
[
]
;
// store attr
foreach
(
$
this
->
_buildAttrs
as
$
buildAttr
)
{
$
store
[
$
buildAttr
]
=
$
this
->
$
buildAttr
;
}
return
$
store
;
}


protected function _reStoreBuildAttr(array $data)
    {
        foreach ($this->_buildAttrs as $buildAttr) {
            $this->$buildAttr = $data[$buildAttr];
        }
    }

protected function _reStoreBuildAttr ( array $ data ) { foreach ( $ this -> _buildAttrs as $ buildAttr ) { $ this -> $ buildAttr = $ data [ $ buildAttr ] ; } }

restore build attr from tmp

@param  array $data
@return  void


protected function _subBuilder(Closure $callback)
    {
        // store build attr
        $store = $this->_storeBuildAttr();

        /**************** begin sub query build ****************/
            // empty attribute
            $this->_resetBuildAttr();
            // call sub query callback
            call_user_func($callback, $this);
            // get sub query build attr
            $sub_attr = [];

            $this->_buildQuery();

            foreach ($this->_buildAttrs as $buildAttr) {
                $sub_attr[$buildAttr] = $this->$buildAttr;
            }
        /**************** end sub query build ****************/

        // restore attribute
        $this->_reStoreBuildAttr($store);

        return $sub_attr;
    }

do sub build

@param  \Closure $callback
@return  array
@throws  \InvalidArgumentException
@throws  \PDOException


public function select()
    {
        $cols = func_get_args();

        if ( ! func_num_args() || in_array('*', $cols)) {
            $this->_cols_str = ' * ';
        } else {
            // _cols_str default ' * ' , it easy to get a result when select func dosen't called
            // but when you call select func , you should set it to ''
            $this->_cols_str = '';
            foreach ($cols as $col) {
                $this->_cols_str .= ' '.self::_wrapRow($col).',';
            }
            $this->_cols_str = rtrim($this->_cols_str, ',');
        }

        return $this;
    }


public function where()
    {
        $operator = 'AND';
        // is the first time call where method ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE ';
        } else {
            $this->_where_str .= ' '.$operator.' ';
        }
        // build attribute, bind params
        $this->_condition_constructor(func_num_args(), func_get_args(), $this->_where_str);

        return $this;
    }


public function orWhere()
    {
        $operator = 'OR';
        // is the first time call where method ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE ';
        } else {
            $this->_where_str .= ' '.$operator.' ';
        }
        // build attribute, bind params
        $this->_condition_constructor(func_num_args(), func_get_args(), $this->_where_str);

        return $this;
    }


public function whereIn($field, array $data, $condition = 'IN', $operator = 'AND')
    {
        if ( ! in_array($condition, ['IN', 'NOT IN']) || ! in_array($operator, ['AND', 'OR'])) {
            throw new \InvalidArgumentException("Error whereIn mode");
        }
        // create placeholder
        foreach ($data as $key => $value) {
            $plh = self::_getPlh();
            $data[$key] = $plh;
            $this->_bind_params[$plh] = $value;
        }
        // is the first time call where method ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE '.self::_wrapRow($field).' '.$condition.' ('.implode(',', $data).')';
        } else {
            $this->_where_str .= ' '.$operator.' '.self::_wrapRow($field).' '.$condition.' ('.implode(',', $data).')';
        }

        return $this;
    }

build whereIn string

@param  string $field
@param  array $data
@param  string $condition
@param  string $operator
@return  self
@throws  \InvalidArgumentException


public function whereBetween($field, $start, $end, $operator = 'AND')
    {
        if ( ! in_array($operator, ['AND', 'OR'])) {
            throw new \InvalidArgumentException("Logical operator");
        }
        // create placeholder
        $start_plh = self::_getPlh();
        $end_plh = self::_getPlh();
        $this->_bind_params[$start_plh] = $start;
        $this->_bind_params[$end_plh] = $end;

        // is the first time call where method ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE '.self::_wrapRow($field).' BETWEEN '.$start_plh.' AND '.$end_plh;
        } else {
            $this->_where_str .= ' '.$operator.' '.self::_wrapRow($field).' BETWEEN '.$start_plh.' AND '.$end_plh;
        }

        return $this;
    }

build whereBetween string

@param  string $field
@param  int $start
@param  int $end
@param  string $operator
@return  self
@throws  \InvalidArgumentException


public function whereNull($field, $condition = 'NULL', $operator = 'AND')
    {
        if ( ! in_array($condition, ['NULL', 'NOT NULL']) || ! in_array($operator, ['AND', 'OR'])) {
            throw new \InvalidArgumentException("Logical operator");
        }
        // is the first time call where method ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE ';
        } else {
            $this->_where_str .= ' '.$operator.' ';
        }

        $this->_where_str .= self::_wrapRow($field).' IS '.$condition.' ';

        return $this;
    }

build whereNull string

@param  string $field
@param  string $condition
@param  string $operator
@return  self
@throws  \InvalidArgumentException


public function whereBrackets(Closure $callback, $operator = 'AND')
    {
        if ( ! in_array($operator, ['AND', 'OR'])) {
            throw new \InvalidArgumentException("Logical operator");
        }
        // first time call where ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE ( ';
        } else {
            $this->_where_str .= ' '.$operator.' ( ';
        }
        $sub_attr = $this->_subBuilder($callback);

        $this->_where_str .= preg_replace('/WHERE/', '', $sub_attr['_where_str'], 1).' ) ';

        return $this;
    }

public
function
whereBrackets
(
Closure
$
callback
,
$
operator
=
'AND'
)
{
if
(
!
in_array
(
$
operator
,
[
'AND'
,
'OR'
]
)
)
{
throw
new
\
InvalidArgumentException
(
"Logical operator"
)
;
}
// first time call where ?
if
(
$
this
->
_where_str
==
''
)
{
$
this
->
_where_str
=
' WHERE ( '
;
}
else
{
$
this
->
_where_str
.=
' '
.
$
operator
.
' ( '
;
}
$
sub_attr
=
$
this
->
_subBuilder
(
$
callback
)
;
$
this
->
_where_str
.=
preg_replace
(
'/WHERE/'
,
''
,
$
sub_attr
[
'_where_str'
]
,
1
)
.
' ) '
;
return
$
this
;
}

build whereBrackets string

@param  \Closure $callback
@param  string $operator
@return  self
@throws  \InvalidArgumentException


public function whereExists(Closure $callback, $condition = 'EXISTS', $operator = 'AND')
    {
        if ( ! in_array($condition, ['EXISTS', 'NOT EXISTS']) || ! in_array($operator, ['AND', 'OR'])) {
            throw new \InvalidArgumentException("Error whereExists mode");
        }
        // first time call where ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE '.$condition.' ( ';
        } else {
            $this->_where_str .= ' '.$operator.' '.$condition.' ( ';
        }

        $sub_attr = $this->_subBuilder($callback);
        $this->_where_str .= $sub_attr['_prepare_sql'].' ) ';

        return $this;
    }

public
function
whereExists
(
Closure
$
callback
,
$
condition
=
'EXISTS'
,
$
operator
=
'AND'
)
{
if
(
!
in_array
(
$
condition
,
[
'EXISTS'
,
'NOT EXISTS'
]
)
||
!
in_array
(
$
operator
,
[
'AND'
,
'OR'
]
)
)
{
throw
new
\
InvalidArgumentException
(
"Error whereExists mode"
)
;
}
// first time call where ?
if
(
$
this
->
_where_str
==
''
)
{
$
this
->
_where_str
=
' WHERE '
.
$
condition
.
' ( '
;
}
else
{
$
this
->
_where_str
.=
' '
.
$
operator
.
' '
.
$
condition
.
' ( '
;
}
$
sub_attr
=
$
this
->
_subBuilder
(
$
callback
)
;
$
this
->
_where_str
.=
$
sub_attr
[
'_prepare_sql'
]
.
' ) '
;
return
$
this
;
}

build whereExists string

@param  \Closure $callback
@param  string $condition
@param  string $operator
@return  self
@throws  \InvalidArgumentException


public function whereInSub($field, Closure $callback, $condition = 'IN', $operator = 'AND')
    {
        if ( ! in_array($condition, ['IN', 'NOT IN']) || ! in_array($operator, ['AND', 'OR'])) {
            throw new \InvalidArgumentException("Error whereIn mode");
        }
        // first time call where ?
        if ($this->_where_str == '') {
            $this->_where_str = ' WHERE '.self::_wrapRow($field).' '.$condition.' ( ';
        } else {
            $this->_where_str .= ' '.$operator.' '.self::_wrapRow($field).' '.$condition.' ( ';
        }

        $sub_attr = $this->_subBuilder($callback);
        $this->_where_str .= $sub_attr['_prepare_sql'].' ) ';

        return $this;
    }

public
function
whereInSub
(
$
field
,
Closure
$
callback
,
$
condition
=
'IN'
,
$
operator
=
'AND'
)
{
if
(
!
in_array
(
$
condition
,
[
'IN'
,
'NOT IN'
]
)
||
!
in_array
(
$
operator
,
[
'AND'
,
'OR'
]
)
)
{
throw
new
\
InvalidArgumentException
(
"Error whereIn mode"
)
;
}
// first time call where ?
if
(
$
this
->
_where_str
==
''
)
{
$
this
->
_where_str
=
' WHERE '
.
self
::
_wrapRow
(
$
field
)
.
' '
.
$
condition
.
' ( '
;
}
else
{
$
this
->
_where_str
.=
' '
.
$
operator
.
' '
.
self
::
_wrapRow
(
$
field
)
.
' '
.
$
condition
.
' ( '
;
}
$
sub_attr
=
$
this
->
_subBuilder
(
$
callback
)
;
$
this
->
_where_str
.=
$
sub_attr
[
'_prepare_sql'
]
.
' ) '
;
return
$
this
;
}

build whereInSub string

@param  string $field
@param  \Closure $callback
@param  string $condition
@param  string $operator
@return  self
@throws  \InvalidArgumentException


public function groupBy($field)
    {
        // is the first time call groupBy method ?
        if ($this->_groupby_str == '') {
            $this->_groupby_str = ' GROUP BY '.self::_wrapRow($field);
        } else {
            $this->_groupby_str .= ' , '.self::_wrapRow($field);
        }

        return $this;
    }

build groupBy string

@param  string $field
@return  self


public function having()
    {
        $operator = 'AND';

        // is the first time call where method ?
        if ($this->_having_str == '') {
            $this->_having_str = ' HAVING ';
        } else {
            $this->_having_str .= ' '.$operator.' ';
        }
        // build attribute, bind params
        $this->_condition_constructor(func_num_args(), func_get_args(), $this->_having_str);

        return $this;
    }


public function orHaving()
    {
        $operator = 'OR';

        // is the first time call where method ?
        if ($this->_having_str == '') {
            $this->_having_str = ' HAVING ';
        } else {
            $this->_having_str .= ' '.$operator.' ';
        }
        // build attribute, bind params
        $this->_condition_constructor(func_num_args(), func_get_args(), $this->_having_str);

        return $this;
    }


public function orderBy($field, $mode = 'ASC')
    {
        $mode = strtoupper($mode);
        if ( ! in_array($mode, ['ASC', 'DESC'])) {
            throw new \InvalidArgumentException("Error order by mode");
        }
        // is the first time call orderBy method ?
        if ($this->_orderby_str == '') {
            $this->_orderby_str = ' ORDER BY '.self::_wrapRow($field).' '.$mode;
        } else {
            $this->_orderby_str .= ' , '.self::_wrapRow($field).' '.$mode;
        }

        return $this;
    }

build orderBy string

@param  string $field
@param  string $mode
@return  self
@throws  \InvalidArgumentException


public function join($table, $one, $two, $type = 'INNER')
    {
        if ( ! in_array($type, ['INNER', 'LEFT', 'RIGHT'])) {
            throw new \InvalidArgumentException("Error join mode");
        }
        // set table prefix
        $table = $this->_wrapTable($table);
        // create join string
        $this->_join_str .= ' '.$type.' JOIN '.self::_wrapRow($table).
            ' ON '.self::_wrapRow($one).' = '.self::_wrapRow($two);
        return $this;
    }

public
function
join
(
$
table
,
$
one
,
$
two
,
$
type
=
'INNER'
)
{
if
(
!
in_array
(
$
type
,
[
'INNER'
,
'LEFT'
,
'RIGHT'
]
)
)
{
throw
new
\
InvalidArgumentException
(
"Error join mode"
)
;
}
// set table prefix
$
table
=
$
this
->
_wrapTable
(
$
table
)
;
// create join string
$
this
->
_join_str
.=
' '
.
$
type
.
' JOIN '
.
self
::
_wrapRow
(
$
table
)
.
' ON '
.
self
::
_wrapRow
(
$
one
)
.
' = '
.
self
::
_wrapRow
(
$
two
)
;
return
$
this
;
}

build join string

@param  string $table
@param  string $one
@param  string $two
@param  string $type
@return  self
@throws  \InvalidArgumentException


public function fromSub(Closure $callback)
    {
        $sub_attr = $this->_subBuilder($callback);
        $this->_table .= ' ( '.$sub_attr['_prepare_sql'].' ) AS tb_'.uniqid().' ';

        return $this;
    }

public
function
fromSub
(
Closure
$
callback
)
{
$
sub_attr
=
$
this
->
_subBuilder
(
$
callback
)
;
$
this
->
_table
.=
' ( '
.
$
sub_attr
[
'_prepare_sql'
]
.
' ) AS tb_'
.
uniqid
(
)
.
' '
;
return
$
this
;
}

build rightJoin string

@param  Closure $callback
@return  self
@throws  \InvalidArgumentException
@throws  \PDOException


public function paginate($step, $page = NULL)
    {
        // store build attr\bind param
        $store = $this->_storeBuildAttr();
        $bind_params = $this->_storeBindParam();
        // get count
        $count = $this->count();
        // restore build attr\bind param
        $this->_reStoreBuildAttr($store);
        $this->_reStoreBindParam($bind_params);

        // create paginate data
        $page = $page ? $page : 1;
        $this->limit($step * ($page - 1), $step);

        $rst['total']        = $count;
        $rst['per_page']     = $step;
        $rst['current_page'] = $page;
        $rst['next_page']    = ($page + 1) > ($count / $step) ? NULL : ($page + 1);
        $rst['prev_page']    = ($page - 1) < 1 ? NULL : ($page - 1);
        $rst['first_page']   = 1;
        $rst['last_page']    = $count / $step;
        $rst['data']         = $this->get();

        return $rst;
    }

public
function
paginate
(
$
step
,
$
page
=
)
{
// store build attr\bind param
$
store
=
$
this
->
_storeBuildAttr
(
)
;
$
bind_params
=
$
this
->
_storeBindParam
(
)
;
// get count
$
count
=
$
this
->
count
(
)
;
// restore build attr\bind param
$
this
->
_reStoreBuildAttr
(
$
store
)
;
$
this
->
_reStoreBindParam
(
$
bind_params
)
;
// create paginate data
$
page
=
$
page
$
page
:
1
;
$
this
->
limit
(
$
step
*
(
$
page
1
)
,
$
step
)
;
$
rst
[
'total'
]
=
$
count
;
$
rst
[
'per_page'
]
=
$
step
;
$
rst
[
'current_page'
]
=
$
page
;
$
rst
[
'next_page'
]
=
(
$
page
+
1
)
>
(
$
count
/
$
step
)
:
(
$
page
+
1
)
;
$
rst
[
'prev_page'
]
=
(
$
page
1
)
<
1
:
(
$
page
1
)
;
$
rst
[
'first_page'
]
=
1
;
$
rst
[
'last_page'
]
=
$
count
/
$
step
;
$
rst
[
'data'
]
=
$
this
->
get
(
)
;
return
$
rst
;
}

get paginate data

@param  int $step
@param  int $page
@return  array
@throws  \PDOException


public function get()
    {
        $this->_buildQuery();
        $this->_execute();

        return $this->_pdoSt->fetchAll(PDO::FETCH_ASSOC);
    }

public function get ( ) { $ this -> _buildQuery ( ) ; $ this -> _execute ( ) ; return $ this -> _pdoSt -> fetchAll ( PDO :: FETCH_ASSOC ) ; }

get assoc data

@return  array
@throws  \PDOException


public function row()
    {
        $this->_buildQuery();
        $this->_execute();

        return $this->_pdoSt->fetch(PDO::FETCH_ASSOC);
    }

public function row ( ) { $ this -> _buildQuery ( ) ; $ this -> _execute ( ) ; return $ this -> _pdoSt -> fetch ( PDO :: FETCH_ASSOC ) ; }

get assoc row data

@return  array
@throws  \PDOException


public function getList($field)
    {
        $this->_cols_str = ' '.self::_quote($field).' ';
        $this->_buildQuery();
        $this->_execute();

        return $this->_pdoSt->fetchAll(PDO::FETCH_COLUMN, 0);
    }

public
function
getList
(
$
field
)
{
$
this
->
_cols_str
=
' '
.
self
::
_quote
(
$
field
)
.
' '
;
$
this
->
_buildQuery
(
)
;
$
this
->
_execute
(
)
;
return
$
this
->
_pdoSt
->
fetchAll
(
PDO
::
FETCH_COLUMN
,
0
)
;
}

get field list

@param  string $field
@return  array
@throws  \PDOException


public function query($sql)
    {
        try {
            return $this->_pdo->query($sql);
        } catch (PDOException $e) {
            // when time out, reconnect
            if ($this->_isTimeout($e)) {

                $this->_closeConnection();
                $this->_connect();

                try {
                    return $this->_pdo->query($sql);
                } catch (PDOException $e) {
                    throw $e;
                }

            } else {
                throw $e;
            }
        }
    }

public
function
query
(
$
sql
)
{
try
{
return
$
this
->
_pdo
->
query
(
$
sql
)
;
}
catch
(
PDOException
$
e
)
{
// when time out, reconnect
if
(
$
this
->
_isTimeout
(
$
e
)
)
{
$
this
->
_closeConnection
(
)
;
$
this
->
_connect
(
)
;
try
{
return
$
this
->
_pdo
->
query
(
$
sql
)
;
}
catch
(
PDOException
$
e
)
{
throw
$
e
;
}
}
else
{
throw
$
e
;
}
}
}

native query, add auto reconnect

@param  string $sql
@return  \PDOStatement/boolean
@throws  \PDOException


public function prepare($sql, array $driver_options = [])
    {
        try {
            return $this->_pdo->prepare($sql, $driver_options);
        } catch (PDOException $e) {
            // when time out, reconnect
            if ($this->_isTimeout($e)) {

                $this->_closeConnection();
                $this->_connect();

                try {
                    return $this->_pdo->prepare($sql, $driver_options);
                } catch (PDOException $e) {
                    throw $e;
                }

            } else {
                throw $e;
            }
        }
    }

public
function
prepare
(
$
sql
,
array
$
driver_options
=
[
]
)
{
try
{
return
$
this
->
_pdo
->
prepare
(
$
sql
,
$
driver_options
)
;
}
catch
(
PDOException
$
e
)
{
// when time out, reconnect
if
(
$
this
->
_isTimeout
(
$
e
)
)
{
$
this
->
_closeConnection
(
)
;
$
this
->
_connect
(
)
;
try
{
return
$
this
->
_pdo
->
prepare
(
$
sql
,
$
driver_options
)
;
}
catch
(
PDOException
$
e
)
{
throw
$
e
;
}
}
else
{
throw
$
e
;
}
}
}

native prepare, add auto reconnect

@param  string $sql
@param  array $driver_options
@return  \PDOStatement/boolean
@throws  \PDOException


public function beginTrans()
    {
        try {
            return $this->_pdo->beginTransaction();
        } catch (PDOException $e) {
            // when time out, reconnect
            if ($this->_isTimeout($e)) {

                $this->_closeConnection();
                $this->_connect();

                try {
                    return $this->_pdo->beginTransaction();
                } catch (PDOException $e) {
                    throw $e;
                }

            } else {
                throw $e;
            }
        }
    }

public
function
beginTrans
(
)
{
try
{
return
$
this
->
_pdo
->
beginTransaction
(
)
;
}
catch
(
PDOException
$
e
)
{
// when time out, reconnect
if
(
$
this
->
_isTimeout
(
$
e
)
)
{
$
this
->
_closeConnection
(
)
;
$
this
->
_connect
(
)
;
try
{
return
$
this
->
_pdo
->
beginTransaction
(
)
;
}
catch
(
PDOException
$
e
)
{
throw
$
e
;
}
}
else
{
throw
$
e
;
}
}
}

begin Transaction, add auto reconnect

@return  boolean
@throws  \PDOException


public function handle(\Exception $e)
    {
        $httpCode = 500;
        // create http response header
        if (property_exists($e, 'httpCode') &&
           array_key_exists($e->httpCode, Response::$statusCodes)
        ) { // is a http exception
            $httpCode = $e->httpCode;
            $header = Response::$statusCodes[$httpCode];
        } else { // other exception
            $header = Response::$statusCodes[$httpCode];
            Error::printError($e); // if Server error, echo to stdout
        }

        Response::header($header);

        return Error::errorHtml($e, $header, Config::get('app.debug'));
    }

public
function
handle
(
\
Exception
$
e
)
{
$
httpCode
=
500
;
// create http response header
if
(
property_exists
(
$
e
,
'httpCode'
)
&&
array_key_exists
(
$
e
->
httpCode
,
Response
::
$
statusCodes
)
)
{
// is a http exception
$
httpCode
=
$
e
->
httpCode
;
$
header
=
Response
::
$
statusCodes
[
$
httpCode
]
;
}
else
{
// other exception
$
header
=
Response
::
$
statusCodes
[
$
httpCode
]
;
Error
::
printError
(
$
e
)
;
// if Server error, echo to stdout
}
Response
::
header
(
$
header
)
;
return
Error
::
errorHtml
(
$
e
,
$
header
,
Config
::
get
(
'app.debug'
)
)
;
}

ExceptionHandler handle.

@param \Exception $e
@return string


public function generateId($size = 0, $mode = self::MODE_NORMAL)
    {
        $size = $size>0? $size: $this->size;
        switch ($mode) {
            case self::MODE_DYNAMIC:
                return $this->core->random($this->generator, $size);
            default:
                return $this->normalRandom($size);
        }
    }

public function generateId ( $ size = 0 , $ mode = self :: MODE_NORMAL ) { $ size = $ size > 0 ? $ size : $ this -> size ; switch ( $ mode ) { case self :: MODE_DYNAMIC : return $ this -> core -> random ( $ this -> generator , $ size ) ; default : return $ this -> normalRandom ( $ size ) ; } }

Generate nanoid via optional modes

@param integer $size
@param integer $mode Client::MODE_NORMAL|Client::MODE_DYNAMIC
@return string


public function formatedId($alphabet, $size, GeneratorInterface $generator = null)
    {
        $generator = $generator?:$this->generator;
        $alphabet = $alphabet?:CoreInterface::SAFE_SYMBOLS;

        return $this->core->random($generator, $size, $alphabet);
    }

public function formatedId ( $ alphabet , $ size , GeneratorInterface $ generator = null ) { $ generator = $ generator ? : $ this -> generator ; $ alphabet = $ alphabet ? : CoreInterface :: SAFE_SYMBOLS ; return $ this -> core -> random ( $ generator , $ size , $ alphabet ) ; }

The original API of nanoid. Use it be careful, Please make sure
you have been implements your custom GeneratorInterface as correctly.
Otherwise use the build-in default random bytes generator

@see https://github.com/ai/nanoid/blob/master/format.js
@see https://github.com/ai/nanoid/blob/master/generate.js
@param GeneratorInterface $generator
@param integer $size
@param string $alphabet default CoreInterface::SAFE_SYMBOLS
@return string

The original API of nanoid . Use it be careful Please make sure you have been implements your custom GeneratorInterface as correctly . Otherwise use the build - in default random bytes generator


private function normalRandom($size)
    {
        $id = '';
        while (1 <= $size--) {
            $rand = mt_rand()/(mt_getrandmax() + 1);
            $id .= $this->alphbet[$rand*64 | 0];
        }

        return $id;
    }

private function normalRandom ( $ size ) { $ id = '' ; while ( 1 <= $ size -- ) { $ rand = mt_rand ( ) / ( mt_getrandmax ( ) + 1 ) ; $ id .= $ this -> alphbet [ $ rand * 64 | 0 ] ; } return $ id ; }

Generate secure URL-friendly unique ID.
By default, ID will have 21 symbols to have same collisions probability
as UUID v4.

@see https://github.com/ai/nanoid/blob/master/index.js
@param integer $size
@return string

Generate secure URL - friendly unique ID . By default ID will have 21 symbols to have same collisions probability as UUID v4 .


public static function normalizeDSN($dsn, $user = null, $pass = null)
    {
        // Try to dissect DSN into parts
        $parts = is_array($dsn) ? $dsn : parse_url($dsn);

        // If parts are usable, convert DSN format
        if ($parts !== false && isset($parts['host'], $parts['path'])) {
            // DSN is using URL-like format, so we need to convert it
            $dsn =
                $parts['scheme'].
                ':host='.$parts['host'].
                (isset($parts['port']) ? ';port='.$parts['port'] : '').
                ';dbname='.substr($parts['path'], 1);
            $user = $user !== null ? $user : (isset($parts['user']) ? $parts['user'] : null);
            $pass = $pass !== null ? $pass : (isset($parts['pass']) ? $parts['pass'] : null);
        }

        // If it's still array, then simply use it
        if (is_array($dsn)) {
            return $dsn;
        }

        // If it's string, then find driver
        if (is_string($dsn)) {
            if (strpos($dsn, ':') === false) {
                throw new Exception([
                    "Your DSN format is invalid. Must be in 'driver:host;options' format",
                    'dsn' => $dsn,
                ]);
            }
            list($driver, $rest) = explode(':', $dsn, 2);
            $driver = strtolower($driver);
        } else {
            // currently impossible to be like this, but we don't want ugly exceptions here
            $driver = $rest = null;
        }

        return ['dsn' => $dsn, 'user' => $user, 'pass' => $pass, 'driver' => $driver, 'rest' => $rest];
    }

Normalize DSN connection string.

Returns normalized DSN as array ['dsn', 'user', 'pass', 'driver', 'rest'].

@param array|string $dsn  DSN string
@param string       $user Optional username, this takes precedence over dsn string
@param string       $pass Optional password, this takes precedence over dsn string

@return array


public function dsql($properties = [])
    {
        $c = $this->query_class;
        $q = new $c($properties);
        $q->connection = $this;

        return $q;
    }

public function dsql ( $ properties = [ ] ) { $ c = $ this -> query_class ; $ q = new $ c ( $ properties ) ; $ q -> connection = $ this ; return $ q ; }

Returns new Query object with connection already set.

@param array $properties

@return Query

Returns new Query object with connection already set .


public function execute(Expression $expr)
    {
        // If custom connection is set, execute again using that
        if ($this->connection && $this->connection !== $this) {
            return $expr->execute($this->connection);
        }

        throw new Exception('Queries cannot be executed through this connection');
    }

Execute Expression by using this connection.

@param Expression $expr

@return \PDOStatement

Execute Expression by using this connection .


public function beginTransaction()
    {
        // transaction starts only if it was not started before
        $r = $this->inTransaction()
            ? false
            : $this->connection->beginTransaction();

        $this->transaction_depth++;

        return $r;
    }

Starts new transaction.

Database driver supports statements for starting and committing
transactions. Unfortunately most of them don't allow to nest
transactions and commit gradually.
With this method you have some implementation of nested transactions.

When you call it for the first time it will begin transaction. If you
call it more times, it will do nothing but will increase depth counter.
You will need to call commit() for each execution of beginTransactions()
and only the last commit will perform actual commit in database.

So, if you have been working with the database and got un-handled
exception in the middle of your code, everything will be rolled back.

@return mixed Don't rely on any meaningful return


public function commit()
    {
        // check if transaction is actually started
        if (!$this->inTransaction()) {
            throw new Exception('Using commit() when no transaction has started');
        }

        $this->transaction_depth--;

        if ($this->transaction_depth == 0) {
            return $this->connection->commit();
        }

        return false;
    }

Commits transaction.

Each occurrence of beginTransaction() must be matched with commit().
Only when same amount of commits are executed, the actual commit will be
issued to the database.

@see beginTransaction()

@return mixed Don't rely on any meaningful return


public function rollBack()
    {
        // check if transaction is actually started
        if (!$this->inTransaction()) {
            throw new Exception('Using rollBack() when no transaction has started');
        }

        $this->transaction_depth--;

        if ($this->transaction_depth == 0) {
            return $this->connection->rollBack();
        }

        return false;
    }

Rollbacks queries since beginTransaction and resets transaction depth.

@see beginTransaction()

@return mixed Don't rely on any meaningful return

Rollbacks queries since beginTransaction and resets transaction depth .


public function lastInsertID($m = null)
    {
        if ($m instanceof \atk4\data\Model) {
            // if we use sequence, then we can easily get current value
            if (isset($m->sequence)) {
                return $this->dsql()->mode('seq_currval')->sequence($m->sequence)->getOne();
            }

            // otherwise we have to select max(id_field) - this can be bad for performance !!!
            // Imants: Disabled for now because otherwise this will work even if database use triggers or
            // any other mechanism to automatically increment ID and we can't tell this line to not execute.
            //return $this->expr('SELECT max([field]) FROM [table]', ['field'=>$m->id_field, 'table'=>$m->table])->getOne();
        }

        // fallback
        return parent::lastInsertID($m);
    }

Return last inserted ID value.

Few Connection drivers need to receive Model to get ID because PDO doesn't support this method.

@param \atk4\data\Model Optional data model from which to return last ID

@return mixed


public function reset($tag = null)
    {
        // unset all arguments
        if ($tag === null) {
            $this->args = ['custom' => []];

            return $this;
        }

        if (!is_string($tag)) {
            throw new Exception([
                'Tag should be string',
                'tag' => $tag,
            ]);
        }

        // unset custom/argument or argument if such exists
        if ($this->offsetExists($tag)) {
            $this->offsetUnset($tag);
        } elseif (isset($this->args[$tag])) {
            unset($this->args[$tag]);
        }

        return $this;
    }

Resets arguments.

@param string $tag

@return $this


protected function _consume($sql_code, $escape_mode = 'param')
    {
        if (!is_object($sql_code)) {
            switch ($escape_mode) {
                case 'param':
                    return $this->_param($sql_code);
                case 'escape':
                    return $this->_escape($sql_code);
                case 'soft-escape':
                    return $this->_escapeSoft($sql_code);
                case 'none':
                    return $sql_code;
            }

            throw new Exception([
                '$escape_mode value is incorrect',
                'escape_mode' => $escape_mode,
            ]);
        }

        // User may add Expressionable trait to any class, then pass it's objects
        if ($sql_code instanceof Expressionable) {
            $sql_code = $sql_code->getDSQLExpression($this);
        }

        if (!$sql_code instanceof self) {
            throw new Exception([
                'Only Expressions or Expressionable objects may be used in Expression',
                'object' => $sql_code,
            ]);
        }

        // at this point $sql_code is instance of Expression
        $sql_code->params = &$this->params;
        $sql_code->_paramBase = &$this->_paramBase;
        $ret = $sql_code->render();

        // Queries should be wrapped in parentheses in most cases
        if ($sql_code instanceof Query) {
            $ret = '('.$ret.')';
        }

        // unset is needed here because ->params=&$othervar->params=foo will also change $othervar.
        // if we unset() first, we’re safe.
        unset($sql_code->params);
        $sql_code->params = [];

        return $ret;
    }

Recursively renders sub-query or expression, combining parameters.

@param mixed  $sql_code    Expression
@param string $escape_mode Fall-back escaping mode - param|escape|none

@return string|array Quoted expression or array of param names

Recursively renders sub - query or expression combining parameters .


protected function _escapeSoft($value)
    {
        // supports array
        if (is_array($value)) {
            return array_map(__METHOD__, $value);
        }

        // in some cases we should not escape
        if ($this->isUnescapablePattern($value)) {
            return $value;
        }

        if (is_string($value) && strpos($value, '.') !== false) {
            return implode('.', array_map(__METHOD__, explode('.', $value)));
        }

        return $this->escape_char.trim($value).$this->escape_char;
    }

protected
function
_escapeSoft
(
$
value
)
{
// supports array
if
(
is_array
(
$
value
)
)
{
return
array_map
(
__METHOD__
,
$
value
)
;
}
// in some cases we should not escape
if
(
$
this
->
isUnescapablePattern
(
$
value
)
)
{
return
$
value
;
}
if
(
is_string
(
$
value
)
&&
strpos
(
$
value
,
'.'
)
!==
false
)
{
return
implode
(
'.'
,
array_map
(
__METHOD__
,
explode
(
'.'
,
$
value
)
)
)
;
}
return
$
this
->
escape_char
.
trim
(
$
value
)
.
$
this
->
escape_char
;
}

Soft-escaping SQL identifier. This method will attempt to put
escaping char around the identifier, however will not do so if you
are using special characters like ".", "(" or escaping char.

It will smartly escape table.field type of strings resulting
in "table"."field".

@param mixed $value Any string or array of strings

@return string|array Escaped string or array of strings

Soft - escaping SQL identifier . This method will attempt to put escaping char around the identifier however will not do so if you are using special characters like . ( or escaping char .


public function render()
    {
        $nameless_count = 0;
        if (!isset($this->_paramBase)) {
            $this->_paramBase = $this->paramBase;
        }

        if ($this->template === null) {
            throw new Exception('Template is not defined for Expression');
        }

        $res = preg_replace_callback(
            '/\[[a-z0-9_]*\]|{[a-z0-9_]*}/i',
            function ($matches) use (&$nameless_count) {
                $identifier = substr($matches[0], 1, -1);
                $escaping = ($matches[0][0] == '[') ? 'param' : 'escape';

                // Allow template to contain []
                if ($identifier === '') {
                    $identifier = $nameless_count++;

                    // use rendering only with named tags
                }
                $fx = '_render_'.$identifier;

                // [foo] will attempt to call $this->_render_foo()

                if (array_key_exists($identifier, $this->args['custom'])) {
                    $value = $this->_consume($this->args['custom'][$identifier], $escaping);
                } elseif (method_exists($this, $fx)) {
                    $value = $this->$fx();
                } else {
                    throw new Exception([
                        'Expression could not render tag',
                        'tag' => $identifier,
                    ]);
                }

                return is_array($value) ? '('.implode(',', $value).')' : $value;
            },
            $this->template
        );
        unset($this->_paramBase);

        return trim($res);
    }

Render expression and return it as string.

@return string Rendered query

Render expression and return it as string .


public function getDebugQuery($html = null)
    {
        $d = $this->render();
        $pp = [];
        foreach (array_reverse($this->params) as $key => $val) {
            if (is_numeric($val)) {
                $d = preg_replace(
                    '/'.$key.'([^_]|$)/',
                    $val.'\1',
                    $d
                );
            } elseif (is_string($val)) {
                $d = preg_replace('/'.$key.'([^_]|$)/', "'".addslashes($val)."'\\1", $d);
            } elseif ($val === null) {
                $d = preg_replace(
                    '/'.$key.'([^_]|$)/',
                    'NULL\1',
                    $d
                );
            } else {
                $d = preg_replace('/'.$key.'([^_]|$)/', $val.'\\1', $d);
            }
            $pp[] = $key;
        }
        if (class_exists('SqlFormatter')) {
            if ($html) {
                $result = \SqlFormatter::format($d);
            } else {
                $result = \SqlFormatter::format($d, false);
            }
        } else {
            $result = $d;  // output as-is
        }
        if (!$html) {
            return str_replace('#lte#', '<=', strip_tags(str_replace('<=', '#lte#', $result), '<>'));
        }

        return $result;
    }

public
function
getDebugQuery
(
$
html
=
)
{
$
d
=
$
this
->
render
(
)
;
$
pp
=
[
]
;
foreach
(
array_reverse
(
$
this
->
params
)
as
$
key
=>
$
val
)
{
if
(
is_numeric
(
$
val
)
)
{
$
d
=
preg_replace
(
'/'
.
$
key
.
'([^_]|$)/'
,
$
val
.
'\1'
,
$
d
)
;
}
elseif
(
is_string
(
$
val
)
)
{
$
d
=
preg_replace
(
'/'
.
$
key
.
'([^_]|$)/'
,
"'"
.
addslashes
(
$
val
)
.
"'\\1"
,
$
d
)
;
}
elseif
(
$
val
===
)
{
$
d
=
preg_replace
(
'/'
.
$
key
.
'([^_]|$)/'
,
'NULL\1'
,
$
d
)
;
}
else
{
$
d
=
preg_replace
(
'/'
.
$
key
.
'([^_]|$)/'
,
$
val
.
'\\1'
,
$
d
)
;
}
$
pp
[
]
=
$
key
;
}
if
(
class_exists
(
'SqlFormatter'
)
)
{
if
(
$
html
)
{
$
result
=
\
SqlFormatter
::
format
(
$
d
)
;
}
else
{
$
result
=
\
SqlFormatter
::
format
(
$
d
,
false
)
;
}
}
else
{
$
result
=
$
d
;
// output as-is
}
if
(
!
$
html
)
{
return
str_replace
(
'#lte#'
,
'<='
,
strip_tags
(
str_replace
(
'<='
,
'#lte#'
,
$
result
)
,
'<>'
)
)
;
}
return
$
result
;
}

Return formatted debug output.

Ignore false positive warnings of PHPMD.

@SuppressWarnings(PHPMD.StaticAccess)

@param bool $html Show as HTML?

@return string SQL syntax of query


public function get()
    {
        $stmt = $this->execute();

        if ($stmt instanceof \Generator) {
            return iterator_to_array($stmt);
        }

        return $stmt->fetchAll();
    }

public function get ( ) { $ stmt = $ this -> execute ( ) ; if ( $ stmt instanceof \ Generator ) { return iterator_to_array ( $ stmt ) ; } return $ stmt -> fetchAll ( ) ; }

Executes expression and return whole result-set in form of array of hashes.

@return array

Executes expression and return whole result - set in form of array of hashes .


public function getOne()
    {
        $data = $this->getRow();
        if (!$data) {
            throw new Exception([
                'Unable to fetch single cell of data for getOne from this query',
                'result' => $data,
                'query'  => $this->getDebugQuery(),
            ]);
        }
        $one = array_shift($data);

        return $one;
    }

Executes expression and return first value of first row of data from result-set.

@return string

Executes expression and return first value of first row of data from result - set .


public function getRow()
    {
        $stmt = $this->execute();

        if ($stmt instanceof \Generator) {
            return $stmt->current();
        }

        return $stmt->fetch();
    }

public function getRow ( ) { $ stmt = $ this -> execute ( ) ; if ( $ stmt instanceof \ Generator ) { return $ stmt -> current ( ) ; } return $ stmt -> fetch ( ) ; }

Executes expression and returns first row of data from result-set as a hash.

@return array

Executes expression and returns first row of data from result - set as a hash .


public function table($table, $alias = null)
    {
        // comma-separated table names
        if (is_string($table) && strpos($table, ',') !== false) {
            $table = explode(',', $table);
        }

        // array of tables - recursively process each
        if (is_array($table)) {
            if ($alias !== null) {
                throw new Exception([
                    'You cannot use single alias with multiple tables',
                    'alias' => $alias,
                ]);
            }

            foreach ($table as $alias => $t) {
                if (is_numeric($alias)) {
                    $alias = null;
                }
                $this->table($t, $alias);
            }

            return $this;
        }

        // if table is set as sub-Query, then alias is mandatory
        if ($table instanceof self && $alias === null) {
            throw new Exception('If table is set as Query, then table alias is mandatory');
        }

        if (is_string($table) && $alias === null) {
            $alias = $table;
        }

        // main_table will be set only if table() is called once.
        // it's used as "default table" when joining with other tables, see join().
        // on multiple calls, main_table will be false and we won't
        // be able to join easily anymore.
        $this->main_table = ($this->main_table === null && $alias !== null ? $alias : false);

        // save table in args
        $this->_set_args('table', $alias, $table);

        return $this;
    }

Specify a table to be used in a query.

@param mixed  $table Specifies table
@param string $alias Specify alias for this table

@return $this


public function where($field, $cond = null, $value = null, $kind = 'where', $num_args = null)
    {
        // Number of passed arguments will be used to determine if arguments were specified or not
        if ($num_args === null) {
            $num_args = func_num_args();
        }

        // Array as first argument means we have to replace it with orExpr()
        if (is_array($field)) {
            // or conditions
            $or = $this->orExpr();
            foreach ($field as $row) {
                if (is_array($row)) {
                    call_user_func_array([$or, 'where'], $row);
                } else {
                    $or->where($row);
                }
            }
            $field = $or;
        }

        if ($num_args === 1 && is_string($field)) {
            $this->args[$kind][] = [$this->expr($field)];

            return $this;
        }

        // first argument is string containing more than just a field name and no more than 2
        // arguments means that we either have a string expression or embedded condition.
        if ($num_args === 2 && is_string($field) && !preg_match('/^[.a-zA-Z0-9_]*$/', $field)) {
            // field contains non-alphanumeric values. Look for condition
            preg_match(
                '/^([^ <>!=]*)([><!=]*|( *(not|is|in|like))*) *$/',
                $field,
                $matches
            );

            // matches[2] will contain the condition, but $cond will contain the value
            $value = $cond;
            $cond = $matches[2];

            // if we couldn't clearly identify the condition, we might be dealing with
            // a more complex expression. If expression is followed by another argument
            // we need to add equation sign  where('now()',123).
            if (!$cond) {
                $matches[1] = $this->expr($field);

                $cond = '=';
            } else {
                $num_args++;
            }

            $field = $matches[1];
        }

        switch ($num_args) {
            case 1:
                $this->args[$kind][] = [$field];
                break;
            case 2:
                if (is_object($cond) && !$cond instanceof Expressionable && !$cond instanceof Expression) {
                    throw new Exception([
                        'Value cannot be converted to SQL-compatible expression',
                        'field'=> $field,
                        'value'=> $cond,
                    ]);
                }

                $this->args[$kind][] = [$field, $cond];
                break;
            case 3:
                if (is_object($value) && !$value instanceof Expressionable && !$value instanceof Expression) {
                    throw new Exception([
                        'Value cannot be converted to SQL-compatible expression',
                        'field'=> $field,
                        'cond' => $cond,
                        'value'=> $value,
                    ]);
                }

                $this->args[$kind][] = [$field, $cond, $value];
                break;
        }

        return $this;
    }

Adds condition to your query.

Examples:
$q->where('id',1);

By default condition implies equality. You can specify a different comparison
operator by either including it along with the field or using 3-argument
format:
$q->where('id>','1');
$q->where('id','>',1);

You may use Expression as any part of the query.
$q->where($q->expr('a=b'));
$q->where('date>',$q->expr('now()'));
$q->where($q->expr('length(password)'),'>',5);

If you specify Query as an argument, it will be automatically
surrounded by brackets:
$q->where('user_id',$q->dsql()->table('users')->field('id'));

You can specify OR conditions by passing single argument - array:
$q->where([
['a','is',null],
['b','is',null]
]);

If entry of the OR condition is not an array, then it's assumed to
be an expression;

$q->where([
['age',20],
'age is null'
]);

The above use of OR conditions rely on orExpr() functionality. See
that method for more information.

To specify OR conditions
$q->where($q->orExpr()->where('a',1)->where('b',1));

@param mixed  $field    Field, array for OR or Expression
@param mixed  $cond     Condition such as '=', '>' or 'is not'
@param mixed  $value    Value. Will be quoted unless you pass expression
@param string $kind     Do not use directly. Use having()
@param string $num_args When $kind is passed, we can't determine number of
actual arguments, so this argument must be specified.

@return $this


protected function __render_condition($row)
    {
        if (count($row) === 3) {
            list($field, $cond, $value) = $row;
        } elseif (count($row) === 2) {
            list($field, $cond) = $row;
        } elseif (count($row) === 1) {
            list($field) = $row;
        }

        $field = $this->_consume($field, 'soft-escape');

        if (count($row) == 1) {
            // Only a single parameter was passed, so we simply include all
            return $field;
        }

        // below are only cases when 2 or 3 arguments are passed

        // if no condition defined - set default condition
        if (count($row) == 2) {
            $value = $cond;

            if (is_array($value)) {
                $cond = 'in';
            } elseif ($value instanceof self && $value->mode === 'select') {
                $cond = 'in';
            } else {
                $cond = '=';
            }
        } else {
            $cond = trim(strtolower($cond));
        }

        // below we can be sure that all 3 arguments has been passed

        // special conditions (IS | IS NOT) if value is null
        if ($value === null) {
            if ($cond === '=') {
                $cond = 'is';
            } elseif (in_array($cond, ['!=', '<>', 'not'])) {
                $cond = 'is not';
            }
        }

        // value should be array for such conditions
        if (in_array($cond, ['in', 'not in', 'not']) && is_string($value)) {
            $value = array_map('trim', explode(',', $value));
        }

        // special conditions (IN | NOT IN) if value is array
        if (is_array($value)) {
            $value = '('.implode(',', $this->_param($value)).')';
            $cond = in_array($cond, ['!=', '<>', 'not', 'not in']) ? 'not in' : 'in';

            return $field.' '.$cond.' '.$value;
        }

        // if value is object, then it should be Expression or Query itself
        // otherwise just escape value
        $value = $this->_consume($value, 'param');

        return $field.' '.$cond.' '.$value;
    }

Renders one condition.

@param array $row Condition

@return string


public function group($group)
    {
        // Case with comma-separated fields
        if (is_string($group) && !$this->isUnescapablePattern($group) && strpos($group, ',') !== false) {
            $group = explode(',', $group);
        }

        if (is_array($group)) {
            foreach ($group as $g) {
                $this->args['group'][] = $g;
            }

            return $this;
        }

        $this->args['group'][] = $group;

        return $this;
    }

public
function
group
(
$
group
)
{
// Case with comma-separated fields
if
(
is_string
(
$
group
)
&&
!
$
this
->
isUnescapablePattern
(
$
group
)
&&
strpos
(
$
group
,
','
)
!==
false
)
{
$
group
=
explode
(
','
,
$
group
)
;
}
if
(
is_array
(
$
group
)
)
{
foreach
(
$
group
as
$
g
)
{
$
this
->
args
[
'group'
]
[
]
=
$
g
;
}
return
$
this
;
}
$
this
->
args
[
'group'
]
[
]
=
$
group
;
return
$
this
;
}

Implements GROUP BY functionality. Simply pass either field name
as string or expression.

@param mixed $group Group by this

@return $this

Implements GROUP BY functionality . Simply pass either field name as string or expression .


public function set($field, $value = null)
    {
        if ($value === false) {
            throw new Exception([
                'Value "false" is not supported by SQL',
                'field' => $field,
                'value' => $value,
            ]);
        }

        if (is_array($value)) {
            throw new Exception([
                'Array values are not supported by SQL',
                'field' => $field,
                'value' => $value,
            ]);
        }

        if (is_array($field)) {
            foreach ($field as $key => $value) {
                $this->set($key, $value);
            }

            return $this;
        }

        if (is_string($field) || $field instanceof Expression || $field instanceof Expressionable) {
            $this->args['set'][] = [$field, $value];
        } else {
            throw new Exception([
                'Field name should be string or Expressionable',
                'field' => $field,
            ]);
        }

        return $this;
    }

Sets field value for INSERT or UPDATE statements.

@param string|array $field Name of the field
@param mixed        $value Value of the field

@return $this

Sets field value for INSERT or UPDATE statements .


public function option($option, $mode = 'select')
    {
        // Case with comma-separated options
        if (is_string($option) && strpos($option, ',') !== false) {
            $option = explode(',', $option);
        }

        if (is_array($option)) {
            foreach ($option as $opt) {
                $this->args['option'][$mode][] = $opt;
            }

            return $this;
        }

        $this->args['option'][$mode][] = $option;

        return $this;
    }

public
function
option
(
$
option
,
$
mode
=
'select'
)
{
// Case with comma-separated options
if
(
is_string
(
$
option
)
&&
strpos
(
$
option
,
','
)
!==
false
)
{
$
option
=
explode
(
','
,
$
option
)
;
}
if
(
is_array
(
$
option
)
)
{
foreach
(
$
option
as
$
opt
)
{
$
this
->
args
[
'option'
]
[
$
mode
]
[
]
=
$
opt
;
}
return
$
this
;
}
$
this
->
args
[
'option'
]
[
$
mode
]
[
]
=
$
option
;
return
$
this
;
}

Set options for particular mode.

@param mixed  $option
@param string $mode   select|insert|replace

@return $this