605 lines
14 KiB
PHP
605 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Collection - Mächtige Sammlung von Model-Objekten mit Filter-, Sortier- und Pagination-Methoden
|
|
* Vollständig PrestaShop-kompatibel mit erweiterten Funktionen
|
|
*/
|
|
class Collection implements \IteratorAggregate, \Countable, \Iterator, \ArrayAccess
|
|
{
|
|
public const LEFT_JOIN = 1;
|
|
public const INNER_JOIN = 2;
|
|
public const LEFT_OUTER_JOIN = 3;
|
|
|
|
/** @var array */
|
|
protected $items = [];
|
|
|
|
/** @var string */
|
|
protected $classname;
|
|
|
|
/** @var int */
|
|
protected $id_lang;
|
|
|
|
/** @var array */
|
|
protected $definition = [];
|
|
|
|
/** @var DbQuery */
|
|
protected $query;
|
|
|
|
/** @var bool */
|
|
protected $is_hydrated = false;
|
|
|
|
/** @var int */
|
|
protected $iterator = 0;
|
|
|
|
/** @var int */
|
|
protected $total;
|
|
|
|
/** @var int */
|
|
protected $page_number = 0;
|
|
|
|
/** @var int */
|
|
protected $page_size = 0;
|
|
|
|
/** @var array */
|
|
protected $fields = [];
|
|
|
|
/** @var array */
|
|
protected $alias = [];
|
|
|
|
/** @var int */
|
|
protected $alias_iterator = 0;
|
|
|
|
/** @var array */
|
|
protected $join_list = [];
|
|
|
|
/** @var array */
|
|
protected $association_definition = [];
|
|
|
|
public const LANG_ALIAS = 'l';
|
|
|
|
/**
|
|
* Konstruktor
|
|
* @param string $classname
|
|
* @param int $id_lang
|
|
* @param array $items
|
|
*/
|
|
public function __construct($classname = null, $id_lang = null, array $items = [])
|
|
{
|
|
$this->items = $items;
|
|
|
|
if ($classname) {
|
|
$this->classname = $classname;
|
|
$this->id_lang = $id_lang;
|
|
$this->definition = ObjectModel::getDefinition($this->classname);
|
|
|
|
if (!isset($this->definition['table'])) {
|
|
throw new Exception('Miss table in definition for class ' . $this->classname);
|
|
} elseif (!isset($this->definition['primary'])) {
|
|
throw new Exception('Miss primary in definition for class ' . $this->classname);
|
|
}
|
|
|
|
$this->query = new DbQuery();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JOIN mit assoziierten Entitäten
|
|
*/
|
|
public function join($association, $on = '', $type = null)
|
|
{
|
|
if (!$association) {
|
|
return $this;
|
|
}
|
|
|
|
if (!isset($this->join_list[$association])) {
|
|
$definition = $this->getDefinition($association);
|
|
$on = '{' . $definition['asso']['complete_field'] . '} = {' . $definition['asso']['complete_foreign_field'] . '}';
|
|
$type = self::LEFT_JOIN;
|
|
$this->join_list[$association] = [
|
|
'table' => ($definition['is_lang']) ? $definition['table'] . '_lang' : $definition['table'],
|
|
'alias' => $this->generateAlias($association),
|
|
'on' => [],
|
|
];
|
|
}
|
|
|
|
if ($on) {
|
|
$this->join_list[$association]['on'][] = $this->parseFields($on);
|
|
}
|
|
|
|
if ($type) {
|
|
$this->join_list[$association]['type'] = $type;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* WHERE-Bedingung (Vergleich)
|
|
*/
|
|
public function where($field, $operator, $value = null, $method = 'where')
|
|
{
|
|
if (func_num_args() == 2) {
|
|
$value = $operator;
|
|
$operator = '=';
|
|
}
|
|
|
|
if ($method != 'where' && $method != 'having') {
|
|
throw new Exception('Bad method argument for where() method (should be "where" or "having")');
|
|
}
|
|
|
|
// Array-Werte (IN, NOT IN)
|
|
if (is_array($value)) {
|
|
switch (strtolower($operator)) {
|
|
case '=':
|
|
case 'in':
|
|
$this->query->$method($this->parseField($field) . ' IN(' . implode(', ', $this->formatValue($value, $field)) . ')');
|
|
break;
|
|
case '!=':
|
|
case '<>':
|
|
case 'notin':
|
|
$this->query->$method($this->parseField($field) . ' NOT IN(' . implode(', ', $this->formatValue($value, $field)) . ')');
|
|
break;
|
|
default:
|
|
throw new Exception('Operator not supported for array value');
|
|
}
|
|
} else {
|
|
// Einzelwerte
|
|
switch (strtolower($operator)) {
|
|
case '=':
|
|
case '!=':
|
|
case '<>':
|
|
case '>':
|
|
case '>=':
|
|
case '<':
|
|
case '<=':
|
|
case 'like':
|
|
case 'regexp':
|
|
$this->query->$method($this->parseField($field) . ' ' . $operator . ' ' . $this->formatValue($value, $field));
|
|
break;
|
|
case 'notlike':
|
|
$this->query->$method($this->parseField($field) . ' NOT LIKE ' . $this->formatValue($value, $field));
|
|
break;
|
|
case 'notregexp':
|
|
$this->query->$method($this->parseField($field) . ' NOT REGEXP ' . $this->formatValue($value, $field));
|
|
break;
|
|
default:
|
|
throw new Exception('Operator not supported');
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* SQL WHERE mit direktem SQL
|
|
*/
|
|
public function sqlWhere($sql)
|
|
{
|
|
$this->query->where($this->parseFields($sql));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* HAVING-Bedingung
|
|
*/
|
|
public function having($field, $operator, $value)
|
|
{
|
|
return $this->where($field, $operator, $value, 'having');
|
|
}
|
|
|
|
/**
|
|
* SQL HAVING mit direktem SQL
|
|
*/
|
|
public function sqlHaving($sql)
|
|
{
|
|
$this->query->having($this->parseFields($sql));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 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($field, $order = 'asc')
|
|
{
|
|
if ($this->query) {
|
|
$this->query->orderBy($this->parseField($field) . ' ' . strtoupper($order));
|
|
return $this;
|
|
}
|
|
|
|
$items = $this->items;
|
|
usort($items, function ($a, $b) use ($field, $order) {
|
|
$aValue = is_array($a) ? $a[$field] : $a->$field;
|
|
$bValue = is_array($b) ? $b[$field] : $b->$field;
|
|
$result = $aValue <=> $bValue;
|
|
return $order === 'desc' ? -$result : $result;
|
|
});
|
|
return new static($items);
|
|
}
|
|
|
|
/**
|
|
* SQL ORDER BY
|
|
*/
|
|
public function sqlOrderBy($sql)
|
|
{
|
|
$this->query->orderBy($this->parseFields($sql));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* ORDER BY DESC
|
|
*/
|
|
public function orderByDesc($key)
|
|
{
|
|
return $this->orderBy($key, 'desc');
|
|
}
|
|
|
|
/**
|
|
* GROUP BY
|
|
*/
|
|
public function groupBy($field)
|
|
{
|
|
$this->query->groupBy($this->parseField($field));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* SQL GROUP BY
|
|
*/
|
|
public function sqlGroupBy($sql)
|
|
{
|
|
$this->query->groupBy($this->parseFields($sql));
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Alle Ergebnisse abrufen
|
|
*/
|
|
public function getAll($display_query = false)
|
|
{
|
|
if ($display_query) {
|
|
echo $this->query->build();
|
|
}
|
|
|
|
if (!$this->is_hydrated) {
|
|
$this->results = Db::getInstance()->executeS($this->query->build());
|
|
$this->is_hydrated = true;
|
|
}
|
|
|
|
return $this->results;
|
|
}
|
|
|
|
/**
|
|
* Erstes Element
|
|
*/
|
|
public function getFirst()
|
|
{
|
|
if (!$this->is_hydrated) {
|
|
$this->getAll();
|
|
}
|
|
|
|
return reset($this->results);
|
|
}
|
|
|
|
/**
|
|
* Letztes Element
|
|
*/
|
|
public function getLast()
|
|
{
|
|
if (!$this->is_hydrated) {
|
|
$this->getAll();
|
|
}
|
|
|
|
return end($this->results);
|
|
}
|
|
|
|
/**
|
|
* Ergebnisse abrufen
|
|
*/
|
|
public function getResults()
|
|
{
|
|
return $this->getAll();
|
|
}
|
|
|
|
/**
|
|
* Pagination
|
|
*/
|
|
public function paginate($perPage = 20, $page = 1)
|
|
{
|
|
$this->setPageSize($perPage);
|
|
$this->setPageNumber($page);
|
|
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* Iterator-Methoden
|
|
*/
|
|
public function rewind(): void
|
|
{
|
|
$this->iterator = 0;
|
|
$this->total = count($this->items);
|
|
}
|
|
|
|
public function current()
|
|
{
|
|
return $this->items[$this->iterator];
|
|
}
|
|
|
|
public function valid(): bool
|
|
{
|
|
return $this->iterator < $this->total;
|
|
}
|
|
|
|
public function key()
|
|
{
|
|
return $this->iterator;
|
|
}
|
|
|
|
public function next(): void
|
|
{
|
|
++$this->iterator;
|
|
}
|
|
|
|
/**
|
|
* Zähle Elemente
|
|
*/
|
|
public function count(): int
|
|
{
|
|
return count($this->items);
|
|
}
|
|
|
|
/**
|
|
* IteratorAggregate
|
|
*/
|
|
public function getIterator()
|
|
{
|
|
return new \ArrayIterator($this->items);
|
|
}
|
|
|
|
/**
|
|
* ArrayAccess-Methoden
|
|
*/
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return isset($this->items[$offset]);
|
|
}
|
|
|
|
public function offsetGet($offset)
|
|
{
|
|
return $this->items[$offset];
|
|
}
|
|
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
if (is_null($offset)) {
|
|
$this->items[] = $value;
|
|
} else {
|
|
$this->items[$offset] = $value;
|
|
}
|
|
}
|
|
|
|
public function offsetUnset($offset): void
|
|
{
|
|
unset($this->items[$offset]);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Definition abrufen
|
|
*/
|
|
protected function getDefinition($association)
|
|
{
|
|
if (!isset($this->association_definition[$association])) {
|
|
$definition = ObjectModel::getDefinition($association);
|
|
if (!isset($definition['associations'])) {
|
|
throw new Exception('No associations found for ' . $association);
|
|
}
|
|
$this->association_definition[$association] = $definition;
|
|
}
|
|
return $this->association_definition[$association];
|
|
}
|
|
|
|
/**
|
|
* Felder parsen
|
|
*/
|
|
protected function parseFields($str)
|
|
{
|
|
return preg_replace_callback('/\{([^}]+)\}/', function ($matches) {
|
|
return $this->parseField($matches[1]);
|
|
}, $str);
|
|
}
|
|
|
|
/**
|
|
* Feld parsen
|
|
*/
|
|
protected function parseField($field)
|
|
{
|
|
$field_info = $this->getFieldInfo($field);
|
|
return $field_info['alias'] . '.' . $field_info['field'];
|
|
}
|
|
|
|
/**
|
|
* Werte formatieren
|
|
*/
|
|
protected function formatValue($value, $field)
|
|
{
|
|
if (is_null($value)) {
|
|
return 'NULL';
|
|
}
|
|
if (is_bool($value)) {
|
|
return $value ? '1' : '0';
|
|
}
|
|
if (is_string($value)) {
|
|
return "'" . pSQL($value) . "'";
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Feld-Informationen abrufen
|
|
*/
|
|
protected function getFieldInfo($field)
|
|
{
|
|
if (!isset($this->fields[$field])) {
|
|
$this->fields[$field] = [
|
|
'field' => $field,
|
|
'alias' => 'main'
|
|
];
|
|
}
|
|
return $this->fields[$field];
|
|
}
|
|
|
|
/**
|
|
* Seitenzahl setzen
|
|
*/
|
|
public function setPageNumber($page_number)
|
|
{
|
|
$this->page_number = (int) $page_number;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Seitengröße setzen
|
|
*/
|
|
public function setPageSize($page_size)
|
|
{
|
|
$this->page_size = (int) $page_size;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Alias generieren
|
|
*/
|
|
protected function generateAlias($association = '')
|
|
{
|
|
return 'alias_' . ++$this->alias_iterator;
|
|
}
|
|
}
|