I am using a global class (like a global model) that acts like a sql abstraction layer for my application. I am using it without problems now, but since the application I am creating is getting bigger, I would like this class to be reviewed if this is really a fine abstrraction layer. I cannot use a proper ORM or framework at this time.
My models talk to the database. Therefore, they all inherit from global_model. I am using PHP's PDO and prepared statement facilities. I did not have functions for select queries because I found they can have different shapes and forms so I didn't implement them, I have no idea how. Therefore, all select statements, along with DDL statements ar executed directly in the subclasses. Here is my code.
spl_autoload_register(function($class_name) {
include $class_name . '.php';
});
abstract class global_model {
protected $con;
protected $table_name;
protected $id; // the id most used for where clause and update statements
protected $errors;
protected $data; // inputs from a form. This is an array like $_POST. The fields from $_POST are first validated in the controller for correct formatted data before being passed here.
public function __construct($data = null) { // null if not used for inserting or updating data
$this->con = database::connect();
$this->errors = array();
$this->con->exec("use " . DB); // I am using different databases.
if (isset($data)) {
$this->data = $data;
}
}
// $data is an array, usually fields gathered by $_POST after being validated for correct formatted data
public function set_data($data) {
$this->data = $data;
}
public function get_data() {
return $this->data;
}
// if called by subclass. Use $this->data as the record to be inserted, and $this->table_name for the table to insert to.
public function add() {
return $this->add_external($this->table_name, $this->data);
}
// this one is an agnostic function if no appropriate models exist.
public function add_external($table, $columns) {
$names = "";
$values = "";
foreach ($columns as $column => $value) {
$names .= "$column,";
$values .= "?,";
}
$names = rtrim($names,",");
$values = rtrim($values,",");
try {
$stmt = $this->con->prepare("insert into $table ($names) values($values)");
return $stmt->execute(array_values($columns));
} catch (Exception $e) {
if ($this->con->inTransaction()) {
throw $e;
}
$this->errors[] = "SQL failed: " . $e->getMessage();
return false;
}
}
// usually used in inserts with foreign keys
public function get_current_id() {
return $this->con->lastInsertId();
}
// if called by subclass. Use $this->data as the record to be edited, and $this->table_name for the table to edit to. The id use to edit is in $this->data['id'].
public function edit() {
return $this->edit_external($this->table_name, $this->data, $this->id); // $this->id is the name (not the value) of the columns used in the where clause for editing.
}
// this one is an agnostic function if no appropriate models exist. The argument $id_name is the name of the primary key column. For example, $id_name would be called user_id, and the value that will go into that column at this session is in $columns['id'].
public function edit_external($table, $columns, $id_name) {
// detach removes an item from an array and returns it
// in this case, we separate the id (the value of the primary key of the table).
$id = detach($columns, 'id');
$names = "";
foreach ($columns as $column => $value) {
$names .= "$column=?,";
}
$names = rtrim($names,",");
try {
$stmt = $this->con->prepare("update $table set $names where $id_name=?");
$values = array_values($columns);
$values[] = $id;
return $stmt->execute($values);
} catch (Exception $e) {
if ($this->con->inTransaction()) {
throw $e;
}
$this->errors[] = "SQL failed: " . $e->getMessage();
return false;
}
}
// just a quick function to edit everything.
public function edit_all($table, $columns) {
$names = "";
foreach ($columns as $column => $value) {
$names .= "$column=?,";
}
$names = rtrim($names,",");
$stmt = $this->con->prepare("update $table set $names");
return $stmt->execute(array_values($columns));
}
public function get_errors() {
return $this->errors;
}
// this just validates inputs from a form using a validator callback. I understand this function does not belong here. But I don't know where to put it and this function is used all the time while accessing the database.
public function validate_input($element, $validator = null, $message = null) {
$name = $element[0];
$value = $element[1];
if (!$validator($value)) {
$this->errors[] = $message == null ? "Please input a valid " . ucfirst(str_replace("_", " ", $name)) . "." : $message;
return false;
}
return true;
}
// I understand this function does not belong here. But I don't know where to put it and this function is used all the time while accessing the database.
public function validate_phone_number(){
return function($number) {
return (is_numeric($number) && (strlen($number) == 7 || strlen($number) == 11));
};
}
// I understand this function does not belong here. But I don't know where to put it and this function is used all the time while accessing the database.
public function validate_email_address() {
return function($var) {
return filter_var($var, FILTER_VALIDATE_EMAIL);
};
}
// $statements is a closure
public function exec_transaction($statements) {
try {
$this->con->beginTransaction();
$statements();
} catch (Exception $e) {
$this->con->rollback();
// this will be change to log the errors in the future.
$this->errors[] = "SQL failed: " . $e->getMessage();
return false;
}
return true;
}
// I understand this absolutely does not belong here, but many times uploading images and inserting/updating needs to be an atomic operation.
public function process_image($file_array) {
return (isset($file_array["image"])) ? substr(upload_image($file_array, "image", "profile"), strlen(DIR)) : "images/default_picture.png";
}
// all business logic and validations go here. This will be called by the controller every time a database operation will be performed.
abstract public function validate();
public function __destruct() {
database::disconnect();
}
}
Here is the class that manages the connections.
class database {
private static $con = null;
private static $host = "localhost";
private static $username = "root";
private static $password = "";
public static function connect() {
if (!self::$con) {
self::$con = new PDO("mysql:host=" . self::$host, self::$username, self::$password, array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION));
}
return self::$con;
}
public static function disconnect() {
if (self::$con) {
self::$con = null;
}
}
}
Although this works fine at this time, I believe my overall structure is extremely bad. How can I improve my code and include some other statements (particularly complicated selects and DDL statements)?