laravel model, adding attribute to model - laravel

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?

Related

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

What is the best way to add the authenticated user to a related model?

What is the best way to add the authenticated user to a related model?
If I wanted to add the authenticated user as the author on a post model whenever a new post is created, what would be the best way to do it?
Currently, I have the following which does the job but it runs an extra query (i.e 1. Create post. 2. Update post with author_id).
public function store(Request $request)
{
$post = Post::create($request->all());
$post→author()->associate($request->user());
$post→save();
return new PostResource($post);
}
There must be a better way to do this. I was thinking of just adding all the attributes manually, with $post-author_id = $request→user()→id, and then calling $post-save(). However, I do not like the idea of having to manually write out all the other attributes of the post.
Another option I was thinking is by creating an event listener on the Post creating event. I do not know if this will reduce the need for an extra query.
What is the simplest solution?
Instead of using the create method, you could simply create a new instance of PostResource and fill it with author_id. So it would be a bundled query.
public function store(Request $request) {
$post = new Post($request->all());
$post->author_id = Auth::user()->id;
$post->save();
return new PostResource($post);
}
I hope it helps.
Maybe you can consider this on related model:
/**
* Save auth user on create
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$userID = auth()->id();
$model->author_id = $userID;
});
}

Laravel nova resource extending/overriding the create method

I am developing a web admin panel using Laravel Nova.
I am having an issue since Nova is quite a new technology.
What I would like to do now is I would like to add a hidden field or extend or override the create method.
This is my scenario. Let's say I have a vacancy nova resource with the following field.
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title')->sortable(),
Text::make('Salary')->sortable()
// I will have another field, called created_by
];
}
Very simple. What I like to do is I want to add a new field called created_by into the database. Then that field will be auto filled with the current logged user id ($request->user()->id).
How can I override or extend the create function of Nova? How can I achieve it?
I can use resource event, but how can I retrieve the logged in user in
the event?
What you're looking for is Resource Events.
From the docs:
All Nova operations use the typical save, delete, forceDelete, restore Eloquent methods you are familiar with. Therefore, it is easy to listen for model events triggered by Nova and react to them.
The easiest approach is to simply attach a model observer to a model:
If you don't feel like creating a new observable you could also create a boot method in your eloquent model as so:
public static function boot()
{
parent::boot();
static::creating(function ($vacancy) {
$vacancy->created_by = auth()->user()->id;
});
}
But please do note that these are a bit harder to track than observables, and you or a next developer in the future might be scratching their head, wondering how's the "created_at" property set.
In my opinion you should go for Observers. Observers will make you code more readable and trackable.
Here is how you can achieve the same with Laravel Observers.
AppServiceProvider.php
public function boot()
{
Nova::serving(function () {
Post::observe(PostObserver::class);
});
}
PostObserver.php
public function creating(Post $post)
{
$post->created_by = Auth::user()->id;
}
OR
You can simply hack a Nova field using withMeta.
Text::make('created_by')->withMeta([
'type' => 'hidden',
'value' => Auth::user()->id
])
You could also do that directly within your Nova resource. Every Nova resource has newModel() method which is called when resource loads fresh instance of your model from db. You can override it and put there your logic for setting any default values (you should always check if values already exist, and only set if they are null, which will only be the case when the model is being created for the first time, which is what you actually need):
public static function newModel()
{
$model = static::$model;
$instance = new $model;
if ($instance->created_by == null) {
$instance->created_by = auth()->user()->id;
}
return $instance;
}
a) Create an Observer class with following command:
php artisan make:observer -m "Post" PostObserver
b) Add following code in the PostObserver:
$post->created_by = Auth::user()->id;
c) Register PostObserver in AppServiceProvider.php
For detailed explanation: https://medium.com/vineeth-vijayan/how-to-add-a-new-field-in-laravel-nova-resource-87f79427d38c
Since Nova v3.0, there is a native Hidden field.
Usage:
Hidden::make('Created By', 'created_by')
->default(
function ($request) {
return $request->user()->id;
}),
Docs: https://nova.laravel.com/docs/3.0/resources/fields.html#hidden-field

Yii 2 getOldAttribute() method not working in afterSave

I have a model and I want to take an old the value of that model's field after save, I was using getOldAttribute() method and it always returns the latest value?
Code sample
class User extends ActiveRecord
{
public function fields()
{
return [
'id',
'name',
'email' //old value: example#example.com
];
}
}
class UserService extends BaseService
{
/**
* Current user have email is `test#test.com`
*/
public function update(User $user)
{
//before save
$oldEmailBeforSave = $user->getOldAttribute('email'); //work fine and value is `example#example.com`
//handle update user
$this->saveEntity($user)
//after save
$oldEmailAfterSave = $user->getOldAttribute('email'); //not work and value is `test#test.com`
}
}
According to the docs you can use the second parameter of the afterSave( $insert, $changedAttributes) which holds the old values of the attributes that were changed and were saved.
Your code should look like below if you want to get the email fields old value if it was changed
public function afterSave( $insert, $changedAttributes ){
parent::afterSave();
if(!$insert){
$oldEmail = $changedAttributes['email'];
//do something here with the old email value
}
}
When you save new attribute, it is no longer "old". getOldAttribute() represents state from database and allows you to compare whether and how attribute was changed between find() and save(). After calling save(), old attributes are refreshed, so getOldAttribute() will return the same as getAttribute().
If you want to access old state of object, you need to clone it and use these methods on clone - $cloned will represent state of object before calling save():
public function update(User $user)
{
//before save
$oldEmailBeforSave = $user->getOldAttribute('email'); // `example#example.com`
$cloned = clone $user;
//handle update user
$this->saveEntity($user)
//after save
$oldEmailAfterSave = $user->getOldAttribute('email'); // `test#test.com`
$oldEmailAfterSave = $cloned->getOldAttribute('email'); // `example#example.com`
}

How to use a protected property in an Eloquent model without Laravel trying to save it to the database

In one of my models, I have an attribute named "slug". When the slug is changed, I need to record the original slug before updating it to the new one, so my model has a protected property "originalSlug". Before saving the model, I do something like this in my model:
protected $originalSlug;
public function customSave($newSlug){
$this->originalSlug = $this->slug;
$this->slug = $newSlug;
return $this->save();
}
Then I have an event that does other tasks using that originalSlug after a successful save.
The problem is Laravel is trying to save the originalSlug to the database though it isn't actually an attribute and doesn't have a database column. So it fails with the "Column not found" error.
What could I do to get Laravel to ignore that originalSlug property, or is there a better way I should be doing this?
If you want Eloquent to ignore a property, it needs to be accessible to set, otherwise __set will be called and Eloquent will treat it as an attribute.
You can alternatively use mutator for this.
So here's what you need:
public $originalSlug;
public function customSave($newSlug){
$this->originalSlug = $this->slug;
$this->slug = $newSlug;
return $this->save();
}
or:
protected $originalSlug;
public function customSave($newSlug){
$this->originalSlug = $this->slug;
$this->slug = $newSlug;
return $this->save();
}
public function setOriginalSlugAttribute($value)
{
$this->originalSlug = $value;
}
Then Eloquent will not set an originalSlug attribute , so it won't be saved to the db.
You can do that with events, like suggested in the comments, and I would suggest this way too.

Resources