model.php
198 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Base class for models |
| 4 | * |
| 5 | * @author Pavel Kulbakin <p.kulbakin@gmail.com> |
| 6 | */ |
| 7 | abstract class PMXE_Model extends ArrayObject { |
| 8 | /** |
| 9 | * WPDB instance |
| 10 | * @var wpdb |
| 11 | */ |
| 12 | protected $wpdb; |
| 13 | /** |
| 14 | * Table name the model is linked to |
| 15 | * @var string |
| 16 | */ |
| 17 | protected $table; |
| 18 | /** |
| 19 | * Array of columns representing primary key |
| 20 | * @var array |
| 21 | */ |
| 22 | protected $primary = array('id'); |
| 23 | /** |
| 24 | * Wether key field is auto_increment (sure make scence only if key s |
| 25 | * @var bool |
| 26 | */ |
| 27 | protected $auto_increment = FALSE; |
| 28 | |
| 29 | /** |
| 30 | * Cached data retrieved from database |
| 31 | * @var array |
| 32 | */ |
| 33 | private static $meta_cache = array(); |
| 34 | |
| 35 | /** |
| 36 | * Initialize model |
| 37 | * @param array[optional] $data Array of record data to initialize object with |
| 38 | */ |
| 39 | public function __construct() { |
| 40 | $this->wpdb = $GLOBALS['wpdb']; |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * Read records from database by specified fields and values |
| 45 | * When 1st parameter is an array, it expected to be an associative array of field => value pairs to read data by |
| 46 | * If 2 parameters are set, first one is expected to be a field name and second - it's value |
| 47 | * |
| 48 | * @param string|array $field |
| 49 | * @param mixed[optional] $value |
| 50 | * @return PMXE_Model |
| 51 | */ |
| 52 | abstract public function getBy($field = NULL, $value = NULL); |
| 53 | |
| 54 | /** |
| 55 | * Magic function to automatically resolve calls like $obj->getBy%FIELD_NAME% |
| 56 | * @param string $method |
| 57 | * @param array $args |
| 58 | * @return PMXE_Model |
| 59 | */ |
| 60 | public function __call($method, $args) { |
| 61 | if (preg_match('%^get_?by_?(.+)%i', $method, $mtch)) { |
| 62 | array_unshift($args, $mtch[1]); |
| 63 | return call_user_func_array(array($this, 'getBy'), $args); |
| 64 | } else { |
| 65 | throw new Exception("Requested method " . get_class($this) . "::$method doesn't exist."); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Bind model to database table |
| 71 | * @param string $tableName |
| 72 | * @return PMXE_Model |
| 73 | */ |
| 74 | public function setTable($tableName) { |
| 75 | if ( ! is_null($this->table)) { |
| 76 | throw new Exception('Table name cannot be changed once being set.'); |
| 77 | } |
| 78 | $this->table = $tableName; |
| 79 | if ( ! isset(self::$meta_cache[$this->table])) { |
| 80 | $tableMeta = $this->wpdb->get_results("SHOW COLUMNS FROM $this->table", ARRAY_A); |
| 81 | $primary = array(); |
| 82 | $auto_increment = false; |
| 83 | foreach ($tableMeta as $colMeta) { |
| 84 | if ('PRI' == $colMeta['Key']) { |
| 85 | $primary[] = $colMeta['Field']; |
| 86 | } |
| 87 | if ('auto_increment' == $colMeta['Extra']) { |
| 88 | $auto_increment = true; |
| 89 | break; // no point to iterate futher since auto_increment means corresponding primary key is simple |
| 90 | } |
| 91 | } |
| 92 | self::$meta_cache[$this->table] = array('primary' => $primary, 'auto_increment' => $auto_increment); |
| 93 | } |
| 94 | $this->primary = self::$meta_cache[$this->table]['primary']; |
| 95 | $this->auto_increment = self::$meta_cache[$this->table]['auto_increment']; |
| 96 | |
| 97 | return $this; |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Return database table name this object is bound to |
| 102 | * @return string |
| 103 | */ |
| 104 | public function getTable() { |
| 105 | return $this->table; |
| 106 | } |
| 107 | /** |
| 108 | * Return column name with table name |
| 109 | * @param string $col |
| 110 | * @return string |
| 111 | */ |
| 112 | public function getFieldName($col) { |
| 113 | return $this->table . '.' . $col; |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Compose WHERE clause based on parameters provided |
| 118 | * @param string|array $field |
| 119 | * @param mixed[optional] $value |
| 120 | * @param string[optional] $operator AND or OR string, 'AND' by default |
| 121 | * @return string |
| 122 | */ |
| 123 | protected function buildWhere($field, $value = NULL, $operator = NULL) { |
| 124 | if ( ! is_array($field)) { |
| 125 | $field = array($field => $value); |
| 126 | } else { // shift arguments |
| 127 | $operator = $value; |
| 128 | } |
| 129 | ! is_null($operator) or $operator = 'AND'; // apply default operator value |
| 130 | |
| 131 | $where = array(); |
| 132 | foreach ($field as $key => $val) { |
| 133 | if (is_int($key)) { |
| 134 | $where[] = '(' . call_user_func_array(array($this, 'buildWhere'), $val) . ')'; |
| 135 | } else { |
| 136 | if ( ! preg_match('%^(.+?) *(=|<>|!=|<|>|<=|>=| (NOT +)?(IN|(LIKE|REGEXP|RLIKE)( BINARY)?))?$%i', trim($key), $mtch)) { |
| 137 | throw new Exception('Wrong field name format.'); |
| 138 | } |
| 139 | $key = $mtch[1]; |
| 140 | if (is_array($val) and (empty($mtch[2]) or 'IN' == strtoupper($mtch[4]))) { |
| 141 | $op = empty($mtch[2]) ? 'IN' : strtoupper(trim($mtch[2])); |
| 142 | if (count($val)) $where[] = $this->wpdb->prepare("$key $op (" . implode(', ', array_fill(0, count($val), "%s")) . ")", $val); |
| 143 | } else { |
| 144 | $op = empty($mtch[2]) ? '=' : strtoupper(trim($mtch[2])); |
| 145 | $where[] = $this->wpdb->prepare("$key $op %s", $val); |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | return implode(" $operator ", $where); |
| 150 | } |
| 151 | |
| 152 | |
| 153 | /** |
| 154 | * Return associative array with record data |
| 155 | * @param bool[optional] $serialize Whether returned fields should be serialized |
| 156 | * @return array |
| 157 | */ |
| 158 | public function toArray($serialize = FALSE) { |
| 159 | $result = (array)$this; |
| 160 | if ($serialize) { |
| 161 | foreach ($result as $k => $v) { |
| 162 | if ( ! is_scalar($v)) { |
| 163 | $result[$k] = serialize($v); |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | return $result; |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Check whether object data is empty |
| 172 | * @return bool |
| 173 | */ |
| 174 | public function isEmpty() { |
| 175 | return $this->count() == 0; |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Empty object data |
| 180 | * @return PMXE_Model |
| 181 | */ |
| 182 | public function clear() { |
| 183 | $this->exchangeArray(array()); |
| 184 | return $this; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Delete all content from model's table |
| 189 | * @return PMXE_Model |
| 190 | */ |
| 191 | public function truncateTable() { |
| 192 | if (FALSE !== $this->wpdb->query("TRUNCATE $this->table")) { |
| 193 | return $this; |
| 194 | } else { |
| 195 | throw new Exception($this->wpdb->last_error); |
| 196 | } |
| 197 | } |
| 198 | } |