initializeModel(); } /** * Initialize model */ protected function initializeModel() { // Set default values if ($this->timestamps) { if (property_exists($this, $this->createdAtField)) { $this->{$this->createdAtField} = date('Y-m-d H:i:s'); } if (property_exists($this, $this->updatedAtField)) { $this->{$this->updatedAtField} = date('Y-m-d H:i:s'); } } } /** * Create new model instance * * @param array $attributes * @return static */ public static function create(array $attributes = []) { $model = new static(); $model->fill($attributes); $model->save(); return $model; } /** * Find model by ID * * @param int $id * @return static|null */ public static function find($id) { return new static($id); } /** * Find model by ID or throw exception * * @param int $id * @return static * @throws Exception */ public static function findOrFail($id) { $model = static::find($id); if (!$model || !$model->id) { throw new Exception('Model not found with ID: ' . $id); } return $model; } /** * Find model by field * * @param string $field * @param mixed $value * @return static|null */ public static function findBy($field, $value) { $sql = 'SELECT * FROM `' . _DB_PREFIX_ . static::$definition['table'] . '` WHERE `' . pSQL($field) . '` = "' . pSQL($value) . '"'; $result = Db::getInstance()->getRow($sql); if ($result) { $model = new static(); $model->hydrate($result); return $model; } return null; } /** * Find model by field or throw exception * * @param string $field * @param mixed $value * @return static * @throws Exception */ public static function findByOrFail($field, $value) { $model = static::findBy($field, $value); if (!$model) { throw new Exception('Model not found with ' . $field . ': ' . $value); } return $model; } /** * Get all models * * @param string $orderBy * @param string $orderWay * @return array */ public static function all($orderBy = null, $orderWay = 'ASC') { $sql = 'SELECT * FROM `' . _DB_PREFIX_ . static::$definition['table'] . '`'; if ($orderBy) { $sql .= ' ORDER BY `' . pSQL($orderBy) . '` ' . pSQL($orderWay); } $results = Db::getInstance()->executeS($sql); $models = []; foreach ($results as $result) { $model = new static(); $model->hydrate($result); $models[] = $model; } return $models; } /** * Get models with pagination * * @param int $page * @param int $perPage * @param string $orderBy * @param string $orderWay * @return array */ public static function paginate($page = 1, $perPage = 20, $orderBy = null, $orderWay = 'ASC') { $offset = ($page - 1) * $perPage; $sql = 'SELECT * FROM `' . _DB_PREFIX_ . static::$definition['table'] . '`'; if ($orderBy) { $sql .= ' ORDER BY `' . pSQL($orderBy) . '` ' . pSQL($orderWay); } $sql .= ' LIMIT ' . (int) $perPage . ' OFFSET ' . (int) $offset; $results = Db::getInstance()->executeS($sql); $models = []; foreach ($results as $result) { $model = new static(); $model->hydrate($result); $models[] = $model; } return $models; } /** * Count total models * * @return int */ public static function count() { $sql = 'SELECT COUNT(*) as total FROM `' . _DB_PREFIX_ . static::$definition['table'] . '`'; $result = Db::getInstance()->getRow($sql); return (int) $result['total']; } /** * Check if model exists * * @param int $id * @return bool */ public static function exists($id) { $sql = 'SELECT COUNT(*) as total FROM `' . _DB_PREFIX_ . static::$definition['table'] . '` WHERE `' . static::$definition['primary'] . '` = ' . (int) $id; $result = Db::getInstance()->getRow($sql); return (int) $result['total'] > 0; } /** * Fill model with attributes * * @param array $attributes * @return $this */ public function fill(array $attributes) { foreach ($attributes as $key => $value) { if (in_array($key, $this->fillable) || empty($this->fillable)) { $this->{$key} = $value; } } return $this; } /** * Update model * * @param array $attributes * @return bool */ public function update(array $attributes = []) { $this->fill($attributes); if ($this->timestamps && property_exists($this, $this->updatedAtField)) { $this->{$this->updatedAtField} = date('Y-m-d H:i:s'); } return parent::update(); } /** * Delete model * * @return bool */ public function delete() { return parent::delete(); } /** * Validate model * * @param bool $die * @param bool $error_return * @return bool|string */ public function validate($die = true, $error_return = false) { $this->errors = []; // Validate required fields foreach (static::$definition['fields'] as $field => $rules) { if (isset($rules['required']) && $rules['required']) { if (!isset($this->{$field}) || empty($this->{$field})) { $this->errors[] = 'Field ' . $field . ' is required'; } } } // Validate custom rules if (!empty(static::$rules)) { foreach (static::$rules as $field => $rules) { if (isset($this->{$field})) { $this->validateField($field, $this->{$field}, $rules); } } } if (!empty($this->errors)) { if ($die) { throw new Exception(implode(', ', $this->errors)); } return $error_return ? implode(', ', $this->errors) : false; } return true; } /** * Validate single field * * @param string $field * @param mixed $value * @param array $rules */ protected function validateField($field, $value, $rules) { foreach ($rules as $rule) { switch ($rule) { case 'email': if (!Validate::isEmail($value)) { $this->errors[] = 'Field ' . $field . ' must be a valid email'; } break; case 'url': if (!Validate::isUrl($value)) { $this->errors[] = 'Field ' . $field . ' must be a valid URL'; } break; case 'numeric': if (!is_numeric($value)) { $this->errors[] = 'Field ' . $field . ' must be numeric'; } break; case 'integer': if (!is_int($value) && !ctype_digit($value)) { $this->errors[] = 'Field ' . $field . ' must be an integer'; } break; case 'boolean': if (!is_bool($value) && !in_array($value, [0, 1, '0', '1'])) { $this->errors[] = 'Field ' . $field . ' must be boolean'; } break; } } } /** * Get validation errors * * @return array */ public function getValidationErrors() { return $this->errors; } /** * Check if model has errors * * @return bool */ public function hasErrors() { return !empty($this->errors); } /** * Get model as array * * @return array */ public function toArray() { $array = []; foreach (get_object_vars($this) as $key => $value) { if (!in_array($key, $this->hidden)) { $array[$key] = $value; } } return $array; } /** * Get model as JSON * * @return string */ public function toJson() { return json_encode($this->toArray()); } /** * Define one-to-one relationship * * @param string $related * @param string $foreignKey * @param string $localKey * @return mixed */ public function hasOne($related, $foreignKey = null, $localKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $localKey = $localKey ?: $this->getKeyName(); $relatedModel = new $related(); return $relatedModel->findBy($foreignKey, $this->{$localKey}); } /** * Define one-to-many relationship * * @param string $related * @param string $foreignKey * @param string $localKey * @return array */ public function hasMany($related, $foreignKey = null, $localKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $localKey = $localKey ?: $this->getKeyName(); $relatedModel = new $related(); $sql = 'SELECT * FROM `' . _DB_PREFIX_ . $relatedModel::$definition['table'] . '` WHERE `' . pSQL($foreignKey) . '` = ' . (int) $this->{$localKey}; $results = Db::getInstance()->executeS($sql); $models = []; foreach ($results as $result) { $model = new $related(); $model->hydrate($result); $models[] = $model; } return $models; } /** * Define belongs-to relationship * * @param string $related * @param string $foreignKey * @param string $ownerKey * @return mixed */ public function belongsTo($related, $foreignKey = null, $ownerKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $ownerKey = $ownerKey ?: 'id'; $relatedModel = new $related(); return $relatedModel->find($this->{$foreignKey}); } /** * Define many-to-many relationship * * @param string $related * @param string $table * @param string $foreignPivotKey * @param string $relatedPivotKey * @param string $parentKey * @param string $relatedKey * @return array */ public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null) { $table = $table ?: $this->joiningTable($related); $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey(); $relatedPivotKey = $relatedPivotKey ?: $this->getRelatedForeignKey($related); $parentKey = $parentKey ?: $this->getKeyName(); $relatedKey = $relatedKey ?: 'id'; $relatedModel = new $related(); $sql = 'SELECT r.* FROM `' . _DB_PREFIX_ . $table . '` p JOIN `' . _DB_PREFIX_ . $relatedModel::$definition['table'] . '` r ON p.`' . pSQL($relatedPivotKey) . '` = r.`' . pSQL($relatedKey) . '` WHERE p.`' . pSQL($foreignPivotKey) . '` = ' . (int) $this->{$parentKey}; $results = Db::getInstance()->executeS($sql); $models = []; foreach ($results as $result) { $model = new $related(); $model->hydrate($result); $models[] = $model; } return $models; } /** * Get foreign key name * * @return string */ protected function getForeignKey() { return strtolower(class_basename($this)) . '_id'; } /** * Get related foreign key name * * @param string $related * @return string */ protected function getRelatedForeignKey($related) { return strtolower(class_basename($related)) . '_id'; } /** * Get joining table name * * @param string $related * @return string */ protected function joiningTable($related) { $models = [strtolower(class_basename($this)), strtolower(class_basename($related))]; sort($models); return implode('_', $models); } /** * Get key name * * @return string */ public function getKeyName() { return $this->primaryKey; } /** * Get key value * * @return mixed */ public function getKey() { return $this->{$this->getKeyName()}; } /** * Check if model is new * * @return bool */ public function isNew() { return !$this->getKey(); } /** * Refresh model from database * * @return $this */ public function refresh() { if ($this->getKey()) { $fresh = static::find($this->getKey()); if ($fresh) { $this->hydrate($fresh->toArray()); } } return $this; } /** * Touch model (update timestamps) * * @return bool */ public function touch() { if ($this->timestamps && property_exists($this, $this->updatedAtField)) { $this->{$this->updatedAtField} = date('Y-m-d H:i:s'); return $this->update(); } return false; } /** * Get table name * * @return string */ public function getTable() { return static::$definition['table']; } /** * Get connection * * @return Db */ public function getConnection() { return Db::getInstance(); } }