Sprint 1.2 abgeschlossen: Model.php und Collection.php mit vollständigen ORM- und Collection-Features implementiert
This commit is contained in:
parent
a40d17e578
commit
584e831f8c
|
|
@ -4,10 +4,10 @@
|
||||||
**Ziel:** 100% PrestaShop-Kompatibilität mit allen Core- und erweiterten Funktionen
|
**Ziel:** 100% PrestaShop-Kompatibilität mit allen Core- und erweiterten Funktionen
|
||||||
**Timeline:** 6 Monate (24 Wochen) - 6 Milestones mit je 4 Sprints
|
**Timeline:** 6 Monate (24 Wochen) - 6 Milestones mit je 4 Sprints
|
||||||
**Status:** In Bearbeitung
|
**Status:** In Bearbeitung
|
||||||
**Aktueller Fortschritt:** 8.33% (2 von 24 Sprints abgeschlossen)
|
**Aktueller Fortschritt:** 12.5% (3 von 24 Sprints abgeschlossen)
|
||||||
|
|
||||||
## MILESTONE 1: CORE-SYSTEM ERWEITERUNG (Woche 1-4)
|
## MILESTONE 1: CORE-SYSTEM ERWEITERUNG (Woche 1-4)
|
||||||
**Status:** In Bearbeitung (50% abgeschlossen)
|
**Status:** In Bearbeitung (75% abgeschlossen)
|
||||||
|
|
||||||
### Sprint 1.1: Tools.php & Context.php Erweiterung ✅ ABGESCHLOSSEN
|
### Sprint 1.1: Tools.php & Context.php Erweiterung ✅ ABGESCHLOSSEN
|
||||||
- ✅ Security-Funktionen (hash, getToken, AdminToken, String-Operationen)
|
- ✅ Security-Funktionen (hash, getToken, AdminToken, String-Operationen)
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
- ✅ Context.php Erweiterung (getContext, cloneContext, Device-Erkennung)
|
- ✅ Context.php Erweiterung (getContext, cloneContext, Device-Erkennung)
|
||||||
- ✅ Cart.php Erweiterung (nbProducts, addCartRule, getOrderTotal, etc.)
|
- ✅ Cart.php Erweiterung (nbProducts, addCartRule, getOrderTotal, etc.)
|
||||||
|
|
||||||
### Sprint 1.2: Datenbank & ORM 🔄 IN BEARBEITUNG (75% abgeschlossen)
|
### Sprint 1.2: Datenbank & ORM ✅ ABGESCHLOSSEN
|
||||||
- ✅ Db.php Erweiterung (query, insert, update, delete, execute, etc.)
|
- ✅ Db.php Erweiterung (query, insert, update, delete, execute, etc.)
|
||||||
- ✅ ObjectModel.php Erweiterung (save, add, update, delete, duplicateObject, validateFields, getFields, formatValue, hydrate, getDefinition, etc.)
|
- ✅ ObjectModel.php Erweiterung (save, add, update, delete, duplicateObject, validateFields, getFields, formatValue, hydrate, getDefinition, etc.)
|
||||||
- 🔄 Model.php Erweiterung (CRUD-Operationen, Validierung, Beziehungen)
|
- ✅ Model.php Erweiterung (CRUD-Operationen, Validierung, Beziehungen)
|
||||||
- ⏳ Collection.php Erweiterung (Filter, Sortierung, Pagination)
|
- ✅ Collection.php Erweiterung (Filter, Sortierung, Pagination)
|
||||||
|
|
||||||
### Sprint 1.3: Core-Klassen System
|
### Sprint 1.3: Core-Klassen System
|
||||||
- ⏳ **Product.php** (8000+ Zeilen) - Vollständige Produktverwaltung
|
- ⏳ **Product.php** (8000+ Zeilen) - Vollständige Produktverwaltung
|
||||||
|
|
@ -299,8 +299,8 @@
|
||||||
- `getTranslations()`, `updateTranslations()` - Übersetzungsverwaltung
|
- `getTranslations()`, `updateTranslations()` - Übersetzungsverwaltung
|
||||||
|
|
||||||
## GESAMTFORTSCHRITT
|
## GESAMTFORTSCHRITT
|
||||||
**Aktueller Stand:** 8.33% (2 von 24 Sprints abgeschlossen)
|
**Aktueller Stand:** 12.5% (3 von 24 Sprints abgeschlossen)
|
||||||
**Nächster Meilenstein:** Sprint 1.2 abschließen (Model.php & Collection.php)
|
**Nächster Meilenstein:** Sprint 1.3 starten (Core-Klassen System)
|
||||||
|
|
||||||
## TECHNISCHE DETAILS
|
## TECHNISCHE DETAILS
|
||||||
- **Datenbank:** MySQL 8.0+ mit erweiterten Tabellen
|
- **Datenbank:** MySQL 8.0+ mit erweiterten Tabellen
|
||||||
|
|
@ -317,10 +317,9 @@
|
||||||
- ✅ Dokumentation bei jedem Sprint
|
- ✅ Dokumentation bei jedem Sprint
|
||||||
|
|
||||||
## NÄCHSTE SCHRITTE
|
## NÄCHSTE SCHRITTE
|
||||||
1. **Sprint 1.2 abschließen:** Model.php und Collection.php implementieren
|
1. **Sprint 1.3 starten:** Core-Klassen System (Product.php, Category.php, Customer.php, Order.php)
|
||||||
2. **Sprint 1.3 starten:** Core-Klassen System (Product.php, Category.php, Customer.php, Order.php)
|
2. **Sprint 1.4 starten:** Erweiterte Core-Klassen (Address.php, Manufacturer.php, Supplier.php, Currency.php, Language.php)
|
||||||
3. **Sprint 1.4 starten:** Erweiterte Core-Klassen (Address.php, Manufacturer.php, Supplier.php, Currency.php, Language.php)
|
3. **Milestone 1 abschließen:** Core-System vollständig
|
||||||
4. **Milestone 1 abschließen:** Core-System vollständig
|
|
||||||
|
|
||||||
---
|
---
|
||||||
*Letzte Aktualisierung: Sprint 1.2 - ObjectModel.php Erweiterung abgeschlossen*
|
*Letzte Aktualisierung: Sprint 1.2 (Model.php & Collection.php) abgeschlossen*
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection - Mächtige Sammlung von Model-Objekten mit Filter-, Sortier- und Pagination-Methoden
|
||||||
|
*/
|
||||||
|
class Collection implements \IteratorAggregate, \Countable
|
||||||
|
{
|
||||||
|
/** @var array */
|
||||||
|
protected $items = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor
|
||||||
|
* @param array $items
|
||||||
|
*/
|
||||||
|
public function __construct(array $items = [])
|
||||||
|
{
|
||||||
|
$this->items = $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE-Bedingung (Vergleich)
|
||||||
|
*/
|
||||||
|
public function where($key, $operator, $value = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 2) {
|
||||||
|
$value = $operator;
|
||||||
|
$operator = '=';
|
||||||
|
}
|
||||||
|
$filtered = array_filter($this->items, function ($item) use ($key, $operator, $value) {
|
||||||
|
$itemValue = is_array($item) ? $item[$key] : $item->$key;
|
||||||
|
switch ($operator) {
|
||||||
|
case '=': return $itemValue == $value;
|
||||||
|
case '!=': return $itemValue != $value;
|
||||||
|
case '>': return $itemValue > $value;
|
||||||
|
case '<': return $itemValue < $value;
|
||||||
|
case '>=': return $itemValue >= $value;
|
||||||
|
case '<=': return $itemValue <= $value;
|
||||||
|
case 'like': return stripos($itemValue, $value) !== false;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new static(array_values($filtered));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE IN
|
||||||
|
*/
|
||||||
|
public function whereIn($key, array $values)
|
||||||
|
{
|
||||||
|
$filtered = array_filter($this->items, function ($item) use ($key, $values) {
|
||||||
|
$itemValue = is_array($item) ? $item[$key] : $item->$key;
|
||||||
|
return in_array($itemValue, $values);
|
||||||
|
});
|
||||||
|
return new static(array_values($filtered));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE BETWEEN
|
||||||
|
*/
|
||||||
|
public function whereBetween($key, array $range)
|
||||||
|
{
|
||||||
|
$filtered = array_filter($this->items, function ($item) use ($key, $range) {
|
||||||
|
$itemValue = is_array($item) ? $item[$key] : $item->$key;
|
||||||
|
return $itemValue >= $range[0] && $itemValue <= $range[1];
|
||||||
|
});
|
||||||
|
return new static(array_values($filtered));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE NULL
|
||||||
|
*/
|
||||||
|
public function whereNull($key)
|
||||||
|
{
|
||||||
|
$filtered = array_filter($this->items, function ($item) use ($key) {
|
||||||
|
$itemValue = is_array($item) ? $item[$key] : $item->$key;
|
||||||
|
return is_null($itemValue);
|
||||||
|
});
|
||||||
|
return new static(array_values($filtered));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE NOT NULL
|
||||||
|
*/
|
||||||
|
public function whereNotNull($key)
|
||||||
|
{
|
||||||
|
$filtered = array_filter($this->items, function ($item) use ($key) {
|
||||||
|
$itemValue = is_array($item) ? $item[$key] : $item->$key;
|
||||||
|
return !is_null($itemValue);
|
||||||
|
});
|
||||||
|
return new static(array_values($filtered));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ORDER BY
|
||||||
|
*/
|
||||||
|
public function orderBy($key)
|
||||||
|
{
|
||||||
|
$items = $this->items;
|
||||||
|
usort($items, function ($a, $b) use ($key) {
|
||||||
|
$aValue = is_array($a) ? $a[$key] : $a->$key;
|
||||||
|
$bValue = is_array($b) ? $b[$key] : $b->$key;
|
||||||
|
return $aValue <=> $bValue;
|
||||||
|
});
|
||||||
|
return new static($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ORDER BY DESC
|
||||||
|
*/
|
||||||
|
public function orderByDesc($key)
|
||||||
|
{
|
||||||
|
$items = $this->items;
|
||||||
|
usort($items, function ($a, $b) use ($key) {
|
||||||
|
$aValue = is_array($a) ? $a[$key] : $a->$key;
|
||||||
|
$bValue = is_array($b) ? $b[$key] : $b->$key;
|
||||||
|
return $bValue <=> $aValue;
|
||||||
|
});
|
||||||
|
return new static($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Neueste zuerst (nach Feld, default: date_add)
|
||||||
|
*/
|
||||||
|
public function latest($key = 'date_add')
|
||||||
|
{
|
||||||
|
return $this->orderByDesc($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Älteste zuerst (nach Feld, default: date_add)
|
||||||
|
*/
|
||||||
|
public function oldest($key = 'date_add')
|
||||||
|
{
|
||||||
|
return $this->orderBy($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination
|
||||||
|
*/
|
||||||
|
public function paginate($perPage = 20, $page = 1)
|
||||||
|
{
|
||||||
|
$offset = ($page - 1) * $perPage;
|
||||||
|
$items = array_slice($this->items, $offset, $perPage);
|
||||||
|
return new static($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einfache Pagination (liefert Array)
|
||||||
|
*/
|
||||||
|
public function simplePaginate($perPage = 20, $page = 1)
|
||||||
|
{
|
||||||
|
$offset = ($page - 1) * $perPage;
|
||||||
|
return array_slice($this->items, $offset, $perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunk-Verarbeitung
|
||||||
|
*/
|
||||||
|
public function chunk($size, callable $callback)
|
||||||
|
{
|
||||||
|
$chunks = array_chunk($this->items, $size);
|
||||||
|
foreach ($chunks as $chunk) {
|
||||||
|
$callback(new static($chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator (each)
|
||||||
|
*/
|
||||||
|
public function each(callable $callback)
|
||||||
|
{
|
||||||
|
foreach ($this->items as $key => $item) {
|
||||||
|
$callback($item, $key);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zähle Elemente
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return count($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IteratorAggregate
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new \ArrayIterator($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Elemente als Array
|
||||||
|
*/
|
||||||
|
public function all()
|
||||||
|
{
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstes Element
|
||||||
|
*/
|
||||||
|
public function first()
|
||||||
|
{
|
||||||
|
return reset($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Letztes Element
|
||||||
|
*/
|
||||||
|
public function last()
|
||||||
|
{
|
||||||
|
return end($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leere Collection?
|
||||||
|
*/
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
return empty($this->items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,620 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model - Base class for all database models
|
||||||
|
* Extends ObjectModel with additional functionality
|
||||||
|
*/
|
||||||
|
abstract class Model extends ObjectModel
|
||||||
|
{
|
||||||
|
/** @var array Validation errors */
|
||||||
|
protected $errors = [];
|
||||||
|
|
||||||
|
/** @var array Validation rules */
|
||||||
|
protected static $rules = [];
|
||||||
|
|
||||||
|
/** @var array Relationships */
|
||||||
|
protected static $relationships = [];
|
||||||
|
|
||||||
|
/** @var array Fillable fields */
|
||||||
|
protected $fillable = [];
|
||||||
|
|
||||||
|
/** @var array Hidden fields */
|
||||||
|
protected $hidden = [];
|
||||||
|
|
||||||
|
/** @var array Casts */
|
||||||
|
protected $casts = [];
|
||||||
|
|
||||||
|
/** @var string Primary key */
|
||||||
|
protected $primaryKey = 'id';
|
||||||
|
|
||||||
|
/** @var bool Timestamps */
|
||||||
|
public $timestamps = true;
|
||||||
|
|
||||||
|
/** @var string Created at field */
|
||||||
|
protected $createdAtField = 'date_add';
|
||||||
|
|
||||||
|
/** @var string Updated at field */
|
||||||
|
protected $updatedAtField = 'date_upd';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param int|null $id
|
||||||
|
* @param int|null $id_lang
|
||||||
|
* @param int|null $id_shop
|
||||||
|
*/
|
||||||
|
public function __construct($id = null, $id_lang = null, $id_shop = null)
|
||||||
|
{
|
||||||
|
parent::__construct($id, $id_lang, $id_shop);
|
||||||
|
$this->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue