Laravel Eloquent: how to set custom attribute when model is construted? - laravel

I am trying to add a custom attribute "role" to my User model. My current implementation is like this:
User extends Authenticatable
{
protected $appends = array('role');
public $getRoleAttribute()
{
$role = DB::table('acl_user_has_roles')->where('user_id', $this->id)
->value('role');
return $role;
}
}
This implementation largely works. The concern is, this role attribute is referenced many times in the life time of $user instance. Whenever it is reference, the getRoleAttribute() function will be called, then database queries will be executed. It seems a bit unnecessary to me, so I am trying find a way to only run these queries once, preferably when model instance is contructed:
I tried to override model constructor as described in answer to another similar question:
public $role;
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
$this->role= $this->role();
}
protected function role()
{
$role = DB::table('acl_user_has_roles')->where('user_id', $this->id)
->value('role');
return $role;
}
When I tried to reference the role attribute like this:
$user = User::find(1);
echo $user->role;
I get nothing.
if I simply set the role attribute to some dummy text:
$this->role = "Dummy Role";
instead of:
$this->role();
Then I can get this "Dummy Role" text.
What am I missing here?

Related

laravel model, adding attribute to model

I have a user model, and I want to add (an attribute to the user model) the user's email that it was before it was updated.
before#email.com
new#email.com
Within the user model, I have this function, I can get the before email, I was wondering I can assign some fake attribute, so I can access it like: $user->beforeEmail
protected static function booted()
{
static::saved(function ($user) {
$user->beforeEmail = $user->original['email'];
});
}
$user->beforeEmail // before#email.com
The code above is not working but provided it to help illustrate what I am trying to accomplish.
You could check if the email address has changed just before storing the new email to the db. This can be accomplished by using the saving event instead of the saved event.
protected static function booted()
{
static::saving(function ($user) {
if($user->isDirty('email')){
$user->beforeEmail = $user->email
}
});
}
Note: Your code example will not save the changes automatically since the saved event is ran after executing the query. It's possible that your code works just by adding $user->save()
Are you trying to get this value in the model or in a different class? As what you have works with a few adjustments already.
protected static function boot(){
parent::boot();
static::saved(function($user){
$user->originalEmail = $user->original['email'];
}
}
You can access originalEmail if you update the model in a controller or other class, like so:
$user = User::find(1);
$user->update([
'email' => 'email#email.com'
]);
// $user, $user->originalEmail, $user->some_test_accessor all return the correct values
I've also tested with an accessor, just to verify and it still works as though the value is available in the model. I'm not sure where you're attempting to access this value, though.
public function getSomeTestAccessorAttribute(){
return $this->originalEmail;
}
Does any of this help?

How do I use a policy on an index that doesn't use the model the policy belongs to?

What I am trying to do is apply a policy on a control method that lists a bunch of records instead of just one record like most of the examples I have seen.
Instead of checking against the ThoughtRecords I want to check the signed in user hashedId to the user that's being queried hashedId in the controller index() method.
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
AuthServiceProvider.php
protected $policies = [
'App\ThoughtRecord' => 'App\Policies\ThoughtRecordPolicy',
];
public function boot()
{
$this->registerPolicies();
}
ThoughtRecordPolicy.php
public function view(User $signedInUser, User $client)
{
//return true;
dd('Policy working');
//return $signedInUser->id === $client->id;
}
ThoughtRecordController.php
public function index($userHashedId)
{
$client = User::where('hashed_id', $userHashedId)->first();
$this->authorize('view', ThoughtRecord::class, $client);
$records = ThoughtRecord::where('user_id', $client->id)->latest()->paginate(1);
return ThoughtRecordResource::collection($records);
}
Error
Too few arguments to function App\Policies\ThoughtRecordPolicy::view()
I have also tried:
$this->authorize('view', $client);
This action is unauthorized.
As said:
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
You need pass both the ThoughtRecord::class and the $client into an array:
$this->authorize('view', [ThoughtRecord::class, $client]);

Using an Eloquent relationship within an Eloquent accessor?

I am writing a Laravel application that manages training courses.
Each course is represented by a Course model.
A course can have many dates - these are represented by a CourseDate model, with a hasMany relationship between the two:
Each course also has a single "date template", which is a CourseDate, but with an "is_template" boolean set.
I want to create an accessor on the Course model that retrieves its date template.
The (relevant) code for each model is:
class Course extends Model {
public function getDateTemplateAttribute() {
$dates = $this->dates;
$filtered = $dates->where('is_template', true);
$template = $filtered->first();
return $template;
}
public function dates() {
$result = $this->hasMany( CourseDate::class );
return $result;
}
}
class CourseDate extends Model {
public function course() {
return $this->belongsTo( Course::class );
}
}
Then, in my controller, I have this:
// this block works absolutely perfectly
$course = Course::find(1);
$dates = $course->dates;
$working_date_template = $dates->where('is_template', true)->first();
// this one doesn't work at all and says "call to a member function first() on array"
$broken_date_template = $course->date_template;
Stepping through with xdebug in the broken code, the line $dates = $this->dates returns an empty array so everything else afterwards breaks.
Is this a limitation with the Laravel accessor/relationship system? Or am I just being dense and doing something wrong.
I worked this out just now.
I needed to use $this->dates() within the model itself as this returns the relationship and I can then filter it out accordingly using the where() method and other query builder methods.
This was, of course, mentioned in the Laravel documentation - I just didn't spot it.

Laravel - Redirect on model constructor

I have a Laravel project with some model which have been pointed to external databases (from Magento). On the constructor of this models I want to check a session variable in order to change the model connection to the user selection. One of this checks must be if the user selected some external platform, for connecting to this, if not I wanna redirect to the selection page. I am trying with this to avoid session expiration and manual route writing (without platform selection).
How can I redirect the user to certain page in the model constructor?
Main Model
class MagentoModel extends Model
{
protected $platform;
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->platform = app('selectedPlatform');
if ($this->platform->id < 1) {
return redirect()->route('index');
// Maybe I have to use Redirect::route('index');
}
}
}
Certain model extending MagentoModel
class Customer extends MagentoModel
{
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->connection = $this->platfom->connection_key;
$this->table = config('customersTable');
}
}
It throws an error right now:
Trying to get property 'connection_key' of non-object
I supose the parent constructor (MagentoModel) is not being executed before the line $this->connection = $this->platfom->connection_key;. Is possible my OOP comprehension is not right just in this case.

Trouble getting attribute of relation in Laravel

I'm having a trouble with a relation in Laravel 5. the thing is that I have a table User and that user belongs to a Group for that, in the User model I have this:
public function group(){
return $this->belongsTo('App\models\Group');
}
The model Group have this attributes: name,unity,level, init_date. I also put there a default function to return a group as String, this is the code:
public function __toString(){
return $this->name.' Unity '.$this->unity;
}
So, the thing that in a view a have many users and for each of them I want to display the unity, name,date. When I call $user->group it returns me correctly the name and the unity in a String (because the _toString function) that means that he is really querying the group perfectly, but then, when I want to access a simple attribute as unity,date,or name with $user->group->name Laravel gives me this error:
Trying to get property of non-object
I even tried $user->group()->name then I gets: Undefined property: Illuminate\Database\Eloquent\Relations\BelongsTo::$name
Edited:
The model User:
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
use Authenticatable, CanResetPassword;
protected $table = 'users';
protected $fillable = ['email', 'password','estate','filial_id','perfil_id','rol','cat_teacher'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
public function __toString(){
return $this->email;
}
public function filial(){
return $this->belongsTo('App\models\Filial');
}
public function perfil(){
return $this->belongsTo('App\models\Perfil','perfil_id');
}
public function grupo(){
return $this->belongsTo('App\models\Group','group_id','id');
}
}
The model Group:
class Group extends Model {
protected $table = 'groups';
protected $fillable = ['name','unity','date'];
public function filiales() {
return $this->belongsTo('App\models\Filial');
}
public function teacher(){
return $this->belongsTo('App\models\User','teacher_id');
}
public function users() {
return $this->hasMany('App\models\User');
}
}
Then, in the controller I made a dd($users) and there not appear the relations, appears other relations but not this one. In the view I want to print some of the attributes in a table, for that I have:
<td>{{$user->group}}</td>
<td>{{$user->group->unity}}</td>
The first line works perfectly, and I donĀ“t know why.
The reason you're unable to return your group's name is that ->group() returns an instance of the query builder, and not an eloquent collection/object. Since a user belongs to a single group, modify your code in one of these two ways:
public function group(){
return $this->belongsTo('App\models\Group')->first();
}
And then access the group using one of the following methods:
$user = User::with("group")->where("id", "=", 1)->first();
$group = $user->group;
echo $group->name;
// OR
$user = User::where("id", "=", 1)->first();
$group = $user->group();
echo $group->name;
Or, leave the group() function as it is and access ->group()->first() on your $user object:
$user = User::where("id", "=", 1)->first();
$group = $user->group()->first();
echo $group->name;
Any of the above methods should properly return your group object's name (or other attributes). Check the Eloquent Documentation for detailed explanations on how to access these objects.

Resources