Files
simrs-jatim/core/PostgresDb.php
2024-04-19 14:04:41 +07:00

1431 lines
41 KiB
PHP

<?php
// use InvalidArgumentException;
// use PDO;
// use PDOException;
// use PDOStatement;
// use RuntimeException;
require_once _DOCROOT_.'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
$dotenv = new Dotenv\Dotenv(_DOCROOT_);
$dotenv->overload();
/**
* PostgresDb Class
* by @SeinopSys | https://github.com/SeinopSys/PHP-PostgreSQL-Database-Class
* Heavily based on MysqliDB version 2.4 as made by
* Jeffery Way <jeffrey@jeffrey-way.com>
* Josh Campbell <jcampbell@ajillion.com>
* Alexander V. Butenko <a.butenka@gmail.com>
* and licensed under GNU Public License v3
* (http://opensource.org/licenses/gpl-3.0.html)
* http://github.com/joshcam/PHP-MySQLi-Database-Class
*
* Modified based on use by Nuril Isbah <nuril.isbah@gmail.com>
**/
class PostgresDb
{
/**
* PDO connection
*
* @var PDO
*/
protected $connection;
/**
* The SQL query to be prepared and executed
*
* @var string
*/
protected $query;
/**
* The previously executed SQL query
*
* @var string
*/
protected $lastQuery;
/**
* An array that holds where joins
*
* @var array
*/
protected $join = [];
/**
* An array that holds where conditions 'fieldName' => 'value'
*
* @var array
*/
protected $where = [];
/**
* Dynamic type list for order by condition value
*/
protected $orderBy = [];
/**
* Dynamic type list for group by condition value
*/
protected $groupBy = [];
/**
* Dynamic array that holds a combination of where condition/table data value types and parameter references
*
* @var array|null
*/
protected $bindParams;
/**
* Variable which holds last statement error
*
* @var string
*/
protected $stmtError;
/**
* Allows the use of the tableNameToClassName method
*
* @var bool
*/
protected $autoClassEnabled = true;
/**
* Name of table we're performing the action on
*
* @var string|null
*/
protected $tableName;
/**
* Type of fetch to perform
*
* @var int
*/
protected $fetchType = PDO::FETCH_ASSOC;
/**
* Fetch argument
*
* @var string
*/
protected $fetchArg;
/**
* Error mode for the connection
* Defaults to
*
* @var int
*/
protected $errorMode = PDO::ERRMODE_WARNING;
/**
* List of keywords used for escaping column names, automatically populated on connection
*
* @var string[]
*/
protected $sqlKeywords = [];
/**
* List of columns to be returned after insert/delete
*
* @var string[]|null
*/
protected $returning;
/**
* Variable which holds an amount of returned rows during queries
*
* @var int
*/
public $count = 0;
/**
* Used for connecting to the database
*
* @var string
*/
private $connectionString;
const ORDERBY_RAND = 'rand()';
public $insertid;
public $num_rows;
public $result_metadata;
public $result_fetch;
public $query_count = 0;
public $debugging = FALSE;
public $error;
public $arr_fields = [];
/**
* PostgresDb constructor
*
* @param string $db
* @param string $host
* @param string $user
* @param string $pass
* @param int $port
*/
public function __construct($dbname = '', $dbhost = '', $dbuser = '', $dbpass = '', $port = '')
{
if($dbhost == '' || $dbuser == '' || $dbpass == '' || $dbname == '' || $port == '')
{
$dbhost = $_ENV['POSTGRE_DB_HOST'];
$dbuser = $_ENV['POSTGRE_DB_USER'];
$dbpass = $_ENV['POSTGRE_DB_PASS'];
$dbname = $_ENV['POSTGRE_DB_NAME'];
$port = $_ENV['POSTGRE_DB_PORT'];
}
$this->connectionString = "pgsql:host=$dbhost port=$port user=$dbuser password=$dbpass dbname=$dbname options='--client_encoding=UTF8'";
$this->connect();
}
/**
* Initiate a database connection using the data passed in the constructor
*
* @throws PDOException
* @throws RuntimeException
*/
protected function connect()
{
$this->setConnection(new PDO($this->connectionString));
$this->connection->setAttribute(PDO::ATTR_ERRMODE, $this->errorMode);
}
/**
* @return PDO
* @throws RuntimeException
* @throws PDOException
*/
public function getConnection()
{
if (!$this->connection) {
$this->connect();
}
return $this->connection;
}
/**
* Allows passing any PDO object to the class, e.g. one initiated by a different library
*
* @param PDO $PDO
*
* @throws PDOException
* @throws RuntimeException
*/
public function setConnection(PDO $PDO)
{
$this->connection = $PDO;
$keywords = $this->query('SELECT word FROM pg_get_keywords()');
foreach ($keywords->fetchAll() as $key) {
$this->sqlKeywords[strtolower($key['word'])] = true;
}
}
/**
* Set the error mode of the PDO instance
* Expects a PDO::ERRMODE_* constant
*
* @param int
*
* @return self
*/
public function setPDOErrmode($errmode)
{
$this->errorMode = $errmode;
if ($this->connection) {
$this->connection->setAttribute(PDO::ATTR_ERRMODE, $this->errorMode);
}
return $this;
}
/**
* Returns the error mode of the PDO instance
*
* @return int
*/
public function getPDOErrmode()
{
return $this->errorMode;
}
/**
* Method attempts to prepare the SQL query
* and throws an error if there was a problem.
*
* @return PDOStatement
* @throws RuntimeException
*/
protected function prepareQuery()
{
try {
$stmt = $this->getConnection()->prepare($this->query);
} catch (PDOException $e) {
$this->debug("Problem preparing query ($this->query): " . $e->getMessage());
$this->error = $e->getMessage();
throw new RuntimeException(
"Problem preparing query ($this->query): " . $e->getMessage(),
$e->getCode(),
$e
);
}
if (is_bool($stmt)) {
$this->debug("Problem preparing query ({$this->query}). Check logs/stderr for any warnings.");
throw new RuntimeException("Problem preparing query ({$this->query}). Check logs/stderr for any warnings.");
}
return $stmt;
}
/**
* Function to replace query placeholders with bound variables
*
* @param string $query
* @param array $bindParams
*
* @return string
*/
public static function replacePlaceHolders($query, $bindParams)
{
$namedParams = [];
foreach ($bindParams as $key => $value) {
if (!is_int($key)) {
unset($bindParams[$key]);
$namedParams[ltrim($key, ':')] = $value;
continue;
}
}
ksort($bindParams);
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
$query = preg_replace_callback(
'/:([a-z]+)/',
function ($matches) use ($namedParams) {
return array_key_exists(
$matches[1],
$namedParams
) ? self::bindValue($namedParams[$matches[1]]) : $matches[1];
},
$query
);
foreach ($bindParams as $param) {
/** @noinspection CallableParameterUseCaseInTypeContextInspection */
$query = preg_replace('/\?/', self::bindValue($param), $query, 1);
}
return $query;
}
/**
* Convert a bound value to a readable string
*
* @param mixed $val
*
* @return string
*/
private static function bindValue($val)
{
switch (gettype($val)) {
case 'NULL':
$val = 'NULL';
break;
case 'string':
$val = "'" . preg_replace('/(^|[^\'])\'/', "''", $val) . "'";
break;
case 'boolean':
$val = $val ? 'true' : 'false';
break;
default:
$val = (string)$val;
}
return $val;
}
/**
* Helper function to add variables into bind parameters array
*
* @param mixed $value Variable value
* @param string|null $key Variable key
*/
protected function bindParam($value, $key = null)
{
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
if ($key === null || is_numeric($key)) {
$this->bindParams[] = $value;
} else {
$this->bindParams[$key] = $value;
}
}
/**
* Helper function to add variables into bind parameters array in bulk
*
* @param array $values Variable with values
* @param bool $ignoreKey Whether array keys should be ignored when binding
*/
protected function bindParams($values, $ignoreKey = false)
{
foreach ($values as $key => $value) {
$this->bindParam($value, $ignoreKey ? null : $key);
}
}
/**
* Helper function to add variables into bind parameters array and will return
* its SQL part of the query according to operator in ' $operator ?'
*
* @param string $operator
* @param $value
*
* @return string
*/
protected function buildPair($operator, $value)
{
$this->bindParam($value);
return " $operator ? ";
}
/**
* Abstraction method that will build an JOIN part of the query
*/
protected function buildJoin()
{
if (empty($this->join)) {
return;
}
foreach ($this->join as $join) {
list($joinType, $joinTable, $joinCondition) = $join;
$quotedTableName = $this->quoteTableName($joinTable);
$this->query = rtrim($this->query) . " $joinType JOIN $quotedTableName ON $joinCondition";
}
}
protected static function escapeApostrophe($str)
{
return preg_replace('~(^|[^\'])\'~', '$1\'\'', $str);
}
/**
* Abstraction method that will build the part of the WHERE conditions
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
protected function buildWhere()
{
if (empty($this->where)) {
return;
}
//Prepare the where portion of the query
$this->query .= ' WHERE';
// $cond, $whereProp, $operator, $whereValue
foreach ($this->where as $where) {
list($cond, $whereProp, $operator, $whereValue) = $where;
if ($whereValue !== self::DBNULL) {
$whereProp = $this->quoteColumnName($whereProp);
}
$this->query = rtrim($this->query) . ' ' . trim("$cond $whereProp");
if ($whereValue === self::DBNULL) {
continue;
}
if (is_array($whereValue)) {
switch ($operator) {
case '!=':
$operator = 'NOT IN';
break;
case '=':
$operator = 'IN';
break;
}
}
switch ($operator) {
case 'NOT IN':
case 'IN':
if (!is_array($whereValue)) {
throw new InvalidArgumentException(
__METHOD__ . ' expects $whereValue to be an array when using IN/NOT IN'
);
}
/** @var $whereValue array */
foreach ($whereValue as $v) {
$this->bindParam($v);
}
$this->query .= " $operator (" . implode(', ', array_fill(0, count($whereValue), '?')) . ') ';
break;
case 'NOT BETWEEN':
case 'BETWEEN':
$this->query .= " $operator ? AND ? ";
$this->bindParams($whereValue, true);
break;
case 'NOT EXISTS':
case 'EXISTS':
$this->query .= $operator . $this->buildPair('', $whereValue);
break;
default:
if (is_array($whereValue)) {
$this->bindParams($whereValue);
} elseif ($whereValue === null) {
switch ($operator) {
case '!=':
$operator = 'IS NOT';
break;
case '=':
$operator = 'IS';
break;
}
$this->query .= " $operator NULL ";
} elseif ($whereValue !== self::DBNULL || $whereValue === 0 || $whereValue === '0') {
$this->query .= $this->buildPair($operator, $whereValue);
}
}
}
$this->query = rtrim($this->query);
}
/**
* Abstraction method that will build the RETURNING clause
*
* @param string|string[]|null $returning What column(s) to return
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
protected function buildReturning($returning)
{
if ($returning === null) {
return;
}
if (!is_array($returning)) {
$returning = array_map('trim', explode(',', $returning));
}
$this->returning = $returning;
$columns = [];
foreach ($returning as $column) {
$columns[] = $this->quoteColumnName($column, true);
}
$this->query .= ' RETURNING ' . implode(', ', $columns);
}
/**
* @param mixed[] $tableData
* @param string[] $tableColumns
* @param bool $isInsert
*
* @throws RuntimeException
*/
public function buildDataPairs($tableData, $tableColumns, $isInsert)
{
foreach ($tableColumns as $column) {
$value = $tableData[$column];
if (!$isInsert) {
$this->query .= "\"$column\" = ";
}
// Simple value
if (!is_array($value)) {
$this->bindParam($value);
$this->query .= '?, ';
continue;
}
if ($isInsert) {
$this->debug("Array passed as insert value for column $column");
throw new RuntimeException("Array passed as insert value for column $column");
}
$this->query .= '';
$in = [];
foreach ($value as $k => $v) {
if (is_int($k)) {
$this->bindParam($value);
$in[] = '?';
} else {
$this->bindParams[$k] = $value;
$in[] = ":$k";
}
}
$this->query = 'IN (' . implode(', ', $in) . ')';
}
$this->query = rtrim($this->query, ', ');
}
/**
* Abstraction method that will build an INSERT or UPDATE part of the query
*
* @param array $tableData
*
* @throws RuntimeException
* @throws InvalidArgumentException
*/
protected function buildInsertQuery($tableData)
{
if (!is_array($tableData)) {
return;
}
$isInsert = stripos($this->query, 'INSERT') === 0;
$dataColumns = array_keys($tableData);
if ($isInsert) {
$this->query .= ' (' . implode(', ', $this->quoteColumnNames($dataColumns)) . ') VALUES (';
} else {
$this->query .= ' SET ';
}
$this->buildDataPairs($tableData, $dataColumns, $isInsert);
if ($isInsert) {
$this->query .= ')';
}
}
/**
* Abstraction method that will build the GROUP BY part of the WHERE statement
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
protected function buildGroupBy()
{
if (empty($this->groupBy)) {
return;
}
$this->query .= ' GROUP BY ' . implode(', ', $this->quoteColumnNames($this->groupBy));
}
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
* @throws InvalidArgumentException
*/
protected function buildOrderBy()
{
if (empty($this->orderBy)) {
return;
}
$this->query .= ' ORDER BY ';
$order = [];
foreach ($this->orderBy as $column => $dir) {
$order[] = "$column $dir";
}
$this->query .= implode(', ', $order);
}
/**
* Abstraction method that will build the LIMIT part of the WHERE statement
*
* @param int|int[] $numRows An array to define SQL limit in format [$limit,$offset] or just $limit
*/
protected function buildLimit($numRows)
{
if ($numRows === null) {
return;
}
$this->query .= ' LIMIT ' . (
is_array($numRows)
? (int)$numRows[1] . ' OFFSET ' . (int)$numRows[0]
: (int)$numRows
);
}
/**
* Abstraction method that will compile the WHERE statement,
* any passed update data, and the desired rows.
* It then builds the SQL query.
*
* @param int|int[] $numRows Array to define SQL limit in format [$limit,$offset] or just $limit
* @param array $tableData Should contain an array of data for updating the database.
* @param string|string[]|null $returning What column(s) to return after inserting
*
* @return PDOStatement|bool Returns the $stmt object.
* @throws InvalidArgumentException
* @throws RuntimeException
*/
protected function buildQuery($numRows = null, $tableData = null, $returning = null)
{
$this->buildJoin();
$this->buildInsertQuery($tableData);
$this->buildWhere();
$this->buildReturning($returning);
$this->buildGroupBy();
$this->buildOrderBy();
$this->buildLimit($numRows);
$this->alterQuery($this->query);
$this->lastQuery = self::replacePlaceHolders($this->query, $this->bindParams);
return $this->prepareQuery();
}
/**
* Execute raw SQL query.
*
* @param string $query User-provided query to execute.
* @param array $bindParams Variables array to bind to the SQL statement.
*
* @return array|false Array containing the returned rows from the query or false on failure
* @throws RuntimeException
* @throws PDOException
*/
public function query($query, $bindParams = null)
{
$this->query = $query;
$this->alterQuery($this->query);
if($stmt = $this->prepareQuery()) {
if (empty($bindParams)) {
$this->bindParams = null;
} elseif (!is_array($bindParams)) {
$this->debug("$bindParams must be an array");
throw new RuntimeException('$bindParams must be an array');
} else {
$this->bindParams($bindParams);
}
if($this->debugging == TRUE)
{
$this->debug($query,'info');
}
$execute = $this->execStatement($stmt);
$this->insertid = $this->getConnection()->lastInsertId;
return $execute;
}
}
/**
* Execute a query with specified parameters and return a single row only
*
* @param string $query User-provided query to execute.
* @param array $bindParams Variables array to bind to the SQL statement.
*
* @return array
* @throws RuntimeException
* @throws PDOException
*/
public function querySingle($query, $bindParams = null)
{
return $this->singleRow($this->query($query, $bindParams));
}
/**
* Get number of rows in database table
*
* @param string $table Name of table
*
* @return int
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function count($table)
{
return $this->disableAutoClass()->getOne($table, 'COUNT(*) as cnt')['cnt'];
}
const DBNULL = '__DBNULL__';
/**
* This method allows you to specify multiple (method chaining optional) AND WHERE statements for SQL queries.
*
* @uses $db->where('id', 7)->where('title', 'MyTitle');
*
* @param string $whereProp The name of the database field.
* @param mixed $whereValue The value of the database field.
* @param string $operator
* @param string $cond
*
* @return self
*/
public function where($whereProp, $whereValue = self::DBNULL, $operator = '=', $cond = 'AND')
{
if (count($this->where) === 0) {
$cond = '';
}
$this->where[] = [$cond, $whereProp, strtoupper($operator), $whereValue];
return $this;
}
/**
* This method allows you to specify multiple (method chaining optional) OR WHERE statements for SQL queries.
*
* @uses $db->orWhere('id', 7)->orWhere('title', 'MyTitle');
*
* @param string $whereProp The name of the database field.
* @param mixed $whereValue The value of the database field.
* @param string $operator
*
* @return self
*/
public function orWhere($whereProp, $whereValue = self::DBNULL, $operator = '=')
{
return $this->where($whereProp, $whereValue, $operator, 'OR');
}
public function groupBy($groupByField)
{
$groupByField = preg_replace('/[^-a-z0-9\.\(\),_"\*]+/i', '', $groupByField);
$this->groupBy[] = $groupByField;
return $this;
}
/**
* This method allows you to specify multiple (method chaining optional) ORDER BY statements for SQL queries.
*
* @param string $orderByColumn
* @param string $orderByDirection
*
* @return self
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function orderBy($orderByColumn, $orderByDirection = 'ASC')
{
$orderByDirection = strtoupper(trim($orderByDirection));
$orderByColumn = $this->quoteColumnName($orderByColumn);
if (!is_string($orderByDirection) || !preg_match('~^(ASC|DESC)~', $orderByDirection)) {
throw new RuntimeException('Wrong order direction ' . $orderByDirection . ' on field ' . $orderByColumn);
}
$this->orderBy[$orderByColumn] = $orderByDirection;
return $this;
}
/**
* Replacement for the orderBy method, which would screw up complex order statements
*
* @param string $order Raw ordering sting
* @param string $direction Order direction
*
* @return self
*/
public function orderByLiteral($order, $direction = 'ASC')
{
$this->orderBy[$order] = $direction;
return $this;
}
/**
* A convenient SELECT * function.
*
* @param string $tableName The name of the database table to work with.
* @param int|int[] $numRows Array to define SQL limit in format [$limit,$offset] or just $limit
* @param string|array $columns
*
* @return array|false Contains the returned rows from the select query or false on failure
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function get($tableName, $numRows = null, $columns = null)
{
if (empty($columns)) {
$columns = '*';
} else {
if (!is_array($columns)) {
$columns = explode(',', $columns);
}
$columns = implode(', ', $this->quoteColumnNames($columns, true));
}
$table = $this->quoteTableName($tableName);
$this->query = "SELECT $columns FROM $table";
$stmt = $this->buildQuery($numRows);
if ($this->autoClassEnabled) {
$this->setTableName($tableName);
}
return $this->execStatement($stmt);
}
/**
* A convenient SELECT * function to get one record.
*
* @param string $tableName The name of the database table to work with.
* @param string $columns
*
* @return mixed|null
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function getOne($tableName, $columns = '*')
{
$res = $this->get($tableName, 1, $columns);
if (is_array($res) && isset($res[0])) {
return $res[0];
}
if ($res) {
return $res;
}
return null;
}
/**
* A convenient function that returns TRUE if exists at least an element that
* satisfy the where condition specified calling the "where" method before this one.
*
* @param string $tableName The name of the database table to work with.
*
* @return bool
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function has($tableName)
{
return $this->count($tableName) >= 1;
}
/**
* Update query. Be sure to first call the "where" method.
*
* @param string $tableName The name of the database table to work with.
* @param array $tableData Array of data to update the desired row.
*
* @return bool
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function update($tableName, $tableData)
{
$tableName = $this->quoteTableName($tableName);
$this->query = "UPDATE $tableName";
$stmt = $this->buildQuery(null, $tableData);
$this->debug($this->query,'info');
$res = $this->execStatement($stmt);
return (bool)$res;
}
/**
* Insert method to add a new row
*
* @param string $tableName The name of the table.
* @param array $insertData Data containing information for inserting into the DB.
* @param string|string[]|null $returnColumns Which columns to return
*
* @return mixed Boolean if $returnColumns is not specified, the returned columns' values otherwise
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function insert($tableName, $insertData, $returnColumns = null)
{
$this->disableAutoClass();
if ($this->autoClassEnabled) {
$this->setTableName($tableName);
}
$table = $this->quoteTableName($tableName);
$this->query = "INSERT INTO $table";
$stmt = $this->buildQuery(null, $insertData, $returnColumns);
$this->debug($this->query,'info');
$res = $this->execStatement($stmt, false);
$return = $this->returnWithReturning($res->fetchAll());
$this->reset();
return $return;
}
/**
* Delete query. Unless you want to "truncate" the table you should first @see where
*
* @param string $tableName The name of the database table to work with.
* @param string|string[]|null $returnColumns Which columns to return
*
* @return mixed Boolean if $returnColumns is not specified, the returned columns' values otherwise
* @throws InvalidArgumentException
* @throws PDOException
* @throws RuntimeException
*/
public function delete($tableName, $returnColumns = null)
{
if (!empty($this->join) || !empty($this->orderBy) || !empty($this->groupBy)) {
throw new RuntimeException(__METHOD__ . ' cannot be used with JOIN, ORDER BY or GROUP BY');
}
$this->disableAutoClass();
$table = $this->quoteTableName($tableName);
$this->query = "DELETE FROM $table";
$stmt = $this->buildQuery(null, null, $returnColumns);
$this->debug($this->query,'info');
$res = $this->execStatement($stmt, false);
$return = $this->returnWithReturning($res->fetch());
$this->reset();
return $return;
}
/**
* This method allows you to concatenate joins for the final SQL statement.
*
* @uses $db->join('table1', 'field1 <> field2', 'LEFT')
*
* @param string $joinTable The name of the table.
* @param string $joinCondition the condition.
* @param string $joinType 'LEFT', 'INNER' etc.
* @param bool $disableAutoClass Disable automatic result conversion to class
* (since result may contain data from other tables)
*
* @return self
* @throws RuntimeException
*/
public function join($joinTable, $joinCondition, $joinType = '', $disableAutoClass = true)
{
$allowedTypes = ['LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'];
$joinType = strtoupper(trim($joinType));
if ($joinType && !in_array($joinType, $allowedTypes, true)) {
throw new RuntimeException(__METHOD__ . ' expects argument 3 to be a valid join type');
}
$joinTable = $this->quoteTableName($joinTable);
$this->join[] = [$joinType, $joinTable, $joinCondition];
if ($disableAutoClass) {
$this->disableAutoClass();
}
return $this;
}
/**
* Method to check if a table exists
*
* @param string $table Table name to check
*
* @return boolean True if table exists
* @throws PDOException
* @throws RuntimeException
*/
public function tableExists($table)
{
$res = $this->querySingle("SELECT to_regclass('public.$table') IS NOT NULL as exists");
return $res['exists'];
}
/**
* Sets a class to be used as the PDO::fetchAll argument
*
* @param string $class
* @param int $type
*
* @return self
*/
public function setClass($class, $type = PDO::FETCH_CLASS)
{
$this->fetchType = $type;
$this->fetchArg = $class;
return $this;
}
/**
* Disabled the tableNameToClassName method
*
* @return self
*/
public function disableAutoClass()
{
$this->autoClassEnabled = false;
return $this;
}
/**
* @param PDOStatement $stmt Statement to execute
* @param boolean $reset Whether the object should be reset (must be done manually if set to false)
*
* @return array|false
* @throws PDOException
*/
protected function execStatement($stmt, $reset = true)
{
$this->lastQuery = $this->bindParams !== null
? self::replacePlaceHolders($this->query, $this->bindParams)
: $this->query;
try {
$success = $stmt->execute($this->bindParams);
} catch (PDOException $e) {
$this->stmtError = $e->getMessage();
$this->reset();
throw $e;
}
if ($success !== true) {
$this->count = 0;
$errInfo = $stmt->errorInfo();
$this->stmtError = "PDO Error #{$errInfo[1]}: {$errInfo[2]}";
$this->debug($this->stmtError);
$result = false;
} else {
$this->count = $this->num_rows = $stmt->rowCount();
$col_count = $stmt->columnCount();
$this->arr_fields = [];
for($i = 0; $i < $col_count; $i++) {
$this->arr_fields[$i] = $stmt->getColumnMeta($i);
}
$this->stmtError = null;
$this->result_fetch = $this->fetchArg !== null
? $stmt->fetchAll($this->fetchType, $this->fetchArg)
: $stmt->fetchAll($this->fetchType);
$result = $this;
}
if ($reset) {
$this->reset();
}
return $result;
}
public function reset()
{
$this->autoClassEnabled = true;
$this->tableName = null;
$this->fetchType = PDO::FETCH_ASSOC;
$this->fetchArg = null;
$this->where = [];
$this->join = [];
$this->orderBy = [];
$this->groupBy = [];
$this->bindParams = [];
$this->query = null;
$this->returning = null;
}
/**
* @param mixed $name
* @throws RuntimeException
*/
private function setTableName($name)
{
if (!is_string($name)) {
throw new RuntimeException('Argument $table_name must be string, ' . gettype($name) . ' given');
}
$this->tableName = $name;
}
/**
* Returns a boolean value if no data needs to be returned, otherwise returns the requested data
*
* @param mixed $res Result of an executed statement
* @return bool|mixed
*/
protected function returnWithReturning($res)
{
if ($res === false || $this->count < 1) {
return false;
}
if ($this->returning !== null) {
if (!is_array($res)) {
return false;
}
// If we got a single column to return then just return it
if (count($this->returning) === 1) {
return array_values($res[0])[0];
}
// If we got multiple, return the entire array
return $res[0];
}
return true;
}
/**
* Get the first entry in a query if it exists, otherwise, return null
*
* @param array|false $query Array containing the query results or false
*
* @return array|null
*/
protected function singleRow($query)
{
return $query === false || empty($query[0]) ? null : $query[0];
}
/**
* Adds quotes around table name for use in queries
*
* @param string $tableName
*
* @return string
*/
protected function quoteTableName($tableName)
{
return preg_replace('~^"?([a-zA-Z\d_\-]+)"?(?:\s*(\s[a-zA-Z\d]+))?$~', '"$1"$2', trim($tableName));
}
/**
* Adds quotes around column name for use in queries
*
* @param string $columnName
* @param bool $allowAs Controls whether "column as alias" can be used
*
* @return string
* @throws InvalidArgumentException
* @throws RuntimeException
*/
protected function quoteColumnName($columnName, $allowAs = false)
{
$columnAlias = '';
$columnName = trim($columnName);
$hasAs = preg_match('~\S\s+AS\s+\S~i', $columnName);
if ($allowAs && $hasAs) {
$values = $this->quoteColumnNames(preg_split('~\s+AS\s+~i', $columnName));
$columnName = $values[0];
$columnAlias = " AS $values[1]";
} elseif (!$allowAs && $hasAs) {
throw new InvalidArgumentException(
__METHOD__ . ": Column name ($columnName) contains disallowed AS keyword"
);
}
// JSON(B) access
if (strpos($columnName, '->>') !== false && preg_match(
'~^"?([a-z_\-\d]+)"?->>\'?([\w\-]+)\'?"?$~',
$columnName,
$match
)) {
$col = "\"$match[1]\"";
return $col . (!empty($match[2]) ? "->>'" . self::escapeApostrophe($match[2]) . "'" : '') . $columnAlias;
}
// Let's not mess with TOO complex column names (containing || or ')
if (strpos($columnName, '||') !== false || preg_match('~\'(?<!\\\\\')~', $columnName)) {
return $columnName . $columnAlias;
}
if (strpos($columnName, '.') !== false && preg_match($dotTest = '~\.(?<!\\\\\.)~', $columnName)) {
$split = preg_split($dotTest, $columnName);
if (count($split) > 2) {
throw new RuntimeException("Column $columnName contains more than one table separation dot");
}
return $this->quoteTableName($split[0]) . '.' . $this->quoteColumnName($split[1]) . $columnAlias;
}
$functionCallOrAsterisk = preg_match('~(^\w+\(|^\s*\*\s*$)~', $columnName);
$validColumnName = preg_match('~^(?=[a-z_])([a-z\d_]+)$~', $columnName);
$isSqlKeyword = isset($this->sqlKeywords[strtolower($columnName)]);
if (!$functionCallOrAsterisk && (!$validColumnName || $isSqlKeyword)) {
return '"' . trim($columnName, '"') . '"' . $columnAlias;
}
return $columnName . $columnAlias;
}
/**
* Adds quotes around column name for use in queries
*
* @param string[] $columnNames
* @param bool $allowAs
*
* @return string[]
* @throws InvalidArgumentException
* @throws RuntimeException
*/
protected function quoteColumnNames($columnNames, $allowAs = false)
{
foreach ($columnNames as $i => $columnName) {
$columnNames[$i] = $this->quoteColumnName($columnName, $allowAs);
}
return $columnNames;
}
/**
* Replaces some custom shortcuts to make the query valid
*/
protected function alterQuery($query)
{
$this->query = preg_replace('~(\s+)&&(\s+)~', '$1AND$2', $query);
}
/**
* Method returns last executed query
*
* @return string
*/
public function getLastQuery()
{
return $this->lastQuery;
}
/**
* Return last error message
*
* @return string
*/
public function getLastError()
{
if (!$this->connection) {
return 'No connection has been made yet';
}
return trim($this->stmtError);
}
/**
* Returns the class name expected for the table name
* This is a utility function for use in case you want to make your own
* automatic table<->class bindings using a wrapper class
*
* @param bool $hasNamespace
*
* @return string|null
*/
public function tableNameToClassName($hasNamespace = false)
{
$className = $this->tableName;
if (is_string($className)) {
$className = preg_replace(
'/s(_|$)/',
'$1',
preg_replace('/ies([-_]|$)/', 'y$1', preg_replace_callback('/(?:^|-)([a-z])/', function ($match) {
return strtoupper($match[1]);
}, $className))
);
$append = $hasNamespace ? '\\' : '';
$className = preg_replace_callback('/__?([a-z])/', function ($match) use ($append) {
return $append . strtoupper($match[1]);
}, $className);
}
return $className;
}
/**
* Close connection
*/
public function __destruct()
{
if ($this->connection) {
$this->connection = null;
}
}
private function makeDir($new_path, $mode) {
return is_dir($new_path) || mkdir($new_path, $mode, true);
}
public function debug($args,$type = 'debug') {
global $logdir;
// create a log channel
$logger = new Logger('query');
$daily_log = date('d-m-Y').'.log';
$year_dir = self::makeDir($logdir.'activity/'.date('Y'),0777);
$month_dir = self::makeDir($logdir.'activity/'.date('Y').'/'.date('m'),0777);
$dir_log = $logdir.'activity/'.date('Y').'/'.date('m').'/'.$daily_log;
$logger->pushHandler(new StreamHandler($dir_log, Logger::DEBUG));
$uri = $_SERVER['REQUEST_URI'];
$protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$query = $_SERVER['QUERY_STRING'];
if(!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip=$_SERVER['HTTP_CLIENT_IP']; // share internet
} elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip=$_SERVER['HTTP_X_FORWARDED_FOR']; // pass from proxy
} else {
$ip=$_SERVER['REMOTE_ADDR'];
}
$arr_log = [];
if($type == 'debug'){
$logger->addDebug($args,[$_SESSION['NAMA_PEGAWAI'],$url,$query,$ip]);
$arr_log['log_type'] = 'DEBUG';
}
elseif($type == 'info'){
$logger->addInfo($args,[$_SESSION['NAMA_PEGAWAI'],$url,$query,$ip]);
$arr_log['log_type'] = 'INFO';
}
$arr_log['log_description'] = $args;
$arr_log['user'] = $_SESSION['NAMA_PEGAWAI'];
$arr_log['ip_addr'] = $ip;
$arr_log['uri'] = $url;
$arr_log['uri_request'] = $query;
$arr_log['dt_logs'] = date('Y-m-d H:i:s');
// $this->debugging = FALSE;
// $tmp_log = $this->insert("t_logs",$arr_log);
}
public function fetchField() {
return $this->arr_fields;
}
public function fetchAll() {
return $this->result_fetch;
}
public function fetchFirst() {
$result = $this->result_fetch[0];
return $result;
}
public function fetchLast() {
$jml_data = count($this->result_fetch);
$result = $this->result_fetch[$jml_data-1];
return $result;
}
public function numRows() {
return $this->num_rows;
}
public function close() {
return $this->__destruct();
}
public function affectedRows() {
return $this->stmt->affected_rows;
}
public function escape($string) {
return $this->escapeApostrophe($string);
}
public function getError()
{
return $this->error;
}
public function fetch_field() {
return $this->arr_fields;
}
}