. namespace hoplite\data; require_once HOPLITE_ROOT . '/base/struct.php'; /*! A Model represents a single instance of a database row and its relations. It inherits the strict data member policy from base\Struct and maintains a condition by which it fetches and updates data. The role of the Model class is not to validate data, but to persist it. Validation is the job of the Controller (in phalanx, that is a Task object); the Model will persist the data and help access related information. This class requires the use of a PDO object. */ class Model extends \hoplite\base\Struct { /*! @var PDO The object the model will use when performing operations. */ static protected $db = NULL; /*! @var string The string prefix to put in front of the table name. */ protected $table_prefix = ''; /*! @var string The name of the database table to which the object belongs. */ protected $table = 'table'; /*! @var string The condition to select this data object by. Parameters should be keyed using :keyname syntax. */ protected $condition = 'pkey = :pkey'; /*! @var string The name of the field(s) that provide the primary key. This can either be a single string or an array for a compound key. A word on primary keys: This class assumes that if the primary key is singular (defined as a string), then it is auto-increment. The implications of this are that the set primary key value is igorned on inserts and upates. If you do not want this behavior for a table with a truly singluar primary key, define it as a plural/compound primary key in the Model (defined as an array). */ protected $primary_key = 'pkey'; public function condition() { return $this->condition; } public function set_condition($cond) { $this->condition = $cond; } static public function db() { return self::$db; } static public function set_db(\PDO $db) { self::$db = $db; } /*! Constructor. This takes in either the value(s) to substitute into the |$this->condition| or NULL to create a new instance of the model. */ public function __construct($condition_data = NULL) { if (is_array($condition_data)) { if (!is_array($this->primary_key)) throw new ModelException('Cannot create ' . get_class($this) . ' with an array when primary key is singular.'); foreach ($condition_data as $key => $value) if (in_array($key, $this->primary_key)) $this->Set($key, $value); } else if (!is_null($condition_data)) { if (is_array($this->primary_key)) throw new ModelException('Cannot create ' . get_class($this) . ' when a singular value is given for a compound primary key.'); $this->Set($this->primary_key, $condition_data); } $this->table = $this->table_prefix . $this->table; } /*! Fetches an object and returns the result based on the |$this->condition|. */ public function Fetch() { $stmt = self::$db->Prepare("SELECT * FROM {$this->table} WHERE " . $this->condition()); $stmt->Execute($this->_GetSQLParams($stmt)); $result = $stmt->FetchObject(); if (!$result) throw new ModelException("Could not fetch " . get_class($this)); return $result; } /*! Fetches an object and stores the result in the model, overwriting existing data values. */ public function FetchInto() { $this->SetFrom($this->Fetch()); } /*! Inserts the new model into the database. This will explicitly filter out primary key information if it is a singular key. */ public function Insert() { $data = $this->ToArray(); if (!is_array($this->primary_key)) unset($data[$this->primary_key]); $keys = array_keys($data); $placeholders = array_map(function ($s) { return ":$s"; }, $keys); $stmt = self::$db->Prepare(" INSERT INTO {$this->table} (" . implode(', ', $keys) . ") VALUES (" . implode(', ', $placeholders) . ") "); $stmt->Execute($data); if (!is_array($this->primary_key)) $this->Set($this->primary_key, self::$db->LastInsertID()); } /*! Updates the database based on the values set in the model. */ public function Update() { $updates = array_map(function($s) { return "$s = :$s"; }, array_keys($this->ToArray())); $condition = $this->condition(); $stmt = self::$db->Prepare("UPDATE {$this->table} SET " . implode(', ', $updates) . " WHERE $condition"); $stmt->Execute($this->_GetSQLParams($stmt)); } /*! Deletes a record in the database based on the set condition. */ public function Delete() { $stmt = self::$db->Prepare("DELETE FROM {$this->table} WHERE " . $this->condition()); $stmt->Execute($this->_GetSQLParams($stmt)); } /*! Returns a subest of |$this->data| that is required to execute a given PDOStatement. This will only work on queries whose parameters are specified using :name syntax. It will filter all values that are not used in the query. */ protected function _GetSQLParams(\PDOStatement $stmt) { $matches = array(); preg_match_all('/\:([a-z0-9_\-]+)/i', $stmt->queryString, $matches); $params = array(); $data = $this->ToArray(); foreach ($matches[1] as $key) $params[$key] = $data[$key]; return $params; } /*! Returns an array of Model objects of the correct type based on a group condition. If no condition is specified, returns all results in the table. If the |$params| argument is not an array, it will be assumed to be a single value and will be converted to an array. */ static public function FetchGroup($condition = '', $params = array()) { $class = get_called_class(); // Late static binding. $props = new $class(); $sql = "SELECT * FROM {$props->table_prefix}{$props->table}"; if ($condition) $sql .= " WHERE $condition"; $stmt = self::$db->Prepare($sql); if (!is_array($params)) $params = array($params); $stmt->Execute($params); $results = array(); while ($obj = $stmt->FetchObject()) { $model = new $class(); $model->SetFrom($obj); $results[] = $model; } return $results; } } class ModelException extends \Exception {}