I added External Scope for User Model. Simply created Scope, with the name DeveloperScope
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Auth;
class DeveloperScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
if(Auth::user()->id != 1){
$builder->where('id', '<>', 1);
}
}
}
Then, I called this scope for User model.
User Model
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\DeveloperScope;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use SoftDeletes;
use Authenticatable, CanResetPassword;
protected $table = 'users';
protected $fillable = ['first_name', 'last_name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
protected $dates = ['deleted_at'];
protected static function boot()
{
parent::boot();
static::addGlobalScope(new DeveloperScope);
}
}
It works well, when I don't use Auth::class inside DeveloperScope class. The reason is that, I just want to hide the main user for another users for all Eloquent methods. Of course, I can use session instead of Auth and retrieve user id. But it is still interesting for me, why browser gives an error: ERR_EMPTY_RESPONSE while using Auth::class ?
The method what you are looking for is exactly Auth::hasUser(). The method was added in Laravel 5.6 through my PR. (So you need to upgrade Laravel first)
[5.6] Add ability to determine if the current user is ALREADY authenticated without triggering side effects by mpyw · Pull Request #24518 · laravel/framework
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class DeveloperScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
if (!Auth::hasUser() || Auth::user()->id != 1) {
$builder->where('id', '<>', 1);
}
}
}
Just call Auth::hasUser() to prevent Auth::user() from causing side effects.
Try injecting the Illuminate\Auth\Guard class and then call the user method on it. e.g.:
$this->auth->user();
You should be fine with just type-hinting it since Laravel will automatically new up an instance for you.
For more info check out the API: https://laravel.com/api/5.2/Illuminate/Auth/Guard.html
Related
I'm using Laratrust to manage these roles:
Patient
Doctor
I've a class called User which is the main entity, then I have a specific class for each role: Patient and Doctor.
Problem
To retrieve a list of users with the role of doctor I have to write:
User::whereRoleIs('doctor')->get();
The problem's that I have defined some relationship within the class Doctor, eg:
<?php
namespace App\Models;
use App\Models\Boilerplate\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Doctor extends User
{
use HasFactory;
protected $table = 'users';
public function patients()
{
return $this->belongsToMany(Patient::class, 'invites', 'doctor_id', 'user_id');
}
}
and I cannot access to the relationship patients from the User model.
Is there a way to call Doctor:with('patients')->get() and return automatically the users which have the role of Doctor?
So if I type: Doctor:all() the result must be equal to User::whereRoleIs('doctor')->get()
How can I do this?
Splitting data like this by model isn't really the intended use for models. Models in Laravel contain data on a per-table basis. In order to achieve what you want I would either make a DocterService that has methods in it to retrieve docter users by calling User::whereRoleIs('doctor') or just use this method straight away.
If you really want to use the model though you can use scopes. (https://laravel.com/docs/9.x/eloquent#query-scopes) Create a new scope that includes the whereRoleIs('doctor') method
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class DoctorScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->whereRoleIs('doctor');
}
}
and apply it to the model by adding the following to the model:
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::addGlobalScope(new DoctorScope);
}
I want to add a global scope to my project. and use it in some models.
so I create this code: (in app/scopes folder)
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class GameStoreScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param Builder $builder
* #param Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
//$storeId = \request()->header('Store');
//dd("asdad");
$builder->where('game_store_id', '=', 1);
}
}
And use it to my model like this:
<?php
namespace App;
use App\Scopes\GameStoreScope;
use Illuminate\Database\Eloquent\Model;
class Player extends Model
{
protected $guarded = [];
protected static function boot()
{
parent::boot();
static::addGlobalScope(new GameStoreScope);
}
but after run my project. always get this FatalError : Class 'App\Scopes\GameStoreScope' not found
Fix namespace ref link https://laravel.com/docs/8.x/eloquent#query-scopes
namespace App\Scopes;
use App\Scopes\GameStoreScope;
use Illuminate\Database\Eloquent\Model;
then run composer dump-autoload
This is my User Model
namespace App;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia\HasMedia;
use Spatie\MediaLibrary\HasMedia\HasMediaTrait;
use Spatie\MediaLibrary\File;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements HasMedia
{
use HasMediaTrait;
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function role(){
return $this->belongsToMany('App/Role');
}
}
This is my UserController
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function store(Request $request)
{
$user=new User;
$user->name=($request['name']);
$user->email=($request['email']);
$password=bcrypt($request['password']);
$user->password=$password;
$user_photo=$request['photo'];
$user->addMediaFromRequest('photo')->toMediaCollection('images');
$user->save();
return redirect('/admin');
}
}
I want to use Spatie Media Library and upload a photo for each user but I get this error related to Spatie Library:
"Call to undefined method
Illuminate\Foundation\Auth\User::addMediaFromRequest()".
I have read some related posts but I don't understand how to fix this.
Thank you.
Version 8 of the library has this documentation. Are you using the correct trait?
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class YourModel extends Model implements HasMedia
{
use InteractsWithMedia;
}
Edited:
On another note, using HasMediaTrait is for version 7 so I assume you're using version 7. I think it's because you're importing the wrong User class. The User class used to implement HasMedia lies in the App\User namespace. But you're importing the User class from the Illuminate\Foundation\Auth\User namespace. So change it to use App\User; and you should be fine.
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function store(Request $request)
{
$user=new User;
$user->name=($request['name']);
$user->email=($request['email']);
$password=bcrypt($request['password']);
$user->password=$password;
$user_photo=$request['photo'];
$user->addMediaFromRequest('photo')->toMediaCollection('images');
$user->save();
return redirect('/admin');
}
}
https://docs.spatie.be/laravel-medialibrary/v8/basic-usage/preparing-your-model/
we are developing an application based on Laravel Spark. as part of this we want to tie resources to a specfic team.
I know that we can add a global scope such as:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class TeamScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('team_id', '=',Auth()->user()->currentTeam->id );
}
}
but according to the docs we have to add that to each model that we want to restrict like so:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope);
}
my issue with this is that it will be possible to create future models and forget to apply this code. Which could give us a security hole?
is there any way to enforce the scope across the board?
I am not sure if there's a way to globally add the Scope.
In my particular application, we have had to add more responsiblities to our Models. So we created a BaseModel class that extends Laravel's Illuminate\Database\Eloquent\Model.
All new Models then extends the BaseModel instead of Laravel's one.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope);
}
}
For example:
<?php
namespace App;
class Attribute extends BaseModel
{
}
You could also have a trait that you can just use to add this scope to your Model. For example:
trait HasTeamScope
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope);
}
}
}
... and then you can easily re-use that in your Model.
For example:
<?php
namespace App;
class Attribute extends BaseModel
{
use HasTeamScope;
}
Now, based on your question, you might also forget to extend the BaseModel in the first instance or add the Trait in the second one whenever you create a new model.
To solve this, you could easily create a new command to produce models that will use your own stub (which extends the BaseModel or adds the trait whenever you create a new model)
You could create your own base model with the desired global scope that future models would extend.
You should create trait with boot function. Trait named BelongsToTeam.
And in all models add only: use BelongsToTeam;
I have a global scope that is used for a few models. Here is the scope class:
<?php
namespace App\Scopes;
use App\Models\UserMeta;
use Auth;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class WorkplaceScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$user_active_workplace = UserMeta::where(['user_id' => Auth::id(), 'meta_key' => 'active_workplace'])->first();
$builder->where('workplace_id', $user_active_workplace->meta_value);
}
}
I am assigning the scope by overriding the model's boot method:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WorkplaceScope);
}
The problem is that I make a query inside the scope and that query is executed a lot of times for no reason. I was thinking of using primitive binding inside a service provider, but I can't figure out how to do it properly. I tried adding this inside the AppServiceProvider:
$this->app->when('App\Scopes\WorkplaceScope')
->needs('$user_active_workplace_meta_value')
->give(1);
but I don't know how to hint the service provider that the WorkplaceScope expects a $user_active_workplace_meta_value variable. Any idea how can I do this?