Fixes #530 - No more joinWith() and groupBy(), align with Laravel to the best of our ability.
Adds new list column type "nameFrom" (take name from X attribute) as an alternative to "select".
This commit is contained in:
parent
982b22a676
commit
d52fe388db
|
|
@ -41,7 +41,14 @@ class ListColumn
|
|||
public $sortable = true;
|
||||
|
||||
/**
|
||||
* @var string Custom SQL for selecting this record value.
|
||||
* @var string Model attribute to use for the display value, this will
|
||||
* override any $sqlSelect definition.
|
||||
*/
|
||||
public $nameFrom;
|
||||
|
||||
/**
|
||||
* @var string Custom SQL for selecting this record display value,
|
||||
* the @ symbol is replaced with the table name.
|
||||
*/
|
||||
public $sqlSelect;
|
||||
|
||||
|
|
@ -103,6 +110,7 @@ class ListColumn
|
|||
if (isset($config['searchable'])) $this->searchable = $config['searchable'];
|
||||
if (isset($config['sortable'])) $this->sortable = $config['sortable'];
|
||||
if (isset($config['invisible'])) $this->invisible = $config['invisible'];
|
||||
if (isset($config['nameFrom'])) $this->nameFrom = $config['nameFrom'];
|
||||
if (isset($config['select'])) $this->sqlSelect = $config['select'];
|
||||
if (isset($config['relation'])) $this->relation = $config['relation'];
|
||||
if (isset($config['format'])) $this->format = $config['format'];
|
||||
|
|
|
|||
|
|
@ -265,9 +265,10 @@ class Lists extends WidgetBase
|
|||
protected function prepareModel()
|
||||
{
|
||||
$query = $this->model->newQuery();
|
||||
$selects = [$this->model->getTable().'.*'];
|
||||
$tables = ['base'=>$this->model->getTable()];
|
||||
$primaryTable = $this->model->getTable();
|
||||
$selects = [$primaryTable.'.*'];
|
||||
$joins = [];
|
||||
$withs = [];
|
||||
|
||||
/*
|
||||
* Extensibility
|
||||
|
|
@ -276,68 +277,129 @@ class Lists extends WidgetBase
|
|||
$this->fireEvent('list.extendQueryBefore', [$query]);
|
||||
|
||||
/*
|
||||
* Related custom selects, must come first
|
||||
* Prepare searchable column names
|
||||
*/
|
||||
foreach ($this->getVisibleListColumns() as $column) {
|
||||
if (!isset($column->relation) || !isset($column->sqlSelect))
|
||||
continue;
|
||||
$primarySearchable = [];
|
||||
$relationSearchable = [];
|
||||
|
||||
if (!$this->model->hasRelation($column->relation))
|
||||
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation]));
|
||||
$columnsToSearch = [];
|
||||
if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) {
|
||||
foreach ($searchableColumns as $column) {
|
||||
/*
|
||||
* Related
|
||||
*/
|
||||
if ($this->isColumnRelated($column)) {
|
||||
$table = $this->model->makeRelation($column->relation)->getTable();
|
||||
$columnName = isset($column->sqlSelect)
|
||||
? DbDongle::raw($this->parseTableName($column->sqlSelect, $table))
|
||||
: $table . '.' . $column->nameFrom;
|
||||
|
||||
$alias = Db::getQueryGrammar()->wrap($column->columnName);
|
||||
$table = $this->model->makeRelation($column->relation)->getTable();
|
||||
$relationType = $this->model->getRelationType($column->relation);
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $table);
|
||||
$relationSearchable[$column->relation][] = $columnName;
|
||||
}
|
||||
/*
|
||||
* Primary
|
||||
*/
|
||||
else {
|
||||
$columnName = isset($column->sqlSelect)
|
||||
? DbDongle::raw($this->parseTableName($column->sqlSelect, $primaryTable))
|
||||
: $primaryTable . '.' . $column->columnName;
|
||||
|
||||
if (in_array($relationType, ['hasMany', 'belongsToMany', 'morphToMany', 'morphedByMany', 'morphMany', 'attachMany', 'hasManyThrough']))
|
||||
$selects[] = DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ') as ". $alias);
|
||||
else
|
||||
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
||||
|
||||
$joins[] = $column->relation;
|
||||
$tables[$column->relation] = $table;
|
||||
$primarySearchable[] = $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($joins)
|
||||
$query->joinWith(array_unique($joins), false);
|
||||
/*
|
||||
* Prepare related eager loads (withs) and custom selects (joins)
|
||||
*/
|
||||
foreach ($this->getVisibleListColumns() as $column) {
|
||||
|
||||
if (!$this->isColumnRelated($column) || (!isset($column->sqlSelect) && !isset($column->nameFrom)))
|
||||
continue;
|
||||
|
||||
if (isset($column->nameFrom))
|
||||
$withs[] = $column->relation;
|
||||
|
||||
$joins[] = $column->relation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Include any relation constraints
|
||||
*/
|
||||
if ($joins) {
|
||||
foreach (array_unique($joins) as $join) {
|
||||
/*
|
||||
* Apply a supplied search term for relation columns and
|
||||
* constrain the query only if there is something to search for
|
||||
*/
|
||||
$columnsToSearch = array_get($relationSearchable, $join, []);
|
||||
|
||||
if (count($columnsToSearch) > 0) {
|
||||
$query->whereHas($join, function($_query) use ($columnsToSearch) {
|
||||
$_query->searchWhere($this->searchTerm, $columnsToSearch);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add eager loads to the query
|
||||
*/
|
||||
if ($withs) {
|
||||
$query->with(array_unique($withs));
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom select queries
|
||||
*/
|
||||
foreach ($this->getVisibleListColumns() as $column) {
|
||||
if (!isset($column->sqlSelect) || isset($column->relation))
|
||||
if (!isset($column->sqlSelect))
|
||||
continue;
|
||||
|
||||
$alias = Db::getQueryGrammar()->wrap($column->columnName);
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $tables['base']);
|
||||
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
||||
|
||||
/*
|
||||
* Relation column
|
||||
*/
|
||||
if (isset($column->relation)) {
|
||||
$table = $this->model->makeRelation($column->relation)->getTable();
|
||||
$relationType = $this->model->getRelationType($column->relation);
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $table);
|
||||
|
||||
/*
|
||||
* Manipulate a count query for the sub query
|
||||
*/
|
||||
$relationObj = $this->model->{$column->relation}();
|
||||
$countQuery = $relationObj->getRelationCountQuery($relationObj->getRelated()->newQuery(), $query);
|
||||
|
||||
$joinSql = $this->isColumnRelated($column, true)
|
||||
? DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ')")
|
||||
: DbDongle::raw($sqlSelect);
|
||||
|
||||
$joinSql = $countQuery->select($joinSql)->toSql();
|
||||
|
||||
$selects[] = Db::raw("(".$joinSql.") as ".$alias);
|
||||
}
|
||||
/*
|
||||
* Primary column
|
||||
*/
|
||||
else {
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $primaryTable);
|
||||
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a supplied search term
|
||||
* Apply a supplied search term for primary columns
|
||||
*/
|
||||
if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) {
|
||||
$query->orWhere(function($innerQuery) use ($searchableColumns, $tables) {
|
||||
$columnsToSearch = [];
|
||||
foreach ($searchableColumns as $column) {
|
||||
|
||||
if (isset($column->sqlSelect)) {
|
||||
$table = (isset($column->relation)) ? $tables[$column->relation] : 'base';
|
||||
$columnName = DbDongle::raw($this->parseTableName($column->sqlSelect, $table));
|
||||
}
|
||||
else
|
||||
$columnName = $tables['base'] . '.' . $column->columnName;
|
||||
|
||||
$columnsToSearch[] = $columnName;
|
||||
}
|
||||
|
||||
$innerQuery->searchWhere($this->searchTerm, $columnsToSearch);
|
||||
if (count($primarySearchable) > 0) {
|
||||
$query->orWhere(function($innerQuery) use ($primarySearchable) {
|
||||
$innerQuery->searchWhere($this->searchTerm, $primarySearchable);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle sorting
|
||||
* Apply sorting
|
||||
*/
|
||||
if ($sortColumn = $this->getSortColumn()) {
|
||||
$query->orderBy($sortColumn, $this->sortDirection);
|
||||
|
|
@ -356,9 +418,7 @@ class Lists extends WidgetBase
|
|||
Event::fire('backend.list.extendQuery', [$this, $query]);
|
||||
$this->fireEvent('list.extendQuery', [$query]);
|
||||
|
||||
// Grouping due to the joinWith() call
|
||||
$query->select($selects);
|
||||
$query->groupBy($this->model->getQualifiedKeyName());
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
|
@ -555,16 +615,33 @@ class Lists extends WidgetBase
|
|||
*/
|
||||
public function getColumnValue($record, $column)
|
||||
{
|
||||
|
||||
$columnName = $column->columnName;
|
||||
|
||||
/*
|
||||
* If the column is a relation, it will be a custom select,
|
||||
* Handle taking name from model attribute.
|
||||
*/
|
||||
if ($column->nameFrom) {
|
||||
if (!array_key_exists($columnName, $record->getRelations()))
|
||||
$value = null;
|
||||
elseif ($this->isColumnRelated($column, true))
|
||||
$value = implode(', ', $record->{$columnName}->lists($column->nameFrom));
|
||||
elseif ($this->isColumnRelated($column))
|
||||
$value = $record->{$columnName}->{$column->nameFrom};
|
||||
else
|
||||
$value = $record->{$column->nameFrom};
|
||||
}
|
||||
/*
|
||||
* Otherwise, if the column is a relation, it will be a custom select,
|
||||
* so prevent the Model from attempting to load the relation
|
||||
* if the value is NULL.
|
||||
*/
|
||||
$columnName = $column->columnName;
|
||||
if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes))
|
||||
$value = $record->attributes[$columnName];
|
||||
else
|
||||
$value = $record->{$columnName};
|
||||
else {
|
||||
if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes))
|
||||
$value = $record->attributes[$columnName];
|
||||
else
|
||||
$value = $record->{$columnName};
|
||||
}
|
||||
|
||||
if (method_exists($this, 'eval'. studly_case($column->type) .'TypeValue'))
|
||||
$value = $this->{'eval'. studly_case($column->type) .'TypeValue'}($record, $column, $value);
|
||||
|
|
@ -962,4 +1039,38 @@ class Lists extends WidgetBase
|
|||
return $this->onRefresh();
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
/**
|
||||
* Check if column refers to a relation of the model
|
||||
* @param ListColumn $column List column object
|
||||
* @param boolean $multi If set, returns true only if the relation is a "multiple relation type"
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isColumnRelated($column, $multi = false)
|
||||
{
|
||||
if (!isset($column->relation))
|
||||
return false;
|
||||
|
||||
if (!$this->model->hasRelation($column->relation))
|
||||
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation]));
|
||||
|
||||
if (!$multi)
|
||||
return true;
|
||||
|
||||
$relationType = $this->model->getRelationType($column->relation);
|
||||
|
||||
return in_array($relationType, [
|
||||
'hasMany',
|
||||
'belongsToMany',
|
||||
'morphToMany',
|
||||
'morphedByMany',
|
||||
'morphMany',
|
||||
'attachMany',
|
||||
'hasManyThrough'
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue