Extending the Laravel Eloquent\Collection Class - laravel

I understand that the default Eloquent\Collection class can be overridden in your model by using the method:
public function newCollection(array $models = array()) {
return new CustomCollection($models);
}
Which works great if I'm using typical queries such as:
Model::where('name', $name)->get();
This is great so I can add methods to the eloquent collection class, such as:
$records = Model::where('name', $name)->get();
$records->toTable();
But if I'm using pagination on the model, for example:
Model::where('name', $name)->paginate(25);
It returns an instance of the class Illuminate\Support\Collection instead of the Illuminate\Database\Eloquent\Collection.
Is there a way of overriding or extending the typical Illuminate\Support\Collection?
I'm trying to add a toTable() method to the returned Collection. I'd rather not have to replace the pagination service provider with my own.
Thanks!!

You will need to replace the pagination service provider, amongst a couple of other classes in the pagination library. By the sound of it you know how to do it this way, but were hoping for another answer, but as I have the code I'll drop it in here for you.
The reason you need to replace these classes/methods is because the files in Illuminate directly reference instances of classes within the Illuminate namespace.
In config/app.php
Replace
'Illuminate\Pagination\PaginationServiceProvider',
With
'ExtendedPaginationServiceProvider',
Create a new file somewhere the autoloader is capable of finding it called ExtendedPaginationServiceProvider.php and place the following in it
<?php
use Illuminate\Support\ServiceProvider;
class ExtendedPaginationServiceProvider extends ServiceProvider
{
/**
* #inheritdoc
*/
public function register()
{
$this->app->bindShared('paginator', function($app)
{
$paginator = new ExtendedPaginationFactory($app['request'], $app['view'], $app['translator']);
$paginator->setViewName($app['config']['view.pagination']);
$app->refresh('request', $paginator, 'setRequest');
return $paginator;
});
}
}
Create a new file somewhere the autoloader is capable of finding it called ExtendedPaginationFactory.php and place the following in it
<?php
use Illuminate\Pagination\Factory;
class ExtendedPaginationFactory extends Factory
{
/**
* #inheritdoc
*/
public function make(array $items, $total, $perPage = null)
{
$paginator = new ExtendedPaginationPaginator($this, $items, $total, $perPage);
return $paginator->setupPaginationContext();
}
}
Create a new file somewhere the autoloader is capable of finding it called ExtendedPaginationPaginator.php and place the following in it
<?php
use Illuminate\Pagination\Paginator;
class ExtendedPaginationPaginator extends Paginator
{
/**
* Get a collection instance containing the items.
*
* #return ExtendedCollection
*/
public function getCollection()
{
return new ExtendedCollection($this->items);
}
}
You'll notice the above returns a new instance of ExtendedCollection. Obviously replace this with your CustomCollection class you refer to in your question.
For others to reference, an ExtendedCollection class may look similar to the below
Create a new file somewhere the autoloader is capable of finding it called ExtendedCollection.php and place the following in it
<?php
use Illuminate\Support\Collection;
class ExtendedCollection extends Collection
{
}
Also, after creating these files, don't forget to run the following in the terminal
composer dump-autoload

Related

How to mock laravel model relathipship?

I have a model that has a relationship with a View, that is complicate to popolate for make the feature test, but in the same time this is called from some component that are inside the controller called.
The following code is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\TemperatureView;
class Town extends Model
{
function temperature()
{
return $this->hasOne(TemperatureView::class);
}
}
This is an example of the controller:
<?php
namespace App\Http\Controllers;
use App\Models\Town;
class TownController extends Controller
{
public function update($id)
{
// Here is the validation and update of Town model
$UpdatedTown = Town::where('id',$id);
$UpdatedTown->update($data);
$this->someOperation($UpdatedTown);
}
private function someOperation($Town)
{
//Here there is some operation that use the temperature Relationship
/*
Example:
$Town->temperature->value;
*/
}
}
The test is like is like this:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\TownModel;
use Mockery;
use Mockery\MockInterface;
class TownTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_get_town_temperature()
{
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test failed
}
public function test_get_town_temperature_with_mocking()
{
$this->instance(
TownModel::class,
Mockery::mock(TownModel::class, function (MockInterface $mock) {
$MockDataTemperature = (object) array('value'=>2);
$mock->shouldReceive('temperature')->andReturn($MockDataTemperature);
})
);
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test also failed
}
}
The first test failed because the Controller has some check on the relationship temperature, that is empty because the view on database is empty.
The second test failed also for the same reason. I tried to follow some others questions with the official guide of Laravel Mocking. I know this is mocking object and not specially Eloquent.
Is something I'm not setting well?
If it's not possible to mock only the function, is possible to mock all the relationship of view, bypassing the DB access to that?
Edit
I undestand that the mocking work only when the class is injected from laravel, so what I wrote above it's not pratical.
I don't know if it's possible to mock only it, I saw a different option, that to create the interface of the model and change for the test, but I didn't want to make it.

Which namespace do i use for laravel view()?

My ide typhints a few different namespaces for rendering a view in my controller and i'm not sure which one i'm supposed to use:
class PostsController extends Controller
{
public function index() : View
{
return view('posts.index');
}
}
The "view" function returns multiple types:
#return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
So which one am i supposed to use? \Illuminate\Contracts\View\View or \Illuminate\Contracts\View\Factory
What is the difference?
Why is it returning two different types instead of one? I code php in a strict way because i prefer it for readable and less error prone code, in my opinion this is a bad way of doing it and as you can see is causing confusion; a method should only be allowed one return type, create multiple methods if need be.
EDIT
Thank you for your input everyone, i have come up with the following that allows me to use the facade and the contract without producing typhint errors in my ide:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View as ViewContract;
use Illuminate\Support\Facades\View as ViewFacade;
/**
* Class PostsController
* #package App\Http\Controllers
*/
class PostsController extends Controller
{
/**
* #return ViewContract
*/
public function index() : ViewContract
{
return ViewFacade::make('posts.index');
}
}
So i can call View:make() and return the contract that View:make() returns.
EDIT 2
Using the view() helper i can condense further, i am aliasing with ViewContract just for my benefit of knowing which namespace i'm using:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View as ViewContract;
/**
* Class PostsController
* #package App\Http\Controllers
*/
class PostsController extends Controller
{
/**
* #return ViewContract
*/
public function index() : ViewContract
{
return view('posts.index');
}
}
I’ll try and address each of your questions.
In your instance, type-hint Illuminate\Contracts\View\View or the concrete implementation (Illuminate\View\View).
I’ll cover this in 3.
You’re using the view global helper function. It can return different types because, well, it can. If you don’t pass a parameter to view() then it will return a view factory instance. If you do pass a parameter (like you have in your usage), then it will return an instance of the view named by the first parameter (if such a view exists). So that’s why the view() helper function is typed to return multiple different types. Because depending on how you use it, it can return a different type.
You mean: \Illuminate\View\View.
public function index(): \Illuminate\View\View
{
return view('posts.index');
}
view() is a helper of Illuminate\Support\Facades\View facade.
return view('posts.index');
Same as :
use Illuminate\Support\Facades\View;
return View::make('posts.index');
See the documentation of Creating & Rendering Views

Laravel 5.3 soft deletes not working when defining a constructor for the model

I have a model Test as follows
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Test extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function __construct() {
if (!\App::environment('production')) {
$this->table='test_stage';
}
}
I have made sure that there is a 'deleted_at' column in my test_stage table. But the soft deletes are not working. Using the delete() method permanently removes the record from the table. As an additional step of verification I manually added 'deleted_at' value for some columns. But query the model still gives me the soft deleted record.
Moreover, removing the model constructor entirely and simply defining the table name using:
protected $table = 'test_stage';
Works like a charm! That is soft deletes magically start working again.
Or is there any way around to define the table name according to the environment without the need of defining a constructor?
I think the problem could be that you're overwriting the constructor, which is set in Illuminate\Database\Eloquent\Model. Have you tried
public function __construct(array $attributes = []) {
parent::__construct($attributes);
if (!\App::environment('production')) {
$this->table='test_stage';
}
}
Edit: more detailed explaination
As you overwrite the constructor of the class you're extending, the original does not get executed anymore. This means necessary functions for the eloquent model do not get executed. See the constructor for Illuminate\Database\Eloquent\Model below:
/**
* Create a new Eloquent model instance.
*
* #param array $attributes
* #return void
*/
public function __construct(array $attributes = [])
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
By making sure the extending class requires the same parameters for the constructor as the extended class and executes parent::__construct($attributes); first, the constructor of the extended class gets executed first. After which you can overwrite $this->table in the extending class.

global variable for all controller and views

In Laravel I have a table settings and i have fetched complete data from the table in the BaseController, as following
public function __construct()
{
// Fetch the Site Settings object
$site_settings = Setting::all();
View::share('site_settings', $site_settings);
}
Now i want to access $site_settings. in all other controllers and views so that i don't need to write the same code again and again, so anybody please tell me the solution or any other way so i can fetch the data from the table once and use it in all controllers and view.
Okay, I'm going to completely ignore the ridiculous amount of over engineering and assumptions that the other answers are rife with, and go with the simple option.
If you're okay for there to be a single database call during each request, then the method is simple, alarmingly so:
class BaseController extends \Controller
{
protected $site_settings;
public function __construct()
{
// Fetch the Site Settings object
$this->site_settings = Setting::all();
View::share('site_settings', $this->site_settings);
}
}
Now providing that all of your controllers extend this BaseController, they can just do $this->site_settings.
If you wish to limit the amount of queries across multiple requests, you could use a caching solution as previously provided, but based on your question, the simple answer is a class property.
At first, a config file is appropriate for this kind of things but you may also use another approach, which is as given below (Laravel - 4):
// You can keep this in your filters.php file
App::before(function($request) {
App::singleton('site_settings', function(){
return Setting::all();
});
// If you use this line of code then it'll be available in any view
// as $site_settings but you may also use app('site_settings') as well
View::share('site_settings', app('site_settings'));
});
To get the same data in any controller you may use:
$site_settings = app('site_settings');
There are many ways, just use one or another, which one you prefer but I'm using the Container.
Use the Config class:
Config::set('site_settings', $site_settings);
Config::get('site_settings');
http://laravel.com/docs/4.2/configuration
Configuration values that are set at run-time are only set for the current request, and will not be carried over to subsequent requests.
In Laravel, 5+ you can create a file in the config folder and create variables in that and use that across the app.
For instance, I want to store some information based on the site.
I create a file called site_vars.php,
which looks like this
<?php
return [
'supportEmail' => 'email#gmail.com',
'adminEmail' => 'admin#sitename.com'
];
Now in the routes, controller, views you can access it using
Config::get('site_vars.supportEmail')
In the views if I this
{{ Config::get('site_vars.supportEmail') }}
It will give email#gmail.com
Hope this helps.
EDiT-
You can also define vars in .env file and use them here.
That is the best way in my opinion as it gives you the flexibility to use values that you want on your local machine.
So, you can do something this in the array
'supportEmail' => env('SUPPORT_EMAIL', 'defaultmail#gmail.com')
Important - After you do this, don't forget to do this on production env
php artisan config:cache
In case, there's still some problem, then you can do this (usually it would never happen but still if it ever happens)
php artisan cache:clear
php artisan config:cache
In your local env, always do this after this adding it
php artisan config:clear
It's always a good practice not to cache config vars in local. in case, it was cached, this would remove the cache and would load the new changes.
I see, that this is still needed for 5.4+ and I just had the same problem, but none of the answers were clean enough, so I tried to accomplish the availability with ServiceProviders. Here is what i did:
Created the Provider SettingsServiceProvider
php artisan make:provider SettingsServiceProvider
Created the Model i needed (GlobalSettings)
php artisan make:model GlobalSettings
Edited the generated register method in \App\Providers\SettingsServiceProvider. As you can see, I retrieve my settings using the eloquent model for it with Setting::all().
public function register()
{
$this->app->singleton('App\GlobalSettings', function ($app) {
return new GlobalSettings(Setting::all());
});
}
Defined some useful parameters and methods (including the constructor with the needed Collection parameter) in GlobalSettings
class GlobalSettings extends Model
{
protected $settings;
protected $keyValuePair;
public function __construct(Collection $settings)
{
$this->settings = $settings;
foreach ($settings as $setting){
$this->keyValuePair[$setting->key] = $setting->value;
}
}
public function has(string $key){ /* check key exists */ }
public function contains(string $key){ /* check value exists */ }
public function get(string $key){ /* get by key */ }
}
At last I registered the provider in config/app.php
'providers' => [
// [...]
App\Providers\SettingsServiceProvider::class
]
After clearing the config cache with php artisan config:cache you can use your singleton as follows.
$foo = app(App\GlobalSettings::class);
echo $foo->has("company") ? $foo->get("company") : "Stack Exchange Inc.";
You can read more about service containers and service providers in Laravel Docs > Service Container and Laravel Docs > Service Providers.
This is my first answer and I had not much time to write it down, so the formatting ist a bit spacey, but I hope you get everything.
I forgot to include the boot method of SettingsServiceProvider, to make the settings variable global available in views, so here you go:
public function boot(GlobalSettings $settinsInstance)
{
View::share('globalsettings', $settinsInstance);
}
Before the boot methods are called all providers have been registered, so we can just use our GlobalSettings instance as parameter, so it can be injected by Laravel.
In blade template:
{{ $globalsettings->get("company") }}
View::share('site_settings', $site_settings);
Add to
app->Providers->AppServiceProvider file boot method
it's global variable.
Most popular answers here with BaseController didn't worked for me on Laravel 5.4, but they have worked on 5.3. No idea why.
I have found a way which works on Laravel 5.4 and gives variables even for views which are skipping controllers. And, of course, you can get variables from the database.
add in your app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Using view composer to set following variables globally
view()->composer('*',function($view) {
$view->with('user', Auth::user());
$view->with('social', Social::all());
// if you need to access in controller and views:
Config::set('something', $something);
});
}
}
credit: http://laraveldaily.com/global-variables-in-base-controller/
In Laravel 5+, to set a variable just once and access it 'globally', I find it easiest to just add it as an attribute to the Request:
$request->attributes->add(['myVar' => $myVar]);
Then you can access it from any of your controllers using:
$myVar = $request->get('myVar');
and from any of your blades using:
{{ Request::get('myVar') }}
In Laravel 5.1 I needed a global variable populated with model data accessible in all views.
I followed a similar approach to ollieread's answer and was able to use my variable ($notifications) in any view.
My controller location: /app/Http/Controllers/Controller.php
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use App\Models\Main as MainModel;
use View;
abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function __construct() {
$oMainM = new MainModel;
$notifications = $oMainM->get_notifications();
View::share('notifications', $notifications);
}
}
My model location: /app/Models/Main.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class Main extends Model
{
public function get_notifications() {...
I have found a better way which works on Laravel 5.5 and makes variables accessible by views. And you can retrieve data from the database, do your logic by importing your Model just as you would in your controller.
The "*" means you are referencing all views, if you research more you can choose views to affect.
add in your app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Contracts\View\View;
use Illuminate\Support\ServiceProvider;
use App\Setting;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// Fetch the Site Settings object
view()->composer('*', function(View $view) {
$site_settings = Setting::all();
$view->with('site_settings', $site_settings);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}
If you are worried about repeated database access, make sure that you have some kind of caching built into your method so that database calls are only made once per page request.
Something like (simplified example):
class Settings {
static protected $all;
static public function cachedAll() {
if (empty(self::$all)) {
self::$all = self::all();
}
return self::$all;
}
}
Then you would access Settings::cachedAll() instead of all() and this would only make one database call per page request. Subsequent calls will use the already-retrieved contents cached in the class variable.
The above example is super simple, and uses an in-memory cache so it only lasts for the single request. If you wanted to, you could use Laravel's caching (using Redis or Memcached) to persist your settings across multiple requests. You can read more about the very simple caching options here:
http://laravel.com/docs/cache
For example you could add a method to your Settings model that looks like:
static public function getSettings() {
$settings = Cache::remember('settings', 60, function() {
return Settings::all();
});
return $settings;
}
This would only make a database call every 60 minutes otherwise it would return the cached value whenever you call Settings::getSettings().
You can also use Laravel helper which I'm using.
Just create Helpers folder under App folder
then add the following code:
namespace App\Helpers;
Use SettingModel;
class SiteHelper
{
public static function settings()
{
if(null !== session('settings')){
$settings = session('settings');
}else{
$settings = SettingModel::all();
session(['settings' => $settings]);
}
return $settings;
}
}
then add it on you config > app.php under alliases
'aliases' => [
....
'Site' => App\Helpers\SiteHelper::class,
]
1. To Use in Controller
use Site;
class SettingsController extends Controller
{
public function index()
{
$settings = Site::settings();
return $settings;
}
}
2. To Use in View:
Site::settings()
A global variable for using in controllers; you can set in AppServiceProvider like this :
public function boot()
{
$company=DB::table('company')->where('id',1)->first();
config(['yourconfig.company' => $company]);
}
usage
config('yourconfig.company');
using middlwares
1- create middlware with any name
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\View;
class GlobalData
{
public function handle($request, Closure $next)
{
// edit this section and share what do you want
$site_settings = Setting::all();
View::share('site_settings', $site_settings);
return $next($request);
}
}
2- register your middleware in Kernal.php
protected $routeMiddleware = [
.
...
'globaldata' => GlobalData::class,
]
3-now group your routes with globaldata middleware
Route::group(['middleware' => ['globaldata']], function () {
// add routes that need to site_settings
}
In file - \vendor\autoload.php, define your gobals variable as follows, should be in the topmost line.
$global_variable = "Some value";//the global variable
Access that global variable anywhere as :-
$GLOBALS['global_variable'];
Enjoy :)
I know I am super late to the party, but this was the easiest way I found.
In app/Providers/AppServiceProvider.php, add your variables in the boot method. Here I am retrieving all countries from the DB:
public function boot()
{
// Global variables
view()->composer('*',function($view) {
$view->with('countries', Country::all());
});
}
There are two options:
Create a php class file inside app/libraries/YourClassFile.php
a. Any function you create in it would be easily accessible in all the views and controllers.
b. If it is a static function you can easily access it by the class name.
c. Make sure you inclued "app/libraries" in autoload classmap in composer file.
In app/config/app.php create a variable and you can reference the same using
Config::get('variable_name');
Hope this helps.
Edit 1:
Example for my 1st point:
// app/libraries/DefaultFunctions.php
class DefaultFunctions{
public static function getSomeValue(){
// Fetch the Site Settings object
$site_settings = Setting::all();
return $site_settings;
}
}
//composer.json
"autoload": {
"classmap": [
..
..
..
"app/libraries" // add the libraries to access globaly.
]
}
//YourController.php
$default_functions = new DefaultFunctions();
$default_functions->getSomeValue();

Dynamically hide certain columns when returning an Eloquent object as JSON?

How do dynamically hide certain columns when returning an Eloquent object as JSON? E.g. to hide the 'password' column:
$users = User::all();
return Response::json($users);
I'm aware I can set protected properties in the model ($hidden or $visible), but how do I set these dynamically? I might want to hide or show different columns in different contexts.
$model->getHidden();
$model->setHidden(array $columns);
$model->setVisible(array $columns);
From Lavarel 5.3 Documentation :
Temporarily Modifying Attribute Visibility
If you would like to make some typically hidden attributes visible on a given model instance, you may use the makeVisible method. The makeVisible method returns the model instance for convenient method chaining:
return $user->makeVisible('attribute')->toArray();
Likewise, if you would like to make some typically visible attributes hidden on a given model instance, you may use the makeHidden method.
return $user->makeHidden('attribute')->toArray();
I've found a complete solution around the problem with using $model->setHidden(array $columns);
Lets say, for example, that you would like to decide in the controller exactly which fields to return. Updating only the model's hidden forces you to go over each model before you return an array of models for example. The problem becomes even worse when those models have relationships that you would also like to change. You have to loop over each model, set the hidden attribute, and then for each also set the relationships hidden. What a mess.
My solution involves creating a static member for each model that when present, updates the visible/hidden attribute just before the call to "toArray":
<?php
trait DynamicHiddenVisible {
public static $_hidden = null;
public static $_visible = null;
public static function setStaticHidden(array $value) {
self::$_hidden = $value;
return self::$_hidden;
}
public static function getStaticHidden() {
return self::$_hidden;
}
public static function setStaticVisible(array $value) {
self::$_visible = $value;
return self::$_visible;
}
public static function getStaticVisible() {
return self::$_visible;
}
public static function getDefaultHidden() {
return with(new static)->getHidden();
}
public static function geDefaultVisible() {
return with(new static)->getVisible();
}
public function toArray() {
if (self::getStaticVisible())
$this->visible = self::getStaticVisible();
else if (self::getStaticHidden())
$this->hidden = self::getStaticHidden();
return parent::toArray();
}
}
As an added bonus, I expose a way to the model's default hidden/visible that you may have set in your model's class.
Don't to forget to add the trait
class Client extends Eloquent {
use DynamicHiddenVisible;
}
Finally, in the controller, before returning your model, decide on visible/hidden attributes:
public function getIndex($clientId) {
// in this specific call, I would like to hide the "special_type" field of my Client model
$hiddenFields = Client::getDefaultHidden();
array_push($hiddenFields, "special_type");
Client::setStaticHidden($hiddenFields);
return Client::find($clientId)->toJson();
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
In 5.4 you can hide and show attributes dinamically:
$model->makeVisible('attribute');
$model->makeHidden('attribute');
Laravel docs
In addition to #deczo's answer - I feel the $hidden variable is not really designed to be used dynamically. It is more to protect specific data from ever been incorrectly displayed (such as 'password').
If you want specific columns - you should probably just be using a select statement and just get the specific columns you want.
For Laravel 5.3 or greater version,
If you want to make multiple attributes temporary hidden or visible using single statement, you may use model->makeVisible() and model->makeHidden() methods with passing array of attributes.
For example, to hide multiple attributes,
$user->makeHidden(["attribute1", "attribute2", "attribute3"]);
And to make visible multiple attributes,
$user->makeVisible(["otherAttribute1", "otherAttribute2", "otherAttribute3"]);
In the Model:
protected $hidden = [
'your_field_1',
'your_field_2',
];
You can override the getHidden method in order to hide certain columns dynamically:
class FooModel extends Model
{
public function getHidden()
{
// do here your validations and return
// the columns names with the specific criteria
// you need
return ['columnName1', 'columnName2'];
}
}
Made a package for this that uses Model Policies.
https://github.com/salomoni/authorized-attributes
Use the Salomoni\AuthorizedAttributes trait
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Salomoni\AuthorizedAttributes;
class Post extends Model
{
use AuthorizedAttributes;
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = ['author_comments'];
}
Create and register a model policy. Add methods for the hidden attributes in camel-case prefixed with see.
namespace App\Policies;
use App\User;
class PostPolicy
{
/**
* Determine if a post author_comments-atrribute can be seen by the user.
*
* #param \App\User $user
* #return bool
*/
public function seeAuthorComments(User $user)
{
return $user->isAuthor();
}
}

Resources