94

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.

Brazeredge
  • 1,061
  • 1
  • 7
  • 12
  • The question is, why you want to do that? Using ORM you'd rather not do it, and if you just don't want to show some of the columns, there are other ways to achieve that. – Jarek Tkaczyk May 12 '14 at 15:18
  • 16
    I ask it because when you have 15 columns and you want 13, It could be faster to do something like ->notGet(['column14', 'column15']); instead of ->get(['column1', 'column2', [...], 'column13']);. You see ? – Brazeredge May 12 '14 at 15:49

11 Answers11

112

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();
Razor
  • 9,577
  • 3
  • 36
  • 51
  • 2
    ->exclude ? This will result in method not allowed. – Leon Sep 19 '16 at 13:53
  • 7
    @Leon The above model function `scopeExclude()` is called like that. Read about laravel scopes on https://laravel.com/docs/5.3/eloquent#local-scopes – cari Nov 28 '16 at 17:15
  • This method does not works when chained with [eager relationship loading](https://laravel.com/docs/5.4/eloquent-relationships#eager-loading) : the model itself returns the correct column without the exluded ones, but relationships fail to be retrieved. – Anwar Apr 20 '17 at 14:06
  • I'm not sure I understand your issue, but in most cases if a relationship fails to be retrieved your are not selection the primary key in the model itself. Thus if it's the `id` column, do not exclude it because Laravel use it internally for mapping. – Razor Apr 21 '17 at 18:36
  • $this->columns doesn't seem to be working anymore, returns null – Miguel Stevens Feb 24 '19 at 12:02
  • @Notflip it still works on Laravel5.7 php7.3, make sure you are not overwriting the property `$columns` – Razor Feb 24 '19 at 17:24
  • @Razor is there any workaround for newer versions, like Laravel 8, without loading the columns from the database schema using `Schema::getColumnListing('table')`? – Christos Lytras Nov 09 '21 at 01:08
  • 1
    @ChristosLytras I'm not aware of any other workaround, as I said it's an SQL limitation, so Laravel can't do it with one query. The overall consensus in all DBMS is *query the schema* so you may define an sql function or procedure or a view... You may also cache the result of `Schema::getColumnListing('table')` and clear it on each migration, that way you avoid an extra SQL query. – Razor Nov 09 '21 at 09:58
  • 1
    hey @Razor I have updated my answer to cache support – ManojKiran A Nov 09 '21 at 11:24
88

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);
iamab.in
  • 2,022
  • 3
  • 18
  • 39
sajed zarrinpour
  • 1,194
  • 10
  • 10
  • 11
    This is not usefull if that column is large, you still will be querying it, the idea is NOT to query it. I have a table with a column that is a shape geometry, each value is like 500KB, I have to call like 100 objects of that model, I need to `except`it in my query. – pmiranda May 22 '19 at 21:43
  • 1
    Depend on the usage, I need to hide `created_at`, `updated_at`, `deleted_at`, this answer is the most perfect for me. – Michael Harley Jun 03 '19 at 10:41
  • This answer is best if you want for example hide one field from serialization. – Ruslan Skaldin Jul 22 '19 at 12:33
  • 1
    This is the best solution for me as well. You can also serialize them like `Model::first()->relationship->makeHidden(['field1', 'field2']);` – Angelin Calu Jun 05 '20 at 11:46
  • 1
    Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::makeHidden() – Fernando Torres Jun 14 '21 at 20:52
  • @FernandoTorres, this answer is old, you can find the detail about this method in the latest version here: https://laravel.com/docs/8.x/eloquent-collections#method-makeHidden – sajed zarrinpour Oct 26 '21 at 19:16
  • `User::where("id", 1)->get()->makeHidden(['accessPermission', 'verification_timestamp']);` – Mohamed Raza Jun 12 '23 at 20:15
50

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;
Christhofer Natalius
  • 2,727
  • 2
  • 30
  • 39
  • 2
    also available in Laravel 5.1 – Bugfixer Feb 08 '18 at 04:13
  • 13
    It hides it from output (toArray(), toJSON()) but still loads it from DB, so this approach is useless when you need to not load certain data – Stalinko Nov 07 '18 at 18:51
  • 1
    @Stalinko If you have certain data in the model you do not want to load at all by default it sounds like you should split up the resources and use relationships. – kb. Dec 16 '18 at 04:45
  • 1
    @kb, I won't agree. Splitting resources and using relationships is a very hard solution, for most complicated cases only. In real life it's pretty common task to load only currently required columns. For example there can be heavy `description` column which is necessary only when you load a single model, but not necessary when you load all the models. Skipping it saves a lot of memory. – Stalinko Dec 16 '18 at 06:11
  • @Stalinko I only partially agree with that. I see your point, but I feel that is the responsibility of a collection to limit the dataset where you need to process a lot of it/there are computational resources to save in only using specific fields. A model should be predictable and atomic, and for special cases where you still need it there are many ways to filter the object/load it by direct queries/builders. If you have [esp large] data fields in your models you often don't want to load it should probably be in a separate model. – kb. Dec 17 '18 at 11:27
17

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

ManojKiran A
  • 5,896
  • 4
  • 30
  • 43
  • 1
    I like your approach, as it is reusable in any Model. The only downside is the getTableColumns() method causing one extra query to DB... but, if that is not a problem (small apps), it's OK – paulmartimx Jun 22 '20 at 18:48
  • Yeah as you said it will make one extra db query but if you application is small you can use my approach but for enterprise application you can store column names in cache . Whenever you make a new deployment you can clear the cache. – ManojKiran A Jun 23 '20 at 07:38
  • 1
    Yes that's a better approach, you can avoid an extra query by caching the result after each migration, I will link to your answer. – Razor Nov 09 '21 at 10:08
  • Note that if you want to use this code for multiple tables, you *must use a different cache key for each table*. – bart Oct 03 '22 at 11:17
  • Will update the answer asap – ManojKiran A Oct 03 '22 at 11:32
  • Now it supports cache unique to each table – ManojKiran A Dec 15 '22 at 04:45
10

you can use hidden array like this:

class Promotion extends Model
{
    protected $table = 'promotion';
    protected $hidden = array('id');
}
vuhung3990
  • 6,353
  • 1
  • 45
  • 44
8

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:

  1. Razor's answer didn't work as I got the following error:

BadMethodCallException with message 'Call to undefined method App/CaseStudy::exclude()'

  1. 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!

Brad Ahrens
  • 4,864
  • 5
  • 36
  • 47
  • Awesome! Thank you! However, just in case, in the result, the order of the resulted column array may mismatch with both the one in the schema and stored in the database, and in case of Laravel, the same with model attributes. – Artfaith Nov 22 '22 at 17:59
7

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.

Rafael Xavier
  • 956
  • 13
  • 13
  • 1
    This one works except that you fetch it from the database to begin with. the main purpose i would want to exclude some staff is so that i do not get huge data from the database – Flash Mar 29 '20 at 11:29
3

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();

Mohamed Hany
  • 451
  • 4
  • 10
2

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();
alisongaleon
  • 153
  • 2
  • 4
  • 13
  • Awesome! Thank you! Related: https://stackoverflow.com/questions/23612221/how-to-exclude-certains-columns-while-using-eloquent#comment131574919_63261970 (*...the order of the resulted column array may mismatch with*...) – Artfaith Nov 22 '22 at 18:02
1

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-additions/#selectallbut

Install via composer:

composer require laracraft-tech/laravel-useful-additions

This is how it is working:

use LaracraftTech\LaravelUsefulAdditions\Traits\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!

Zacharias
  • 57
  • 9
-4

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;
}
dastan
  • 892
  • 10
  • 17
  • 1
    Its worked for me in `"laravel/lumen-framework": "5.7.8"` – dastan May 01 '20 at 10:59
  • This is done on php end on MySQL/DB end. You are still fetching all the columns from DB. This is just removing(unset) the data from the collections of already fetched data. – Anuj Shrestha Jul 23 '20 at 05:31
  • yes, correct, fetching all data from DB but removing a field from the collection using unset, but I don't know why it's not working for others, and its downvoted. it's excluding filed. – dastan Jul 23 '20 at 06:47
  • it is because we have already got the non required column data on memory. For a small application or if the column has a small data size then it will not be an issue. If you can optimize on database end then that should always be the priority. – Anuj Shrestha Jul 23 '20 at 07:08
  • Ya but if its require in one API but not in another API – dastan Jul 23 '20 at 07:14