// Add associations support in Collection
This commit is contained in:
+226
-31
@@ -32,6 +32,10 @@
|
|||||||
*/
|
*/
|
||||||
class CollectionCore implements Iterator, ArrayAccess, Countable
|
class CollectionCore implements Iterator, ArrayAccess, Countable
|
||||||
{
|
{
|
||||||
|
const LEFT_JOIN = 1;
|
||||||
|
const INNER_JOIN = 2;
|
||||||
|
const LEFT_OUTER_JOIN = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string Object class name
|
* @var string Object class name
|
||||||
*/
|
*/
|
||||||
@@ -75,6 +79,10 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
protected $fields = array();
|
protected $fields = array();
|
||||||
protected $alias = array();
|
protected $alias = array();
|
||||||
protected $alias_iterator = 0;
|
protected $alias_iterator = 0;
|
||||||
|
protected $join_list = array();
|
||||||
|
protected $association_definition = array();
|
||||||
|
|
||||||
|
const LANG_ALIAS = 'l';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $classname
|
* @param string $classname
|
||||||
@@ -87,25 +95,45 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
|
|
||||||
$this->definition = ObjectModel::getDefinition($this->classname);
|
$this->definition = ObjectModel::getDefinition($this->classname);
|
||||||
if (!isset($this->definition['table']))
|
if (!isset($this->definition['table']))
|
||||||
throw new PrestashopException('Miss table in definition for class '.$this->classname);
|
throw new PrestaShopException('Miss table in definition for class '.$this->classname);
|
||||||
else if (!isset($this->definition['primary']))
|
else if (!isset($this->definition['primary']))
|
||||||
throw new PrestashopException('Miss primary in definition for class '.$this->classname);
|
throw new PrestaShopException('Miss primary in definition for class '.$this->classname);
|
||||||
|
|
||||||
$alias = $this->generateAlias();
|
|
||||||
$this->query = new DbQuery();
|
$this->query = new DbQuery();
|
||||||
$this->query->select($alias.'.*');
|
}
|
||||||
$this->query->from($this->definition['table'], $alias);
|
|
||||||
|
|
||||||
// If multilang, create association to lang table
|
/**
|
||||||
// @todo create virtual association with lang in ObjectModel::getDefinition()
|
* Join current entity to an associated entity
|
||||||
if (isset($this->definition['multilang']) && $this->definition['multilang'])
|
*
|
||||||
|
* @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]))
|
||||||
{
|
{
|
||||||
$lang_alias = $this->generateAlias('@lang');
|
$definition = $this->getDefinition($association);
|
||||||
$this->query->select($lang_alias.'.*');
|
$on = '{'.$definition['asso']['complete_field'].'} = {'.$definition['asso']['complete_foreign_field'].'}';
|
||||||
$this->query->leftJoin($this->definition['table'].'_lang', $lang_alias, $alias.'.'.$this->definition['primary'].' = '.$lang_alias.'.'. $this->definition['primary']);
|
$type = self::LEFT_JOIN;
|
||||||
if ($this->id_lang)
|
$this->join_list[$association] = array(
|
||||||
$this->query->where($lang_alias.'.id_lang = '.(int)$this->id_lang);
|
'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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,10 +142,14 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
* @param string $field Field name
|
* @param string $field Field name
|
||||||
* @param string $operator List of operators : =, !=, <>, <, <=, >, >=, like, notlike, regexp, notregexp
|
* @param string $operator List of operators : =, !=, <>, <, <=, >, >=, like, notlike, regexp, notregexp
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
|
* @param string $type where|having
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
public function where($field, $operator, $value)
|
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)
|
// Create WHERE clause with an array value (IN, NOT IN)
|
||||||
if (is_array($value))
|
if (is_array($value))
|
||||||
{
|
{
|
||||||
@@ -125,17 +157,17 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
{
|
{
|
||||||
case '=' :
|
case '=' :
|
||||||
case 'in' :
|
case 'in' :
|
||||||
$this->query->where($this->parseField($field).' IN('.implode(', ', $this->formatValue($value, $field)).')');
|
$this->query->$method($this->parseField($field).' IN('.implode(', ', $this->formatValue($value, $field)).')');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '!=' :
|
case '!=' :
|
||||||
case '<>' :
|
case '<>' :
|
||||||
case 'notin' :
|
case 'notin' :
|
||||||
$this->query->where($this->parseField($field).' NOT IN('.implode(', ', $this->formatValue($value, $field)).')');
|
$this->query->$method($this->parseField($field).' NOT IN('.implode(', ', $this->formatValue($value, $field)).')');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
throw new PrestashopException('Operator not supported for array value');
|
throw new PrestaShopException('Operator not supported for array value');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create WHERE clause
|
// Create WHERE clause
|
||||||
@@ -152,19 +184,19 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
case '<=' :
|
case '<=' :
|
||||||
case 'like' :
|
case 'like' :
|
||||||
case 'regexp' :
|
case 'regexp' :
|
||||||
$this->query->where($this->parseField($field).' '.$operator.' '.$this->formatValue($value, $field));
|
$this->query->$method($this->parseField($field).' '.$operator.' '.$this->formatValue($value, $field));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'notlike' :
|
case 'notlike' :
|
||||||
$this->query->where($this->parseField($field).' NOT LIKE '.$this->formatValue($value, $field));
|
$this->query->$method($this->parseField($field).' NOT LIKE '.$this->formatValue($value, $field));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'notregexp' :
|
case 'notregexp' :
|
||||||
$this->query->where($this->parseField($field).' NOT REGEXP '.$this->formatValue($value, $field));
|
$this->query->$method($this->parseField($field).' NOT REGEXP '.$this->formatValue($value, $field));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
throw new PrestashopException('Operator not supported');
|
throw new PrestaShopException('Operator not supported');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +207,7 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
* Add WHERE restriction on query using real SQL syntax
|
* Add WHERE restriction on query using real SQL syntax
|
||||||
*
|
*
|
||||||
* @param string $sql
|
* @param string $sql
|
||||||
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
public function sqlWhere($sql)
|
public function sqlWhere($sql)
|
||||||
{
|
{
|
||||||
@@ -182,6 +215,31 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
return $this;
|
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
|
* Add ORDER BY restriction on query
|
||||||
*
|
*
|
||||||
@@ -193,11 +251,23 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
{
|
{
|
||||||
$order = strtolower($order);
|
$order = strtolower($order);
|
||||||
if ($order != 'asc' && $order != 'desc')
|
if ($order != 'asc' && $order != 'desc')
|
||||||
throw new PrestashopException('Order must be asc or desc');
|
throw new PrestaShopException('Order must be asc or desc');
|
||||||
$this->query->orderBy($this->parseField($field).' '.$order);
|
$this->query->orderBy($this->parseField($field).' '.$order);
|
||||||
return $this;
|
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
|
* Add GROUP BY restriction on query
|
||||||
*
|
*
|
||||||
@@ -210,6 +280,18 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add GROUP BY restriction on query using real SQL syntax
|
||||||
|
*
|
||||||
|
* @param string $sql
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function sqlGroupBy($sql)
|
||||||
|
{
|
||||||
|
$this->query->orderBy($this->parseFields($sql));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch sql query to create collection of objects
|
* Launch sql query to create collection of objects
|
||||||
*
|
*
|
||||||
@@ -222,6 +304,39 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
return $this;
|
return $this;
|
||||||
$this->is_hydrated = true;
|
$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)
|
if ($display_query)
|
||||||
echo $this->query.'<br />';
|
echo $this->query.'<br />';
|
||||||
|
|
||||||
@@ -334,7 +449,7 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
{
|
{
|
||||||
$this->getAll();
|
$this->getAll();
|
||||||
if (!isset($this->results[$offset]))
|
if (!isset($this->results[$offset]))
|
||||||
throw new PrestashopException('Unknown offset '.$offset.' for collection '.$this->classname);
|
throw new PrestaShopException('Unknown offset '.$offset.' for collection '.$this->classname);
|
||||||
return $this->results[$offset];
|
return $this->results[$offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +463,7 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
public function offsetSet($offset, $value)
|
public function offsetSet($offset, $value)
|
||||||
{
|
{
|
||||||
if (!$value instanceof $this->classname)
|
if (!$value instanceof $this->classname)
|
||||||
throw new PrestashopException('You cannot add an element which is not an instance of '.$this->classname);
|
throw new PrestaShopException('You cannot add an element which is not an instance of '.$this->classname);
|
||||||
|
|
||||||
$this->getAll();
|
$this->getAll();
|
||||||
if (is_null($offset))
|
if (is_null($offset))
|
||||||
@@ -369,6 +484,69 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
unset($this->results[$offset]);
|
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
|
* Parse all fields with {field} syntax in a string
|
||||||
*
|
*
|
||||||
@@ -400,6 +578,7 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
*
|
*
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @param string $field Field name
|
* @param string $field Field name
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
protected function formatValue($value, $field)
|
protected function formatValue($value, $field)
|
||||||
{
|
{
|
||||||
@@ -425,20 +604,36 @@ class CollectionCore implements Iterator, ArrayAccess, Countable
|
|||||||
if (!isset($this->fields[$field]))
|
if (!isset($this->fields[$field]))
|
||||||
{
|
{
|
||||||
$split = explode('.', $field);
|
$split = explode('.', $field);
|
||||||
$association = '';
|
$total = count($split);
|
||||||
$definition = ObjectModel::getDefinition($this->classname);
|
if ($total > 1)
|
||||||
for ($i = 0, $total_association = count($split) - 1; $i < $total_association; $i++)
|
|
||||||
{
|
{
|
||||||
// @todo association
|
$fieldname = $split[$total - 1];
|
||||||
|
unset($split[$total - 1]);
|
||||||
|
$association = implode('.', $split);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$fieldname = $field;
|
||||||
|
$association = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$fieldname = $split[$i];
|
$definition = $this->getDefinition($association);
|
||||||
if ($fieldname == $definition['primary'])
|
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;
|
$type = ObjectModel::TYPE_INT;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Test if field exists
|
||||||
if (!isset($definition['fields'][$fieldname]))
|
if (!isset($definition['fields'][$fieldname]))
|
||||||
throw new PrestashopException('Field '.$fieldname.' not found in class '.$this->classname);
|
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'];
|
$type = $definition['fields'][$fieldname]['type'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ class CountryCore extends ObjectModel
|
|||||||
// Lang fields
|
// Lang fields
|
||||||
'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 64),
|
'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 64),
|
||||||
),
|
),
|
||||||
|
'associations' => array(
|
||||||
|
'zone' => array('type' => self::HAS_ONE),
|
||||||
|
'currency' => array('type' => self::HAS_ONE),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
protected $webserviceParameters = array(
|
protected $webserviceParameters = array(
|
||||||
|
|||||||
Reference in New Issue
Block a user