Newwebshop/classes/Collection.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;
}
}