garancia/classes/Collection.php

676 lines
16 KiB
PHP
Raw Normal View History

2016-10-10 15:24:25 +02:00
<?php
/*
* 2007-2013 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2013 PrestaShop SA
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* Create a collection of ObjectModel objects
*
* @since 1.5.0
*/
class CollectionCore implements Iterator, ArrayAccess, Countable
{
const LEFT_JOIN = 1;
const INNER_JOIN = 2;
const LEFT_OUTER_JOIN = 3;
/**
* @var string Object class name
*/
protected $classname;
/**
* @var int
*/
protected $id_lang;
/**
* @var array Object definition
*/
protected $definition = array();
/**
* @var DbQuery
*/
protected $query;
/**
* @var array Collection of objects in an array
*/
protected $results = array();
/**
* @var bool Is current collection already hydrated
*/
protected $is_hydrated = false;
/**
* @var int Collection iterator
*/
protected $iterator = 0;
/**
* @var int Total of elements for iteration
*/
protected $total;
protected $fields = array();
protected $alias = array();
protected $alias_iterator = 0;
protected $join_list = array();
protected $association_definition = array();
const LANG_ALIAS = 'l';
/**
* @param string $classname
* @param int $id_lang
*/
public function __construct($classname, $id_lang = null)
{
$this->classname = $classname;
$this->id_lang = $id_lang;
$this->definition = ObjectModel::getDefinition($this->classname);
if (!isset($this->definition['table']))
throw new PrestaShopException('Miss table in definition for class '.$this->classname);
else if (!isset($this->definition['primary']))
throw new PrestaShopException('Miss primary in definition for class '.$this->classname);
$this->query = new DbQuery();
}
/**
* Join current entity to an associated entity
*
* @param $association Association name
* @param string $on
* @param int $type
* @return Collection
*/
public function join($association, $on = '', $type = null)
{
if (!$association)
return;
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] = array(
'table' => ($definition['is_lang']) ? $definition['table'].'_lang' : $definition['table'],
'alias' => $this->generateAlias($association),
'on' => array(),
);
}
if ($on)
$this->join_list[$association]['on'][] = $this->parseFields($on);
if ($type)
$this->join_list[$association]['type'] = $type;
return $this;
}
/**
* Add WHERE restriction on query
*
* @param string $field Field name
* @param string $operator List of operators : =, !=, <>, <, <=, >, >=, like, notlike, regexp, notregexp
* @param mixed $value
* @param string $type where|having
* @return Collection
*/
public function where($field, $operator, $value, $method = 'where')
{
if ($method != 'where' && $method != 'having')
throw new PrestaShopException('Bad method argument for where() method (should be "where" or "having")');
// Create WHERE clause with an array value (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 PrestaShopException('Operator not supported for array value');
}
}
// Create WHERE clause
else
{
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 PrestaShopException('Operator not supported');
}
}
return $this;
}
/**
* Add WHERE restriction on query using real SQL syntax
*
* @param string $sql
* @return Collection
*/
public function sqlWhere($sql)
{
$this->query->where($this->parseFields($sql));
return $this;
}
/**
* Add HAVING restriction on query
*
* @param string $field Field name
* @param string $operator List of operators : =, !=, <>, <, <=, >, >=, like, notlike, regexp, notregexp
* @param mixed $value
* @return Collection
*/
public function having($field, $operator, $value)
{
return $this->where($field, $operator, $value, 'having');
}
/**
* Add HAVING restriction on query using real SQL syntax
*
* @param string $sql
* @return Collection
*/
public function sqlHaving($sql)
{
$this->query->having($this->parseFields($sql));
return $this;
}
/**
* Add ORDER BY restriction on query
*
* @param string $field Field name
* @param string $order asc|desc
* @return Collection
*/
public function orderBy($field, $order = 'asc')
{
$order = strtolower($order);
if ($order != 'asc' && $order != 'desc')
throw new PrestaShopException('Order must be asc or desc');
$this->query->orderBy($this->parseField($field).' '.$order);
return $this;
}
/**
* Add ORDER BY restriction on query using real SQL syntax
*
* @param string $sql
* @return Collection
*/
public function sqlOrderBy($sql)
{
$this->query->orderBy($this->parseFields($sql));
return $this;
}
/**
* Add GROUP BY restriction on query
*
* @param string $field Field name
* @return Collection
*/
public function groupBy($field)
{
$this->query->groupBy($this->parseField($field));
return $this;
}
/**
* Add GROUP BY restriction on query using real SQL syntax
*
* @param string $sql
* @return Collection
*/
public function sqlGroupBy($sql)
{
$this->query->groupBy($this->parseFields($sql));
return $this;
}
/**
* Launch sql query to create collection of objects
*
* @param bool $display_query If true, query will be displayed (for debug purpose)
* @return Collection
*/
public function getAll($display_query = false)
{
if ($this->is_hydrated)
return $this;
$this->is_hydrated = true;
$alias = $this->generateAlias();
//$this->query->select($alias.'.*');
$this->query->from($this->definition['table'], $alias);
// If multilang, create association to lang table
if (!empty($this->definition['multilang']))
{
$this->join(self::LANG_ALIAS);
if ($this->id_lang)
$this->where(self::LANG_ALIAS.'.id_lang', '=', $this->id_lang);
}
// Add join clause
foreach ($this->join_list as $data)
{
$on = '('.implode(') AND (', $data['on']).')';
switch ($data['type'])
{
case self::LEFT_JOIN :
$this->query->leftJoin($data['table'], $data['alias'], $on);
break;
case self::INNER_JOIN :
$this->query->innerJoin($data['table'], $data['alias'], $on);
break;
case self::LEFT_OUTER_JOIN :
$this->query->leftOuterJoin($data['table'], $data['alias'], $on);
break;
}
}
// Shall we display query for debug ?
if ($display_query)
echo $this->query.'<br />';
$this->results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($this->query);
$this->results = ObjectModel::hydrateCollection($this->classname, $this->results, $this->id_lang);
return $this;
}
/**
* Retrieve the first result
*
* @return ObjectModel
*/
public function getFirst()
{
$this->getAll();
if (!count($this))
return false;
return $this[0];
}
/**
* Get results array
*
* @return array
*/
public function getResults()
{
$this->getAll();
return $this->results;
}
/**
* This method is called when a foreach begin
*
* @see Iterator::rewind()
*/
public function rewind()
{
$this->getAll();
$this->results = array_merge($this->results);
$this->iterator = 0;
$this->total = count($this->results);
}
/**
* Get current result
*
* @see Iterator::current()
* @return ObjectModel
*/
public function current()
{
return isset($this->results[$this->iterator]) ? $this->results[$this->iterator] : null;
}
/**
* Check if there is a current result
*
* @see Iterator::valid()
* @return bool
*/
public function valid()
{
return $this->iterator < $this->total;
}
/**
* Get current result index
*
* @see Iterator::key()
* @return int
*/
public function key()
{
return $this->iterator;
}
/**
* Go to next result
*
* @see Iterator::next()
*/
public function next()
{
$this->iterator++;
}
/**
* Get total of results
*
* @see Countable::count()
* @return int
*/
public function count()
{
$this->getAll();
return count($this->results);
}
/**
* Check if a result exist
*
* @see ArrayAccess::offsetExists()
* @param $offset
* @return bool
*/
public function offsetExists($offset)
{
$this->getAll();
return isset($this->results[$offset]);
}
/**
* Get a result by offset
*
* @see ArrayAccess::offsetGet()
* @param $offset
* @return ObjectModel
*/
public function offsetGet($offset)
{
$this->getAll();
if (!isset($this->results[$offset]))
throw new PrestaShopException('Unknown offset '.$offset.' for collection '.$this->classname);
return $this->results[$offset];
}
/**
* Add an element in the collection
*
* @see ArrayAccess::offsetSet()
* @param $offset
* @param $value
*/
public function offsetSet($offset, $value)
{
if (!$value instanceof $this->classname)
throw new PrestaShopException('You cannot add an element which is not an instance of '.$this->classname);
$this->getAll();
if (is_null($offset))
$this->results[] = $value;
else
$this->results[$offset] = $value;
}
/**
* Delete an element from the collection
*
* @see ArrayAccess::offsetUnset()
* @param $offset
*/
public function offsetUnset($offset)
{
$this->getAll();
unset($this->results[$offset]);
}
/**
* Get definition of an association
*
* @param string $association
* @return array
*/
protected function getDefinition($association)
{
if (!$association)
return $this->definition;
if (!isset($this->association_definition[$association]))
{
$definition = $this->definition;
$split = explode('.', $association);
$is_lang = false;
for ($i = 0, $total_association = count($split); $i < $total_association; $i++)
{
$asso = $split[$i];
// Check is current association exists in current definition
if (!isset($definition['associations'][$asso]))
throw new PrestaShopException('Association '.$asso.' not found for class '.$this->definition['classname']);
$current_def = $definition['associations'][$asso];
// Special case for lang alias
if ($asso == self::LANG_ALIAS)
{
$is_lang = true;
break;
}
$classname = (isset($current_def['object'])) ? $current_def['object'] : Tools::toCamelCase($asso, true);
$definition = ObjectModel::getDefinition($classname);
}
// Get definition of associated entity and add information on current association
$current_def['name'] = $asso;
if (!isset($current_def['object']))
$current_def['object'] = Tools::toCamelCase($asso, true);
if (!isset($current_def['field']))
$current_def['field'] = 'id_'.$asso;
if (!isset($current_def['foreign_field']))
$current_def['foreign_field'] = 'id_'.$asso;
if ($total_association > 1)
{
unset($split[$total_association - 1]);
$current_def['complete_field'] = implode('.', $split).'.'.$current_def['field'];
}
else
$current_def['complete_field'] = $current_def['field'];
$current_def['complete_foreign_field'] = $association.'.'.$current_def['field'];
$definition['is_lang'] = $is_lang;
$definition['asso'] = $current_def;
$this->association_definition[$association] = $definition;
}
else
$definition = $this->association_definition[$association];
return $definition;
}
/**
* Parse all fields with {field} syntax in a string
*
* @param string $str
* @return string
*/
protected function parseFields($str)
{
preg_match_all('#\{(([a-z0-9_]+\.)*[a-z0-9_]+)\}#i', $str, $m);
for ($i = 0, $total = count($m[0]); $i < $total; $i++)
$str = str_replace($m[0][$i], $this->parseField($m[1][$i]), $str);
return $str;
}
/**
* Replace a field with its SQL version (E.g. manufacturer.name with a2.name)
*
* @param string $field Field name
* @return string
*/
protected function parseField($field)
{
$info = $this->getFieldInfo($field);
return $info['alias'].'.`'.$info['name'].'`';
}
/**
* Format a value with the type of the given field
*
* @param mixed $value
* @param string $field Field name
* @return mixed
*/
protected function formatValue($value, $field)
{
$info = $this->getFieldInfo($field);
if (is_array($value))
{
$results = array();
foreach ($value as $item)
$results[] = ObjectModel::formatValue($item, $info['type'], true);
return $results;
}
return ObjectModel::formatValue($value, $info['type'], true);
}
/**
* Obtain some information on a field (alias, name, type, etc.)
*
* @param string $field Field name
* @return array
*/
protected function getFieldInfo($field)
{
if (!isset($this->fields[$field]))
{
$split = explode('.', $field);
$total = count($split);
if ($total > 1)
{
$fieldname = $split[$total - 1];
unset($split[$total - 1]);
$association = implode('.', $split);
}
else
{
$fieldname = $field;
$association = '';
}
$definition = $this->getDefinition($association);
if ($association && !isset($this->join_list[$association]))
$this->join($association);
if ($fieldname == $definition['primary'] || (!empty($definition['is_lang']) && $fieldname == 'id_lang'))
$type = ObjectModel::TYPE_INT;
else
{
// Test if field exists
if (!isset($definition['fields'][$fieldname]))
throw new PrestaShopException('Field '.$fieldname.' not found in class '.$definition['classname']);
// Test field validity for language fields
if (empty($definition['is_lang']) && !empty($definition['fields'][$fieldname]['lang']))
throw new PrestaShopException('Field '.$fieldname.' is declared as lang field but is used in non multilang context');
else if (!empty($definition['is_lang']) && empty($definition['fields'][$fieldname]['lang']))
throw new PrestaShopException('Field '.$fieldname.' is not declared as lang field but is used in multilang context');
$type = $definition['fields'][$fieldname]['type'];
}
$this->fields[$field] = array(
'name' => $fieldname,
'association' => $association,
'alias' => $this->generateAlias($association),
'type' => $type,
);
}
return $this->fields[$field];
}
/**
* Generate uniq alias from association name
*
* @param string $association Use empty association for alias on current table
* @return string
*/
protected function generateAlias($association = '')
{
if (!isset($this->alias[$association]))
$this->alias[$association] = 'a'.$this->alias_iterator++;
return $this->alias[$association];
}
}