You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I added a relationship field using the fetch, but I wanted to show an attribute of the model not an actual field in the database. This does not work because the select blade is using pluck.
I could fix this using the $appends to add the 'extra_attribute_on_the_model' on the model, but this is a huge overhead because of the extra joins.
What I expected to happen
Be able to use attributes in the fetch on the field.
What happened
The field tries to pluck the attribute, but this does not exist.
What I've already tried to fix it
I modified the vendor/backpack/pro/src/Http/Controllers/Operations/FetchOperation.php with an extra config 'attributes':
I added the fetchGet function to remove redundancy (search vs no search)
<?php
namespace Backpack\Pro\Http\Controllers\Operations;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
trait FetchOperation
{
/**
* Define which routes are needed for this operation.
*
* @param string $segment Name of the current entity (singular). Used as first URL segment.
* @param string $routeName Prefix of the route name.
* @param string $controller Name of the current CrudController.
*/
protected function setupFetchOperationRoutes($segment, $routeName, $controller)
{
// get all method names on the current model that start with "fetch" (ex: fetchCategory)
// if a method that looks like that is present, it means we need to add the routes that fetch that entity
preg_match_all('/(?<=^|;)fetch([^;]+?)(;|$)/', implode(';', get_class_methods($this)), $matches);
if (count($matches[1])) {
foreach ($matches[1] as $methodName) {
Route::post($segment.'/fetch/'.Str::kebab($methodName), [
'as' => $segment.'.fetch'.Str::studly($methodName),
'uses' => $controller.'@fetch'.$methodName,
'operation' => 'FetchOperation',
]);
}
}
}
protected function setupFetchOperationDefaults() {
$this->crud->setOperationSetting('searchOperator', config('backpack.operations.fetch.searchOperator', 'LIKE'));
}
/**
* Gets items from database and returns to selects.
*
* @param string|array $arg
* @return \Illuminate\Http\JsonResponse|Illuminate\Database\Eloquent\Collection|Illuminate\Pagination\LengthAwarePaginator
*/
private function fetch($arg)
{
// get the actual words that were used to search for an item (the search term / search string)
$search_string = request()->input('q') ?? false;
// if the Class was passed as the sole argument, use that as the configured Model
// otherwise assume the arguments are actually the configuration array
$config = [];
if (! is_array($arg)) {
if (! class_exists($arg)) {
return response()->json(['error' => 'Class: '.$arg.' does not exists'], 500);
}
$config['model'] = $arg;
} else {
$config = $arg;
}
$model_instance = new $config['model']();
// set configuration defaults
$config['paginate'] = isset($config['paginate']) ? $config['paginate'] : 10;
$config['searchable_attributes'] = $config['searchable_attributes'] ?? $model_instance->identifiableAttribute();
// if a closure that has been passed as "query", use the closure - otherwise use the model
$config['query'] = isset($config['query']) && is_callable($config['query']) ? $config['query']($model_instance) : $model_instance;
// FetchOperation sends an empty query to retrieve the default entry for select when field is not nullable.
// Also sends an empty query in case we want to load all entities to emulate non-ajax fields
// when using InlineCreate.
/*
return $config['query']->get()->map(function($item) {
return ['id' => $item->id, 'name' => $item->name, 'full_name' => $item->full_name];
});
*/
if ($search_string === false) {
return $this->fetchGet($config);
}
$textColumnTypes = ['string', 'json_string', 'text', 'longText', 'json_array', 'json', 'varchar', 'char'];
$searchOperator = $config['searchOperator'] ?? $this->crud->getOperationSetting('searchOperator');
// if the query builder brings any where clause already defined by the user we must
// ensure that the where prevails and we should only use our search as a complement to the query constraints.
// e.g user want only the active products, so in fetch they would return something like:
// .... 'query' => function($model) { return $model->where('active', 1); }
// So it reads: SELECT ... WHERE active = 1 AND (XXX = x OR YYY = y) and not SELECT ... WHERE active = 1 AND XXX = x OR YYY = y;
if (! empty($config['query']->getQuery()->wheres)) {
$config['query'] = $config['query']->where(function ($query) use ($model_instance, $config, $search_string, $textColumnTypes, $searchOperator) {
foreach ((array) $config['searchable_attributes'] as $k => $searchColumn) {
$operation = ($k == 0) ? 'where' : 'orWhere';
$columnType = $model_instance->getColumnType($searchColumn);
if (in_array($columnType, $textColumnTypes)) {
$tempQuery = $query->{$operation}($searchColumn, $searchOperator, '%'.$search_string.'%');
} else {
$tempQuery = $query->{$operation}($searchColumn, $search_string);
}
}
// If developer provide an empty searchable_attributes array it means they don't want us to search
// in any specific column, or try to guess the column from model identifiableAttribute.
// In that scenario we will not have any $tempQuery here, so we just return the query, is up to the developer
// to do their own search.
return $tempQuery ?? $query;
});
} else {
foreach ((array) $config['searchable_attributes'] as $k => $searchColumn) {
$operation = ($k == 0) ? 'where' : 'orWhere';
$columnType = $model_instance->getColumnType($searchColumn);
if (in_array($columnType, $textColumnTypes)) {
$config['query'] = $config['query']->{$operation}($searchColumn, $searchOperator, '%'.$search_string.'%');
} else {
$config['query'] = $config['query']->{$operation}($searchColumn, $search_string);
}
}
}
// return the results with or without pagination
return $this->fetchGet($config);
}
private function fetchGet($config){
if($config['paginate'] !== false) {
$get = $config['query']->simplePaginate($config['paginate']);
}else{
$get = $config['query']->get();
}
if(isset($config['attributes'])){
if(!is_array($config['attributes'])){
$config['attributes'] = [$config['attributes']];
}
$get = $get->map(function($item) use($config) {
$attributes = [];
$attributes['id'] = $item->{$item->getKeyName()};
foreach($config['attributes'] as $attribute){
$attributes[$attribute] = $item->{$attribute};
}
return $attributes;
});
}
return $get;
}
}
If I add the 'extra_attribute_on_the_model' or multiple attributes ['extra_attribute_on_the_model','another_extra_attribute_on_the_model'] to the fetch config, this now works with mapping the extra attribute to the result collection.
Backpack, Laravel, PHP, DB version
When I run php artisan backpack:version the output is:
Bug report
What I did
I added a relationship field using the fetch, but I wanted to show an attribute of the model not an actual field in the database. This does not work because the select blade is using pluck.
I could fix this using the $appends to add the 'extra_attribute_on_the_model' on the model, but this is a huge overhead because of the extra joins.
What I expected to happen
Be able to use attributes in the fetch on the field.
What happened
The field tries to pluck the attribute, but this does not exist.
What I've already tried to fix it
I modified the vendor/backpack/pro/src/Http/Controllers/Operations/FetchOperation.php with an extra config 'attributes':
I added the fetchGet function to remove redundancy (search vs no search)
If I add the 'extra_attribute_on_the_model' or multiple attributes ['extra_attribute_on_the_model','another_extra_attribute_on_the_model'] to the fetch config, this now works with mapping the extra attribute to the result collection.
Backpack, Laravel, PHP, DB version
When I run
php artisan backpack:version
the output is:The text was updated successfully, but these errors were encountered: