| | |
| | | namespace JVBase\managers; |
| | | |
| | | use Exception; |
| | | use JVBase\inc\managers\ErrorHelper; |
| | | use wpdb; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | */ |
| | | class CustomTable |
| | | { |
| | | |
| | | use ErrorHelper; |
| | | private static ?wpdb $wpdb = null; |
| | | private static string $prefix; |
| | | protected string $tableName; |
| | | protected bool $table_exists = false; |
| | | protected string $definition; |
| | | protected string $fullTableName; |
| | | protected bool $useTransactions; |
| | |
| | | |
| | | /** |
| | | * Fluent factory method |
| | | * |
| | | * @param string $tableName Table name without prefix/BASE |
| | | * @param string $tableName |
| | | * @param bool $user |
| | | * @param bool $auth |
| | | * @param bool $useTransactions |
| | | * @return self |
| | | * |
| | | * @example CustomTable::for('favourites')->insert($data); |
| | | */ |
| | | public static function for(string $tableName, bool $user = false, bool $auth = false, bool $useTransactions = false): self |
| | | { |
| | | if (!isset(self::$instances[$tableName])) { |
| | | self::$instances[$tableName] = new self($tableName, $user, $auth, $useTransactions); |
| | | self::$instances[$tableName]->setFlags(); |
| | | } |
| | | |
| | | |
| | | return self::$instances[$tableName]; |
| | | } |
| | | protected function setFlags():void |
| | | { |
| | | $this->table_exists = !is_null(self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")); |
| | | } |
| | | |
| | | public static function destroyInstance(string $tableName):void |
| | | { |
| | |
| | | require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| | | |
| | | foreach (self::$instances as $instance) { |
| | | if (!$instance->wpdb->get_var("SHOW TABLES LIKE '{$instance->fullTableName}'")) { |
| | | if (!$instance->table_exists) { |
| | | $instance->createTable(); |
| | | } |
| | | } |
| | |
| | | error_log('[CustomTable]No definition set for '.$this->tableName); |
| | | return; |
| | | } |
| | | if (self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) { |
| | | if ($this->table_exists) { |
| | | return; |
| | | } |
| | | require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); |
| | |
| | | $error_msg = "SQL Error creating table {$this->tableName}: " . self::$wpdb->last_error; |
| | | error_log($error_msg); |
| | | error_log("Failed SQL Query: " . $schema); |
| | | } elseif (self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) { |
| | | } else { |
| | | error_log("Successfully created table: {$this->fullTableName}"); |
| | | } |
| | | } |
| | |
| | | ); |
| | | |
| | | if ($result === false) { |
| | | $this->logError('insert', $data); |
| | | $this->logError(self::$wpdb->last_error, 'insert', $data); |
| | | return false; |
| | | } |
| | | |
| | |
| | | |
| | | /** |
| | | * Find or create a record |
| | | * |
| | | * @param array $searchData Data to search for |
| | | * @param array $createData Optional additional data for creation |
| | | * @return array ['id' => int, 'created' => bool, 'record' => object] |
| | | * @return int|false The ID of the created record, or false |
| | | * |
| | | * @example |
| | | * $result = CustomTable::for('favourites')->findOrCreate( |
| | | * ['user_id' => 1, 'target_id' => 123], |
| | | * ['type' => 'tattoo'] |
| | | * ); |
| | | * // Returns: ['id' => 456, 'created' => false, 'record' => object] |
| | | * $result = CustomTable::for('favourites')->findOrCreate( |
| | | * ['user_id' => 1, 'target_id' => 123], |
| | | * ['type' => 'tattoo'] |
| | | * ); |
| | | */ |
| | | public function findOrCreate(array $searchData, array $createData = []):int|false |
| | | { |
| | |
| | | $result = self::$wpdb->query(self::$wpdb->prepare($query, $values)); |
| | | |
| | | if ($result === false) { |
| | | $this->logError('bulkInsert', ['rows' => count($rows)]); |
| | | $this->logError(self::$wpdb->last_error, 'bulkInsert', ['rows' => count($rows)]); |
| | | return false; |
| | | } |
| | | |
| | |
| | | |
| | | /** |
| | | * Get multiple records |
| | | * |
| | | * @param array $args Query arguments: where, orderby, order, limit, offset |
| | | * @param bool $itemsOnly |
| | | * @param string $output OBJECT, ARRAY_A, or ARRAY_N |
| | | * @return array |
| | | * |
| | | * @example |
| | | * $favs = $table->getMany([ |
| | | * 'where' => ['user_id' => 1], |
| | | * 'orderby' => 'date_added', |
| | | * 'order' => 'DESC', |
| | | * 'per_page' => 20 |
| | | * ]); |
| | | * $favs = $table->getMany([ |
| | | * 'where' => ['user_id' => 1], |
| | | * 'orderby' => 'date_added', |
| | | * 'order' => 'DESC', |
| | | * 'per_page' => 20 |
| | | * ]); |
| | | */ |
| | | public function getMany(array $args = [], bool $itemsOnly = true, string $output = OBJECT): array |
| | | { |
| | |
| | | return sanitize_sql_orderby($orderby); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Get a specific column value from all matches |
| | | * @param string $column |
| | | * @param array $where |
| | | * @param string|null $orderby |
| | | * @param string|null $order |
| | | * @param int|null $limit |
| | | * @return array |
| | | */ |
| | | public function pluck(string $column, array $where = [], ?string $orderby = null, ?string $order = null, ?int $limit = null): array |
| | |
| | | $key, |
| | | function() use ($column, $where, $orderby, $order, $limit) { |
| | | if (!empty($this->columns) && !array_key_exists($column, $this->columns)) { |
| | | $this->logError('pluck', ['reason' => "Column '{$column}' not in definition"]); |
| | | $this->logError("Column '{$column}' not in definition", 'pluck'); |
| | | return []; |
| | | } |
| | | $args = ['where' => $where]; |
| | |
| | | ); |
| | | |
| | | if ($result === false) { |
| | | $this->logError('update', ['data' => $data, 'where' => $where]); |
| | | $this->logError(self::$wpdb->last_error,'update', ['data' => $data, 'where' => $where]); |
| | | } |
| | | $this->cache->flush(); |
| | | |
| | |
| | | ); |
| | | |
| | | if ($result === false) { |
| | | $this->logError('delete', ['where' => $where]); |
| | | $this->logError(self::$wpdb->last_error, 'delete', ['where' => $where]); |
| | | } |
| | | $this->cache->flush(); |
| | | |
| | |
| | | return $result; |
| | | } catch (Exception $e) { |
| | | $this->rollback(); |
| | | $this->logError('transaction', ['error' => $e->getMessage()]); |
| | | $this->logError(self::$wpdb->last_error,'transaction', ['error' => $e->getMessage()]); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | return isset($cache[$this->tableName][$column]); |
| | | } |
| | | |
| | | /** |
| | | * Log database errors |
| | | */ |
| | | private function logError(string $operation, array $context = []): void |
| | | { |
| | | if (function_exists('JVB')) { |
| | | Errors::getInstance()->log( |
| | | $this->tableName, |
| | | "CustomTable {$operation} failed: " . self::$wpdb->last_error, |
| | | $context |
| | | ); |
| | | } else { |
| | | error_log("[CustomTable:{$this->tableName}] {$operation} failed: " . self::$wpdb->last_error); |
| | | } |
| | | } |
| | | |
| | | public function sum(string $column, array $where):float |
| | | { |
| | | $rows = $this->pluck($column, $where); |