'int', 'name' => 'string'] * @var array */ static protected $table_fields = ['id' => 'int']; /** * Join some fields from other tables to variables in model * Example: * [ * [ * "field" => ["COUNT(*) AS keys_count"], // where keys_count is variable in the class * "join_table" => "table_name tb1 ON tb1.prod_id = obj.id" * ] * ] * @var array */ static protected $additional_fields = []; /** * Search fields with prefix (example obj.) * ['obj.name', 'obj.email'] * @var array */ static protected $search_fields = array(); static public $last_query = ''; static public $last_args_query = array(); private $func_total_count = 0; // Only for count() function public function __construct($args = array(), $has_prefix = false) { foreach ($args as $key => $value) { // Default SELECT which used in filter() or get() with field_{key} if ($has_prefix) if (str_starts_with($key, 'field_')) $key = substr($key, strlen('field_')); // If not exists in standart table field names, this field is not for this model (it's needed for LEFT JOIN with other models) if (!key_exists( $key, static::$table_fields )) { $this->{$key} = $value; continue; } // Exception only for id field if ($key == 'id') { $this->id = $value; continue; } if (gettype($value) == 'string') $value = trim($value); // Set type for variable if ($value === null) { // If $value has null, skip continue; } switch (static::$table_fields[$key]) { case 'int': $this->{'field_' . $key} = (int)$value; break; case 'float': $this->{'field_' . $key} = (float)$value; break; case 'DateTime': $this->{'field_' . $key} = new CustomDateTime($value); break; case 'bool'; $this->{'field_' . $key} = (bool)$value; break; case 'string': default: $this->{'field_' . $key} = (string)$value; break; } } } public function get_id() { return $this->id; } public function is_saved() { if (isset($this->id)) return true; else return false; } public static function init_table() { $result = db_query(''); return $result; } public static function get_table_name() { return static::$table_name; } protected static function get_additional_fields() { return static::$additional_fields; } private static function get_fields_for_select($additional_fields = array()) { $fields = array_keys(static::$table_fields); $result = ''; // Standart fields for ($i = 0; $i < count($fields); $i++) { $result .= 'obj.' . $fields[$i] . ' AS ' . 'field_' . $fields[$i]; // Place ', ' for every field excerpt last if ($i + 1 != count($fields)) $result .= ', '; } // Additionl fields $additional_fields += static::get_additional_fields(); if (!empty($additional_fields)) { $fields = array(); foreach ($additional_fields as $ad_field) { $fields = array_merge($ad_field['field'], $fields); } $add_fields_str = implode(', ', $fields); $result .= ', ' . $add_fields_str; } return $result; } /** * Collect fields from model as in the database * @return array */ protected function get_db_fields() { $result_fields = []; // Getting all the public properties of the object $class_fields = array_keys(get_object_vars($this)); foreach ($class_fields as $field) { // Checking for the 'field_' prefix if (str_starts_with($field, 'field_')) { // Check if $field has value if (!isset($this->{$field})) continue; // Removing the prefix and adding as a result $result_fields[] = substr($field, strlen('field_')); } } return $result_fields; } /** * Collect values from model as in the database * @return array */ protected function get_db_values() { $fields = $this->get_db_fields(); $values = []; foreach ($fields as $field) { $val = $this->{"field_$field"}; switch (gettype($val)) { case 'boolean': $val = (int)$val; break; } $values[] = $val; } return $values; } /** * Return some list by filter and sort * @param array $fields * @param array $sort_by * @param int $count * @param string $field_relation * @param int $offset * @param string $search * @param array $additional_fields like static::$additional_fields * @return array(self) */ static function filter( $fields = array(), $sort_by = array(), $count = 10, $field_relation = 'AND', $offset = 0, $search = '', $additional_fields = array() ) { /** * $fields = array( * [ * 'name' => 'obj.price' * 'type' => '>=' // default = * 'value' => 10 * ] * ) * $sort_by = ['-price', 'comments'] */ // Filter part $where = []; $params = []; $whereSql = ''; $having = []; $havingSql = ''; if (!empty($fields)) { foreach ($fields as $field) { // Having or Where $is_having = $field['is_having'] ?? false; // For null values if ($field['value'] === null) { $where[] = "{$field['name']} {$field['type']} NULL"; continue; } // For array and IN if ($field['type'] == 'IN') { if (!is_array($field['value'])) continue; $placeholders = implode( ', ', array_fill(0, count($field['value']), '?') ); $where[] = "{$field['name']} IN ({$placeholders})"; $params = array_merge($params, $field['value']); continue; } $condition = isset($field['type']) ? $field['type'] : '='; // use '=' as default if ($is_having) $having[] = "{$field['name']} {$condition} ?"; else $where[] = "{$field['name']} {$condition} ?"; if ($field['type'] != 'IN') $params[] = $field['value']; } $whereSql = implode(' ' . $field_relation . ' ', $where); if (!empty($having)) $havingSql = 'HAVING ' . implode(' ' . $field_relation . ' ', $having); } // Addition JOIN fields from other tables $join_table = ''; $additional_fields = array_merge($additional_fields, static::get_additional_fields()); if (!empty($additional_fields)) { $tables = array(); foreach ($additional_fields as $val) { if (!isset($val['join_table'])) continue; $tables[] = 'LEFT JOIN ' . $val['join_table']; } $join_table = implode(' ', $tables); } // Search part $whereSearchSql = []; if (!empty($search)) { foreach (static::$search_fields as $field_name) { $whereSearchSql[] = "{$field_name} LIKE ?"; $params[] = "%" . $search . "%"; } $whereSearchSql = implode(' OR ', $whereSearchSql); }; // Search and filter in WHERE if (!empty($whereSql) && !empty($whereSearchSql)) $whereSql = 'WHERE (' . $whereSql . ') AND (' . $whereSearchSql . ')'; elseif (!empty($whereSql)) $whereSql = 'WHERE (' . $whereSql . ')'; elseif (!empty($whereSearchSql)) $whereSql = 'WHERE (' . $whereSearchSql . ')'; // Sort part $sortSql = ''; $sort_by[] = 'obj.id'; // Sort by id on the end if (!empty($sort_by)) { $sortSql = 'ORDER BY ' . implode(', ', array_map(function ($sort) { return ltrim($sort, '-') . ' ' . (strpos($sort, '-') === 0 ? 'DESC' : 'ASC'); }, $sort_by)); } // Limit part $limit = "LIMIT " . (int) $count; $offsetSql = "OFFSET " . (int)$offset; // Fields to select from db $select_fields = static::get_fields_for_select($additional_fields); // Create full sql query $sql = "SELECT " . $select_fields . " FROM " . static::$table_name . " obj " . $join_table . " " . $whereSql . " GROUP BY obj.id " . $havingSql . " " . $sortSql . " " . $limit . " " . $offsetSql; static::$last_query = $sql; static::$last_args_query = $params; $result = db_prepare($sql, $params); // Return result as objects or mysqli_result return static::createObjectsFromQuery($result); } /** * Return first row from db * @param array $fields * @return static|false */ static function get($fields, $sort_by = array(), $field_relation = 'AND') { $result = static::filter( $fields, $sort_by, 1, $field_relation ); if (count($result) > 0) return $result[0]; else return false; } static function count($fields, $search = '', $additional_fields = array()) { $filter_result = static::filter( $fields, array(), 1, 'AND', 0, $search, array_merge($additional_fields, array( [ 'field' => ['COUNT(*) OVER () AS func_total_count'] ] )) ); if (empty($filter_result)) return 0; else return $filter_result[0]->func_total_count; } public function getAssocArr() { return get_object_vars($this); } public function delete() { // Object is not in the table if (!isset($this->id)) return false; $sql = "DELETE FROM " . static::$table_name . " WHERE id = ?"; db_prepare($sql, [$this->id]); } protected function after_save() {} public function save() { global $pdo; // Check is valid fields or not if ($this->valid() !== true) throw new ValidationError($this->valid()); // Collect field and valus from fields in the class $fields = $this->get_db_fields(); $values = $this->get_db_values(); // Create SQL Prepare if (isset($this->id)) { // If we have id, then model is already in database // SQL for UPDATE .. SET ... $set_clause = implode( ', ', array_map( fn($field) => "$field = ?", $fields ) ); // Query and values $query = 'UPDATE ' . static::$table_name . ' SET ' . $set_clause . ' WHERE id = ?'; $query_values = $values; $query_values[] = $this->id; } else { // if we don't have id (not in db), we create a new row // placeholder for SQL: ?, ?, ?, ?,... $placeholders = implode(', ', array_fill(0, count($fields), '?')); // list for SQL Insert $field_list = implode(', ', $fields); // Query and values $query = 'INSERT INTO ' . static::$table_name . ' (' . $field_list . ') VALUES (' . $placeholders . ')'; $query_values = $values; } db_prepare($query, $query_values); if ($this->id === null) { $this->id = $pdo->lastInsertId(); } $this->after_save(); } /** * Check fields. Return true or array with string errors * @return array|bool */ public function valid() { return true; } /** * Return model from Mysql result * @param array $pdo_result pdo resut FETCH_MODE = FETCH_ASSOC * @return array(self) */ protected static function createObjectsFromQuery(array $pdo_result) { $models = []; foreach ($pdo_result as $row) { $models[] = new static($row, true); } return $models; } }