How to filter User by role based on the called Model? - laravel

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

Related

Make updates to Illuminate\Database\Eloquent\Model

I'm pretty sure this is not the best thing to do - change/add/update Laravel core but I'm facing the following problem.
Version 7 has a new date serialization format (more info here) and I want to update it to the old return value. What they suggest to do is add this in each model.
protected function serializeDate(DateTimeInterface $date) {
return $date->format('Y-m-d H:i:s');
}
As I have numerous models I was wondering if I can do a more general thing and update the abstract Model Class for example. Any suggestions?
As Eloquent model is not localized by default in your project. You should create a Model class and inherit from the eloquent model
namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model as BaseModel;
/**
* Class Model
* #package App\Models
* #method static count
* #method static latest
* #method static oldest
*/
class Model extends BaseModel
{
/**
* Prepare a date for array / JSON serialization.
*
* #param DateTimeInterface $date
* #return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
}
Then, update all of the models to inherit from your local Model class, not the Illuminate\Database\Eloquent\Model

Class 'App\Scope\__name_Scope' not found in lumen (Laravel micro-framework)

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

Enforce Global Scope Across all models

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;

Laravel Role HasMany relationship

I have a User Model and a Role Model which are in many-to-many relation.
I have two roles: admin and manager.
I also have an Order Model. Managers need to have many orders. Where do I state such relations? Do I have to state it in the User Class?
Do I need to create separate models for Admins and Managers?
Managers & admins are a subset of your users, defined by their roles.
Therefore, we'll use scope to filter users who are managers by their roles.
App\Scopes\ManagerUserScope.php
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class ManagerUserScope 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)
{
//Assuming Your user has a relationship (has many) role
$builder->whereHas('role', function($roleQuery) {
//Assuming that in the role table, the manager role entry has ID 1
$roleQuery->where('id','=',1);
});
}
}
Then, we extend the User model to create a manager model which has the above scope automatically applied.
App\Models\Manager.php
namespace App\Models;
use App\Scopes\ManagerUserScope;
class Manager extends User {
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ManagerUserScope);
}
/**
* Relationship: Orders (Has many)
*/
public function orders()
{
return $this->hasMany('orders');
}
}
Your many-to-many relationship between User and Role can be perfectly described with belongsToMany Eloquent relation methods both ways. Also since each Order must have responsible manager for it we also have one-to-many relation between Manager and Order which will be described with hasMany/belongsTo methods.
So, your User model will have:
public function roles()
{
return $this->belongsToMany('App\Role');
}
public function orders()
{
return $this->hasMany('App\Order');
}
For your Role model:
public function users()
{
return $this->belongsToMany('App\User');
}
And lastly your Order model:
public function manager()
{
return $this->belongsTo('App\User');
}
There is no need to create a certain limitations (like "only users with role manager can have orders") on DB schema level, it's easier to implement in the code. So, for example, you might want to implement a method that will assign order to user and check his roles first.

Eloquent ORM relationships one-to-one or one-to-many?

I see the power of using Eloquent but have yet to put it to good use in my project. With two tables I want to achieve this:
//output - LinkedIn
echo User::find(42)->SocialProvider->Name
I have a user table and I have a SocialProvider table with a list of social sites with their name and api key info.
Record 42 in my user's table has a column 'SocialProviderID' with the the id of the LinkedIn record in my SocialProvider table.
I have defined the relationships in model classes as following
class User extends BaseModel implements UserInterface, RemindableInterface {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'dbo_RegisteredUser';
protected $primaryKey = "UserID";
public function SocialProvider() { return $this->hasOne('SocialProvider','id'); }
AND
class SocialProvider extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'dbo_SocialProvider';
public function user() { return $this->belongsTo('User','SocialProviderID'); }
But the queries and results are not executing as I'd like.
Does anyone have an ideas?
I'm using MSSQL but in my more familiar MYSQL, I would like this ORM scenario to perform a join like this:
SELECT * FROM dbo_RegisteredUser
LEFT JOIN (dbo_SocialProvider)
ON (dboRegisteredUser.SocialProviderID=dbo_SocialProvider.id)
WHERE dbo_RegisteredUser.UserID=42
Thanks
Jon.
Ahh I figured it out with a bit of debugging. I specified the relationships incorrectly.
In my User model I needed
class User extends Eloquent implements UserInterface, RemindableInterface {
public function socialProvider() {
return $this->belongsTo('SocialProvider','SocialProviderID');
}
Well...relationships use the ID of the model in question....and you're not referencing the actual user anywhere...
You need to have a collumn in the social Provider table that stores the user_id
Then in your functions...
public function SocialProvider() { return $this->hasOne('SocialProvider','user_id'); }
and...
public function user() { return $this->belongsTo('User'); }

Resources