Laravel : Get all columns except some of them [duplicate] - php
When I'm using eloquent, I can use the "where" method then the method 'get' to fill an object containing what I've selected in my database.
I mean:
$users = User::where('gender', 'M')->where('is_active', 1)->get(['pseudo', 'email', 'age', 'created_at'])->toArray();
Here I can choose the columns I want to get like 'pseudo', 'email', etc..
But what I miss in laravel doc is the way to do the contrary.
It could be something like that:
$users = User::where('gender', 'M')->where('is_active', 1)->notGet(['pseudo', 'email', 'age', 'created_at'])->toArray();
Thank you for you futur answer and have a nice day.
If you only need to hide attributes from your model's array or JSON representation, you may use one or both approaches:
Add the
$hidden property to your model
class User extends Model
{
/**
* The attributes that should be hidden for arrays.
*/
protected $hidden = ['password'];
}
Use the
makeHidden
function
$users = $users->makeHidden(['address', 'phone_number']);
See other answers for more details... But sometimes you don't want to load huge data (geospatial, html, logs...) into your application, it will be slow and take more memory. OP asked for an SQL query hence my answer, but most of the time it's more convenient to only hide the data from the JSON response.
AFAIK there is no build in option in SQL to exclude columns explicitly, so Laravel can't do it. But you can try this trick
Update
Another trick is to specify all columns in your model (or use an extra query to get all columns using $this->getTableColumns() from this answer, it can also be cached after each migration to avoid two queries) then add a local scope function
// The below code requires you to define all columns in $columns.
// A better approach is to query the schema of the table and cache it after each
// migration, for more details: https://stackoverflow.com/a/56425794/3192276
protected $columns = ['id','pseudo','email'];
public function scopeExclude($query, $value = [])
{
return $query->select(array_diff($this->columns, (array) $value));
}
Then you can do :
$users = User::where('gender', 'M')
->where('is_active', 1)
->exclude(['pseudo', 'email', 'age', 'created_at'])
->toArray();
using hidden array in model is good, but if you don't want to hide your column all the time and use makeVisible to access them in need, then instead, hide your column from serialization where you need with makeHidden function like this :
$res = Model::where('your query')->get();
$res->makeHidden(['column_one','column_two','column_n']);
return response()->json($res);
I don't know about previous Laravel version, but in 5.4 you can put this line in User model
protected $hidden = ['pseudo', 'email', 'age', 'created_at'];
and then User::find(1); will return all fields except pseudo, email, age, and created_at.
But you still can retrieve those hidden fields by using:
$user = User::find(1);
$email = $user['email']; // or $user->email;
I have looked into the answer by #Razor
But there is Very Conveinent way by skipping $columns property
/**
* Scope a query to only exclude specific Columns.
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeExclude($query, ...$columns)
{
if ($columns !== []) {
if (count($columns) !== count($columns, COUNT_RECURSIVE)) {
$columns = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($columns)));
}
return $query->select(array_diff($this->getTableColumns(), $columns));
}
return $query;
}
/**
* Shows All the columns of the Corresponding Table of Model
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* If You need to get all the Columns of the Model Table.
* Useful while including the columns in search
* #return array
**/
public function getTableColumns()
{
return \Illuminate\Support\Facades\Cache::rememberForever('MigrMod:'.filemtime(database_path('migrations')).':'.$this->getTable(), function () {
return $this->getConnection()->getSchemaBuilder()->getColumnListing($this->getTable());
});
}
getTableColumns function will get all the columns of the table so you dont need to define the $column property
NOTE: COLUMN NAMES OF TABLE WILL BE CACHED UNTIL CONTENTS OF MIGRATIONS DIRECTORY IS ADDED OR DELETED.
MODIFYING THE CONTENTS OF FILES INSIDE THE MIGRATIONS DIRECTORY WILL
NOT RE-CACHE THE COLUMNS
To clear cache manually you can run php artisan cache:clear
you can use hidden array like this:
class Promotion extends Model
{
protected $table = 'promotion';
protected $hidden = array('id');
}
I have a solution that worked for me, which is slightly different than those already stated.
Solution:
$all_columns = Schema::getColumnListing('TABLE_NAME');
$exclude_columns = ['COLUMN_TO_EXCLUDE_1', 'COLUMN_TO_EXCLUDE_2'];
$get_columns = array_diff($all_columns, $exclude_columns);
return User::select($get_columns)->get();
Reasoning:
For me:
Razor's answer didn't work as I got the following error:
BadMethodCallException with message 'Call to undefined method App/CaseStudy::exclude()'
Then, the remaining answers were attemping to hide the columns within the model. Unfortunately, that would hide them for each method in my class and this isn't something that I wanted.
So, in the end, I modified Razor's solution so that it would work without having to hide any of the columns for each method.
I hope this helps someone! 😊
We get the object eloquent from the model full with all fields, transform it to array and we put it inside of a collection. Than we get all fields except all fields specified in array $fields.
$fields = ['a', 'b', 'c', 'N'];
$object = Model::find($id);
return collect($object->toArray())->except($fields);
More clearly, let's give an example:
// Array of fields you want to remove
$fields_to_remove = ['age', 'birthday', 'address'];
// Get the result of database
$user = User::find($id);
// Transform user object to array
$user = $user->toArray();
// Create a collection with the user inside
$collection = collect($user);
// Get all fields of our collection except these fields we don't want
$result = $collection->except($fields_to_remove);
// Return
return $result;
This example above makes exactly the same thing of the first one, but it's more explained.
you can use makeHidden array like this: (After get() or all())
$users = User::where('gender', 'M')->where('is_active', 1)->get()->makeHidden(['pseudo', 'email', 'age', 'created_at'])->toArray();
You can leverage Illuminate\Support\Facades\Schema::getColumnListing('table_name');
use Illuminate\Support\Facades\Schema;
$users_table_columns = Schema::getColumnListing('users');
$exclude_columns = [
'password',
'token',
'address',
];
$select = array_diff($users_table_columns, (array) $exclude_columns);
$site = User::select($select)
->where('gender', 'M')
->where('is_active', 1)
->first();
I wrapped a slitly changed approach from #manojkiran-a up in a small package, cause I needed it in multiple projects:
https://github.com/laracraft-tech/laravel-useful-traits/#selectallbut
Install via composer:
composer require laracraft-tech/laravel-useful-traits
This is how it is working:
use LaracraftTech\LaravelUsefulTraits\UsefulScopes;
$class = new class extends Model
{
use UsefulScopes;
protected $timestamps = false;
protected $table = 'scope_tests';
};
$class->create([
'foo' => 'foo',
'bar' => 'bar',
'quz' => 'quz',
]);
$class::query()->selectAllBut(['foo'])->first()->toArray();
// return ['bar' => 'bar', 'quz' => 'quz']
Note: Since you can't do a native "select all but x,y,z" in mysql, we need to query (and cache) the existing columns of the table, and then exclude the given columns which should be ignored (not selected) from the existing columns.
Cache: Column names of each table will be cached until contents of migrations directory is added or deleted. Modifying the contents of files inside the migrations directory will not re-cache the columns. Consider to clear the cache whenever you make a new deployment/migration!
You can use unset unset($category->created_at,$category->updated_at);
$fcategory = array();
$kCategory = KCategory::where("enabled", true)->get();
foreach ($kCategory as $category) {
$subkCategory = PostCategory::select("id", "name", "desc")
->where("id_kcategory", $category->id)
->where("enabled", true)
->get();
unset($category->created_at, $category->updated_at);
$fcategory[] = $category;
}
Related
Laravel filter data after with closure
I have one quite simple question, Imagine I have Orders model and now I am writing something like that : Order::where('status', 1)->with('orderer')->get(); Ok. It's simple and returns something like that: { id: 1, price: 200, status: 1, income: 21, orderer_id: 4, orderer: { //some orderer fields } } now I don't want to get the whole object, I want to remove income, orderer_id and status properties from data. if I write something like that : get(["id", "price"]) I end up without orderer object (get(["id", "price", "orderer"]) doesn't work too), I couldn't make it work even using select(), so what is the solution? Also I don't want to hide it from everyone, for example admin should know income but user shouldn't, so $hidden field will not work.
You can add select() but make sure select does not take array but comma separated arguments : $orders = Order::where('status', 1)->with('orderer'); if($user->role == 'admin'){ $orders->select('id','income','status','price'); } else{ $orders->select('id','status','price'); } $orders = $orders->get(); Above will first check the current logged in user's role and accordingly will select the columns required.
https://scotch.io/bar-talk/hiding-fields-when-querying-laravel-eloquent-models In your Order Eloquent model: protected $hidden = array('hide_this_field', 'and_that_field'); Edit: You want to filter based on role like Admin or User, next time please write that down in your question as well. Well a solution for that is to capture the DB query result, and walk that array, then unset properties of the model if the user is not an admin. Edit2: I also see a discussion here which might help. Some user suggested using middle ware: https://laracasts.com/discuss/channels/laravel/hide-eloquent-fields-based-on-user-role-or-any-model
If you are looking for a built in Laravel way to handle this, you could use API Resources: https://laravel.com/docs/5.7/eloquent-resources php atrisan make:resource OrderResource namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class OrderResource extends JsonResource { /** * Transform the resource into an array. * * #param \Illuminate\Http\Request $request * #return array */ public function toArray($request) { $current_role = $request->user()->role; //or however you determine admin etc $out = [ 'id' => $this->id, 'price' => $this->price, 'orderer'=> $this->orderer, ]; if($current_role == 'admin'){ $out['income'] = $this->income; $out['status'] = $this->status; } return $out; } } In your Controller action return OrderResource::collection(Order::where('status', 1)->with('orderer')->get()); If you want something a little more robust, consider https://github.com/spatie/laravel-fractal
Laravel 5.6: Invoking eloquent relationships change the collection data
Is there a way to invoke eloquent relationship methods without changing the original eloquent collection that the method runs on? Currently I have to employ a temporary collection to run the method immutable and to prevent adding entire related record to the response return: $result = Item::find($id); $array = array_values($result->toArray()); $temp = Item::find($id); $title = $temp->article->title; dd($temp); //This prints entire article record added to the temp collection data. array_push($array, $title); return response()->json($array);
You are not dealing with collections here but with models. Item::find($id) will get you an object of class Item (or null if not found). As far as I know, there is no way to load a relation without storing it in the relation accessor. But you can always unset the accessor again to delete the loaded relation (from memory). For your example, this process yields: $result = Item::find($id); $title = $result->article->title; unset($result->article); return response()->json(array_merge($result->toArray(), [$title])); The above works but is no very nice code. Instead, you could do one of the following three things: Use attributesToArray() instead of toArray() (which merges attributes and relations): $result = Item::find($id); return response()->json(array_merge($result->attributesToArray(), [$result->article->title])); Add your own getter method on the Item class that will return all the data you want. Then use it in the controller: class Item { public function getMyData(): array { return array_merge($this->attributesToArray(), [$this->article->title]); } } Controller: $result = Item::find($id); return response()->json($result->getMyData()); Create your own response resource: <?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class ItemResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'title' => $this->article->title, 'author' => $this->article->author, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } } Which can then be used like this: return new ItemResource(Item::find($id)); The cleanest approach is option 3. Of course you could also use $this->attributesToArray() instead of enumerating the fields, but enumerating them will yield you security in future considering you might extend the model and do not want to expose the new fields.
I see two ways you can achieve that. First, you can use an eloquent Resource. Basically it'll allow you to return exactly what you want from the model, so in your case, you'll be able to exclude the article. You can find the documentation here. The second way is pretty new and is still undocumented (as fas i know), but it actually works well. You can use the unsetRelation method. So in your case, you just have to do: $article = $result->article; // The article is loaded $result->unsetRelation('article'); // It is unloaded and will not appear in the response You can find the unsetRelation documentation here
There is not as far as I know. When dealing with Model outputs, I usually construct them manually like this: $item = Item::find($id); $result = $item->only('id', 'name', 'description', ...); $result['title'] = $item->article->title; return $result; Should you need more power or a reusable solution, Resources are your best bet. https://laravel.com/docs/5.6/eloquent-resources#concept-overview
Laravel Eloquent
When I try to fill my database using the model method create like this: public function registerDevice($command) { $deviceId = $command->deviceId; $deviceToken = $command->deviceToken; $this->deviceId = $deviceId; $this->deviceToken = $deviceToken; Device::create(array('device_id' => $deviceId, 'device_token' => $deviceToken)); $this->raise(new DeviceWasRegistered($this)); return $this; } The entry is being made, but only the timestamps are being updates. The value fields are empty. No error coming up or something else is failing. But the values I want to put into the db are there if I var_dump the variables. Do I miss something out?
In order for the create method to work, you need to put your two fields in the $fillable array on the model. So make sure you have this in your model: protected $fillable = [ 'device_id', 'device_token', ]; You can read more about the create method and mass assignment at http://laravel.com/docs/5.1/eloquent#mass-assignment.
Is it possible to map a column name in an Laravel Eloquent model?
I have a DB table with a column "visible" (true/false). The Eloquent model already has a property visible, see this code snippet taken from Illuminate\Database\Eloquent\Model. /** * The attributes that should be visible in arrays. * * #var array */ protected $visible = array(); When printing out the model, the "visible" property is the already existing array instead of the column value. Is there a way to rename the column in the Model? I already tried this (found here: http://laravel.com/docs/4.2/eloquent#accessors-and-mutators) public function setVisibleAttribute($value) { $this->attributes['isvisible'] = $value; } public function getVisibleAttribute($value) { $this->attributes['isvisible']; } I know I can solve the problem like this: User::select('id', 'name', 'visible as isVisible')->get(); I was just wondering if there isn't a really built in mechanism. Important note: the DB is also used by existing software so renaming it isn't really an option.
How to exclude certains columns while using eloquent
When I'm using eloquent, I can use the "where" method then the method 'get' to fill an object containing what I've selected in my database. I mean: $users = User::where('gender', 'M')->where('is_active', 1)->get(['pseudo', 'email', 'age', 'created_at'])->toArray(); Here I can choose the columns I want to get like 'pseudo', 'email', etc.. But what I miss in laravel doc is the way to do the contrary. It could be something like that: $users = User::where('gender', 'M')->where('is_active', 1)->notGet(['pseudo', 'email', 'age', 'created_at'])->toArray(); Thank you for you futur answer and have a nice day.
If you only need to hide attributes from your model's array or JSON representation, you may use one or both approaches: Add the $hidden property to your model class User extends Model { /** * The attributes that should be hidden for arrays. */ protected $hidden = ['password']; } Use the makeHidden function $users = $users->makeHidden(['address', 'phone_number']); See other answers for more details... But sometimes you don't want to load huge data (geospatial, html, logs...) into your application, it will be slow and take more memory. OP asked for an SQL query hence my answer, but most of the time it's more convenient to only hide the data from the JSON response. AFAIK there is no build in option in SQL to exclude columns explicitly, so Laravel can't do it. But you can try this trick Update Another trick is to specify all columns in your model (or use an extra query to get all columns using $this->getTableColumns() from this answer, it can also be cached after each migration to avoid two queries) then add a local scope function // The below code requires you to define all columns in $columns. // A better approach is to query the schema of the table and cache it after each // migration, for more details: https://stackoverflow.com/a/56425794/3192276 protected $columns = ['id','pseudo','email']; public function scopeExclude($query, $value = []) { return $query->select(array_diff($this->columns, (array) $value)); } Then you can do : $users = User::where('gender', 'M') ->where('is_active', 1) ->exclude(['pseudo', 'email', 'age', 'created_at']) ->toArray();
using hidden array in model is good, but if you don't want to hide your column all the time and use makeVisible to access them in need, then instead, hide your column from serialization where you need with makeHidden function like this : $res = Model::where('your query')->get(); $res->makeHidden(['column_one','column_two','column_n']); return response()->json($res);
I don't know about previous Laravel version, but in 5.4 you can put this line in User model protected $hidden = ['pseudo', 'email', 'age', 'created_at']; and then User::find(1); will return all fields except pseudo, email, age, and created_at. But you still can retrieve those hidden fields by using: $user = User::find(1); $email = $user['email']; // or $user->email;
I have looked into the answer by #Razor But there is Very Conveinent way by skipping $columns property /** * Scope a query to only exclude specific Columns. * * #author Manojkiran.A <manojkiran10031998#gmail.com> * #param \Illuminate\Database\Eloquent\Builder $query * #return \Illuminate\Database\Eloquent\Builder */ public function scopeExclude($query, ...$columns) { if ($columns !== []) { if (count($columns) !== count($columns, COUNT_RECURSIVE)) { $columns = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($columns))); } return $query->select(array_diff($this->getTableColumns(), $columns)); } return $query; } /** * Shows All the columns of the Corresponding Table of Model * * #author Manojkiran.A <manojkiran10031998#gmail.com> * If You need to get all the Columns of the Model Table. * Useful while including the columns in search * #return array **/ public function getTableColumns() { return \Illuminate\Support\Facades\Cache::rememberForever('MigrMod:'.filemtime(database_path('migrations')).':'.$this->getTable(), function () { return $this->getConnection()->getSchemaBuilder()->getColumnListing($this->getTable()); }); } getTableColumns function will get all the columns of the table so you dont need to define the $column property NOTE: COLUMN NAMES OF TABLE WILL BE CACHED UNTIL CONTENTS OF MIGRATIONS DIRECTORY IS ADDED OR DELETED. MODIFYING THE CONTENTS OF FILES INSIDE THE MIGRATIONS DIRECTORY WILL NOT RE-CACHE THE COLUMNS To clear cache manually you can run php artisan cache:clear
you can use hidden array like this: class Promotion extends Model { protected $table = 'promotion'; protected $hidden = array('id'); }
I have a solution that worked for me, which is slightly different than those already stated. Solution: $all_columns = Schema::getColumnListing('TABLE_NAME'); $exclude_columns = ['COLUMN_TO_EXCLUDE_1', 'COLUMN_TO_EXCLUDE_2']; $get_columns = array_diff($all_columns, $exclude_columns); return User::select($get_columns)->get(); Reasoning: For me: Razor's answer didn't work as I got the following error: BadMethodCallException with message 'Call to undefined method App/CaseStudy::exclude()' Then, the remaining answers were attemping to hide the columns within the model. Unfortunately, that would hide them for each method in my class and this isn't something that I wanted. So, in the end, I modified Razor's solution so that it would work without having to hide any of the columns for each method. I hope this helps someone! 😊
We get the object eloquent from the model full with all fields, transform it to array and we put it inside of a collection. Than we get all fields except all fields specified in array $fields. $fields = ['a', 'b', 'c', 'N']; $object = Model::find($id); return collect($object->toArray())->except($fields); More clearly, let's give an example: // Array of fields you want to remove $fields_to_remove = ['age', 'birthday', 'address']; // Get the result of database $user = User::find($id); // Transform user object to array $user = $user->toArray(); // Create a collection with the user inside $collection = collect($user); // Get all fields of our collection except these fields we don't want $result = $collection->except($fields_to_remove); // Return return $result; This example above makes exactly the same thing of the first one, but it's more explained.
you can use makeHidden array like this: (After get() or all()) $users = User::where('gender', 'M')->where('is_active', 1)->get()->makeHidden(['pseudo', 'email', 'age', 'created_at'])->toArray();
You can leverage Illuminate\Support\Facades\Schema::getColumnListing('table_name'); use Illuminate\Support\Facades\Schema; $users_table_columns = Schema::getColumnListing('users'); $exclude_columns = [ 'password', 'token', 'address', ]; $select = array_diff($users_table_columns, (array) $exclude_columns); $site = User::select($select) ->where('gender', 'M') ->where('is_active', 1) ->first();
I wrapped a slitly changed approach from #manojkiran-a up in a small package, cause I needed it in multiple projects: https://github.com/laracraft-tech/laravel-useful-traits/#selectallbut Install via composer: composer require laracraft-tech/laravel-useful-traits This is how it is working: use LaracraftTech\LaravelUsefulTraits\UsefulScopes; $class = new class extends Model { use UsefulScopes; protected $timestamps = false; protected $table = 'scope_tests'; }; $class->create([ 'foo' => 'foo', 'bar' => 'bar', 'quz' => 'quz', ]); $class::query()->selectAllBut(['foo'])->first()->toArray(); // return ['bar' => 'bar', 'quz' => 'quz'] Note: Since you can't do a native "select all but x,y,z" in mysql, we need to query (and cache) the existing columns of the table, and then exclude the given columns which should be ignored (not selected) from the existing columns. Cache: Column names of each table will be cached until contents of migrations directory is added or deleted. Modifying the contents of files inside the migrations directory will not re-cache the columns. Consider to clear the cache whenever you make a new deployment/migration!
You can use unset unset($category->created_at,$category->updated_at); $fcategory = array(); $kCategory = KCategory::where("enabled", true)->get(); foreach ($kCategory as $category) { $subkCategory = PostCategory::select("id", "name", "desc") ->where("id_kcategory", $category->id) ->where("enabled", true) ->get(); unset($category->created_at, $category->updated_at); $fcategory[] = $category; }