Sprint 1.2: Db.php erweitert - query, insert, update, delete, execute, executeS, getRow, getValue, numRows, escape und weitere Core-Datenbankfunktionen implementiert

This commit is contained in:
thomas 2025-07-06 21:51:08 +02:00
parent dcba6b6132
commit cd4744d362
2 changed files with 693 additions and 7 deletions

View File

@ -10,8 +10,8 @@
- [x] Context.php Erweiterung (getContext, cloneContext, updateCustomer, getTranslator, getTranslatorFromLocale, getComputingPrecision, Device-Erkennung, Mobile-Erkennung)
- [x] Cart.php Erweiterung (nbProducts, getNbProducts, addCartRule, getProductQuantity, updateQty, deleteProduct, getOrderTotal, getTotalWeight, isVirtualCart, hasProducts, hasRealProducts, getCarrierCost, getGiftWrappingPrice, lastNoneOrderedCart, getCustomerCarts, checkQuantities, getProducts, getDiscounts)
### Sprint 1.2: Datenbank & ORM (0% abgeschlossen)
- [ ] Db.php Erweiterung
### Sprint 1.2: Datenbank & ORM (25% abgeschlossen)
- [x] Db.php Erweiterung (query, insert, update, delete, execute, executeS, getRow, getValue, numRows, escape, checkConnection, checkEncoding, hasTableWithSamePrefix, checkCreatePrivilege, checkSelectPrivilege)
- [ ] ObjectModel.php Erweiterung
- [ ] Database Schema Erweiterung
- [ ] Migration System
@ -122,10 +122,10 @@
- [ ] Monitoring Setup
- [ ] Backup Setup
## Gesamtfortschritt: 17% (1 von 18 Sprints abgeschlossen)
## Gesamtfortschritt: 19% (1.25 von 18 Sprints abgeschlossen)
### Nächste Schritte:
1. Sprint 1.2 beginnen (Datenbank & ORM)
2. Db.php Erweiterung
3. ObjectModel.php Erweiterung
4. Database Schema Erweiterung
1. ObjectModel.php Erweiterung
2. Database Schema Erweiterung
3. Migration System
4. Sprint 1.2 abschließen

686
classes/Db.php Normal file
View File

@ -0,0 +1,686 @@
<?php
/**
* Database abstraction layer
*/
abstract class Db
{
/** @var int Constant used by insert() method */
public const INSERT = 1;
/** @var int Constant used by insert() method */
public const INSERT_IGNORE = 2;
/** @var int Constant used by insert() method */
public const REPLACE = 3;
/** @var int Constant used by insert() method */
public const ON_DUPLICATE_KEY = 4;
/** @var string Server (eg. localhost) */
protected $server;
/** @var string Database user (eg. root) */
protected $user;
/** @var string Database password */
protected $password;
/** @var string Database name */
protected $database;
/** @var bool */
protected $is_cache_enabled;
/** @var PDO|mysqli|resource|null Resource link */
protected $link;
/** @var PDOStatement|mysqli_result|resource|bool SQL cached result */
protected $result;
/** @var array List of DB instances */
public static $instance = [];
/** @var array List of server settings */
public static $_servers = [];
/** @var bool|null Flag used to load slave servers only once */
public static $_slave_servers_loaded = null;
/** @var string Store last executed query */
protected $last_query;
/** @var string Store hash of the last executed query */
protected $last_query_hash;
/** @var bool Last cached query */
protected $last_cached;
/**
* Opens a database connection
*
* @return PDO|mysqli|resource
*/
abstract public function connect();
/**
* Closes database connection
*/
abstract public function disconnect();
/**
* Execute a query and get result resource
*
* @param string $sql
* @return PDOStatement|mysqli_result|resource|bool
*/
abstract protected function _query($sql);
/**
* Get number of rows in a result
*
* @param mixed $result
* @return int
*/
abstract protected function _numRows($result);
/**
* Get the ID generated from the previous INSERT operation
*
* @return int|string
*/
abstract public function Insert_ID();
/**
* Get number of affected rows in previous database operation
*
* @return int
*/
abstract public function Affected_Rows();
/**
* Get next row for a query which does not return an array
*
* @param PDOStatement|mysqli_result|resource|bool $result
* @return array|object|false|null
*/
abstract public function nextRow($result = false);
/**
* Get all rows for a query which return an array
*
* @param PDOStatement|mysqli_result|resource|bool|null $result
* @return array
*/
abstract protected function getAll($result = false);
/**
* Get database version
*
* @return string
*/
abstract public function getVersion();
/**
* Protect string against SQL injections
*
* @param string $str
* @return string
*/
abstract public function _escape($str);
/**
* Returns the text of the error message from previous database operation
*
* @return string
*/
abstract public function getMsgError();
/**
* Returns the number of the error from previous database operation
*
* @return int
*/
abstract public function getNumberError();
/**
* Sets the current active database
*
* @param string $db_name
* @return bool|int
*/
abstract public function set_db($db_name);
/**
* Selects best table engine
*
* @return string
*/
abstract public function getBestEngine();
/**
* Get singleton instance
*
* @param bool $master
* @return Db
*/
public static function getInstance($master = true)
{
$id = $master ? 0 : 1;
if (!isset(self::$instance[$id])) {
$class = self::getClass();
self::$instance[$id] = new $class(
_DB_SERVER_,
_DB_USER_,
_DB_PASSWD_,
_DB_NAME_,
true
);
}
return self::$instance[$id];
}
/**
* Get database class
*
* @return string
*/
public static function getClass()
{
$class = 'DbPDO';
if (class_exists($class)) {
return $class;
}
return 'DbMySQLi';
}
/**
* Constructor
*
* @param string $server
* @param string $user
* @param string $password
* @param string $database
* @param bool $connect
*/
public function __construct($server, $user, $password, $database, $connect = true)
{
$this->server = $server;
$this->user = $user;
$this->password = $password;
$this->database = $database;
$this->is_cache_enabled = _PS_CACHE_ENABLED_;
if ($connect) {
$this->connect();
}
}
/**
* Disable cache
*/
public function disableCache()
{
$this->is_cache_enabled = false;
}
/**
* Enable cache
*/
public function enableCache()
{
$this->is_cache_enabled = true;
}
/**
* Execute query
*
* @param string $sql
* @return bool
*/
public function query($sql)
{
if ($sql instanceof DbQuery) {
$sql = $sql->build();
}
$this->result = $this->_query($sql);
if (!$this->result && $this->getNumberError() == 2006) {
$this->connect();
$this->result = $this->_query($sql);
}
if (_PS_DEBUG_SQL_) {
$this->displayError($sql);
}
return $this->result;
}
/**
* Execute INSERT query
*
* @param string $table
* @param array $data
* @param bool $null_values
* @param bool $use_cache
* @param int $type
* @param bool $add_prefix
* @return bool
*/
public function insert($table, $data, $null_values = false, $use_cache = true, $type = self::INSERT, $add_prefix = true)
{
if (!$data && !$null_values) {
return true;
}
if ($add_prefix) {
$table = _DB_PREFIX_ . $table;
}
if ($type == self::INSERT) {
$insert_keyword = 'INSERT';
} elseif ($type == self::INSERT_IGNORE) {
$insert_keyword = 'INSERT IGNORE';
} elseif ($type == self::REPLACE) {
$insert_keyword = 'REPLACE';
} elseif ($type == self::ON_DUPLICATE_KEY) {
$insert_keyword = 'INSERT';
} else {
throw new Exception('Bad keyword, must be Db::INSERT or Db::INSERT_IGNORE or Db::REPLACE or Db::ON_DUPLICATE_KEY');
}
// Check if $data is a list of row
$current = current($data);
if (!is_array($current) || isset($current['type'])) {
$data = [$data];
}
$keys = [];
$values_stringified = [];
$first_loop = true;
$duplicate_key_stringified = '';
foreach ($data as $row_data) {
$values = [];
foreach ($row_data as $key => $value) {
if (!$first_loop) {
if (!in_array("`$key`", $keys)) {
throw new Exception('Keys form $data subarray don\'t match');
}
if ($duplicate_key_stringified != '') {
throw new Exception('On duplicate key cannot be used on insert with more than 1 VALUE group');
}
} else {
$keys[] = '`' . $this->escape($key) . '`';
}
if (!is_array($value)) {
$value = ['type' => 'text', 'value' => $value];
}
if ($value['type'] == 'sql') {
$values[] = $string_value = $value['value'];
} else {
$values[] = $string_value = $null_values && ($value['value'] === '' || null === $value['value']) ? 'NULL' : "'{$value['value']}'";
}
if ($type == self::ON_DUPLICATE_KEY) {
$duplicate_key_stringified .= '`' . $this->escape($key) . '` = ' . $string_value . ',';
}
}
$first_loop = false;
$values_stringified[] = '(' . implode(', ', $values) . ')';
}
$keys_stringified = implode(', ', $keys);
$sql = $insert_keyword . ' INTO `' . $table . '` (' . $keys_stringified . ') VALUES ' . implode(', ', $values_stringified);
if ($type == self::ON_DUPLICATE_KEY) {
$sql .= ' ON DUPLICATE KEY UPDATE ' . substr($duplicate_key_stringified, 0, -1);
}
return (bool) $this->q($sql, $use_cache);
}
/**
* Execute UPDATE query
*
* @param string $table
* @param array $data
* @param string $where
* @param int $limit
* @param bool $null_values
* @param bool $use_cache
* @param bool $add_prefix
* @return bool
*/
public function update($table, $data, $where = '', $limit = 0, $null_values = false, $use_cache = true, $add_prefix = true)
{
if (!$data) {
return true;
}
if ($add_prefix) {
$table = _DB_PREFIX_ . $table;
}
$sql = 'UPDATE `' . $this->escape($table) . '` SET ';
foreach ($data as $key => $value) {
if (!is_array($value)) {
$value = ['type' => 'text', 'value' => $value];
}
if ($value['type'] == 'sql') {
$sql .= '`' . $this->escape($key) . "` = {$value['value']},";
} else {
$sql .= ($null_values && ($value['value'] === '' || null === $value['value'])) ? '`' . $this->escape($key) . '` = NULL,' : '`' . $this->escape($key) . "` = '{$value['value']}',";
}
}
$sql = rtrim($sql, ',');
if ($where) {
$sql .= ' WHERE ' . $where;
}
if ($limit) {
$sql .= ' LIMIT ' . (int) $limit;
}
return (bool) $this->q($sql, $use_cache);
}
/**
* Execute DELETE query
*
* @param string $table
* @param string $where
* @param int $limit
* @param bool $use_cache
* @param bool $add_prefix
* @return bool
*/
public function delete($table, $where = '', $limit = 0, $use_cache = true, $add_prefix = true)
{
if ($add_prefix) {
$table = _DB_PREFIX_ . $table;
}
$this->result = false;
$sql = 'DELETE FROM `' . $this->escape($table) . '`' . ($where ? ' WHERE ' . $where : '') . ($limit ? ' LIMIT ' . (int) $limit : '');
$res = $this->query($sql);
if ($use_cache && $this->is_cache_enabled) {
Cache::getInstance()->deleteQuery($sql);
}
return (bool) $res;
}
/**
* Execute query
*
* @param string|DbQuery $sql
* @param bool $use_cache
* @return bool
*/
public function execute($sql, $use_cache = true)
{
if ($sql instanceof DbQuery) {
$sql = $sql->build();
}
$this->result = $this->query($sql);
if ($use_cache && $this->is_cache_enabled) {
Cache::getInstance()->deleteQuery($sql);
}
return (bool) $this->result;
}
/**
* Execute query and return all results
*
* @param string|DbQuery $sql
* @param bool $array
* @param bool $use_cache
* @return array
*/
public function executeS($sql, $array = true, $use_cache = true)
{
if ($sql instanceof DbQuery) {
$sql = $sql->build();
}
$this->result = $this->query($sql);
if (!$this->result) {
return [];
}
$result = $this->getAll($this->result);
if ($use_cache && $this->is_cache_enabled) {
Cache::getInstance()->deleteQuery($sql);
}
return $result;
}
/**
* Get single row
*
* @param string|DbQuery $sql
* @param bool $use_cache
* @return array|false
*/
public function getRow($sql, $use_cache = true)
{
if ($sql instanceof DbQuery) {
$sql = $sql->build();
}
$this->result = $this->query($sql);
if (!$this->result) {
return false;
}
$result = $this->nextRow($this->result);
if ($use_cache && $this->is_cache_enabled) {
Cache::getInstance()->deleteQuery($sql);
}
return $result;
}
/**
* Get single value
*
* @param string|DbQuery $sql
* @param bool $use_cache
* @return string|false
*/
public function getValue($sql, $use_cache = true)
{
if ($sql instanceof DbQuery) {
$sql = $sql->build();
}
$this->result = $this->query($sql);
if (!$this->result) {
return false;
}
$result = $this->nextRow($this->result);
if ($use_cache && $this->is_cache_enabled) {
Cache::getInstance()->deleteQuery($sql);
}
if (!$result) {
return false;
}
return reset($result);
}
/**
* Get number of rows
*
* @return int
*/
public function numRows()
{
if (!$this->result) {
return 0;
}
return $this->_numRows($this->result);
}
/**
* Protected query method
*
* @param string $sql
* @param bool $use_cache
* @return bool
*/
protected function q($sql, $use_cache = true)
{
$this->result = $this->query($sql);
if ($use_cache && $this->is_cache_enabled) {
Cache::getInstance()->deleteQuery($sql);
}
return (bool) $this->result;
}
/**
* Display error
*
* @param bool|string $sql
*/
public function displayError($sql = false)
{
if (_PS_DEBUG_SQL_) {
$error = $this->getMsgError();
if ($sql) {
$error .= ' - SQL: ' . $sql;
}
echo '<pre>' . $error . '</pre>';
}
}
/**
* Escape string
*
* @param string $string
* @param bool $html_ok
* @param bool $bq_sql
* @return string
*/
public function escape($string, $html_ok = false, $bq_sql = false)
{
if (_PS_MAGIC_QUOTES_GPC_) {
$string = stripslashes($string);
}
if (!$html_ok) {
$string = strip_tags(Tools::nl2br($string));
}
if (!$bq_sql) {
$string = str_replace('`', '\`', $string);
}
return $this->_escape($string);
}
/**
* Check database connection
*
* @param string $server
* @param string $user
* @param string $pwd
* @param string $db
* @param bool $new_db_link
* @param string|null $engine
* @param int $timeout
* @return bool
*/
public static function checkConnection($server, $user, $pwd, $db, $new_db_link = true, $engine = null, $timeout = 5)
{
$class = self::getClass();
return $class::checkConnection($server, $user, $pwd, $db, $new_db_link, $engine, $timeout);
}
/**
* Check encoding
*
* @param string $server
* @param string $user
* @param string $pwd
* @return bool
*/
public static function checkEncoding($server, $user, $pwd)
{
$class = self::getClass();
return $class::checkEncoding($server, $user, $pwd);
}
/**
* Check if table with same prefix exists
*
* @param string $server
* @param string $user
* @param string $pwd
* @param string $db
* @param string $prefix
* @return bool
*/
public static function hasTableWithSamePrefix($server, $user, $pwd, $db, $prefix)
{
$class = self::getClass();
return $class::hasTableWithSamePrefix($server, $user, $pwd, $db, $prefix);
}
/**
* Check create privilege
*
* @param string $server
* @param string $user
* @param string $pwd
* @param string $db
* @param string $prefix
* @param string|null $engine
* @return bool
*/
public static function checkCreatePrivilege($server, $user, $pwd, $db, $prefix, $engine = null)
{
$class = self::getClass();
return $class::checkCreatePrivilege($server, $user, $pwd, $db, $prefix, $engine);
}
/**
* Check select privilege
*
* @param string $server
* @param string $user
* @param string $pwd
* @param string $db
* @param string $prefix
* @param string|null $engine
* @return bool
*/
public static function checkSelectPrivilege($server, $user, $pwd, $db, $prefix, $engine = null)
{
$class = self::getClass();
return $class::checkSelectPrivilege($server, $user, $pwd, $db, $prefix, $engine);
}
/**
* Get link
*
* @return PDO|mysqli|resource|null
*/
public function getLink()
{
return $this->link;
}
}