How to check if model's attached items have changed? - laravel

Suppose we have Class and Student models with many-to-many relationship defined between them.
I want to perform some action, if $students, that are attached to a $class, change. For example:
$class->attach($newSetOfStudents); // notify a teacher about the change behind the scenes
$class->save(); // by overriden save() or attach() methods
How can this be accomplished? I tried to use ..->isDirty() when overriding the save() method, but it does not seem to work with attached models.
Update
Checking the pivots for ..->isDirty() doesn't seem to work either:
// ..
foreach($class->students as $student){
if($student->pivot->isDirty()) return true;
}
// ..

the best thing to do, is using beforeSave() method by extending Ardent, there you can handle the case you want, do the changes, let it go to the save method by its own.
take a look at this nice package

You can use Model Event - created on Pivot like this:
use Illuminate\Database\Eloquent\Relations\Pivot;
Pivot::created(function($pivot) {
// Do something here
});
This solutions fires for each pivot & only works in case of attach().
See more about this - here
Hope this helps!

Related

Call loadMissing() in a isolated model replica

I have a question maybe a little silly. I'm trying to replicate and store a model instance in a variable to call the loadMissing method. The idea is to call the loadMissing method without being affected by the original model. Let me explain.
I have the following method in my User model:
class User extends Authenticatable
{
...
public function myCustomMethod()
{
$cloned = $this->replicate()
->loadMissing(['roles', 'roles.permissions'])
->getAttribute('roles')
->flatMap(function ($role) {
return $role->permissions
});
return $cloned;
}
The problem is that in this way the original model instance is affected, and it loads in the original instance all the relations that I am loading only in this method.
My question is: is there a way to load and isolate relationships in a replica of the model instance without affecting the original model?
Thanks in advance.
EDIT 1
I have also tried cloning the instance and trying to use the loadMissing method on the cloned instance, but the original instance is also affected. For example:
class User extends Authenticatable
{
...
public function myCustomMethod()
{
$original = $this;
$cloned = clone $original;
$cloned->loadMissing('roles.permissions');
dump($original);
dump($cloned);
die();
}
But in this way, I get the following results. Notice that both the original and the cloned instance are loading the relationships when I'm just trying to load the relationships in the cloned instance:
$original
$cloned
I was wondering if this is normal behavior. If it is a normal behavior, which I suppose it is not, I was wondering if there is any way to use the loadMissing method on a copy, replica or clone of an instance without modifying the original instance. Thank you very much in advance.
EDIT 2
My controller looks like simple like this:
class HomeController extends Controller
{
public function index()
{
dd(User::find(1)->myCustomMethod());
}
}
SOLUTION
After several days looking for documentation, doing tests and pulling my hair a little, I have found the solution to my problem and share it in case someone comes here looking for similar information in the future.
Basically the answer that helped me find the right way was this:
https://stackoverflow.com/a/29787693/13066106
Which is documented in the official PHP documentation:
https://www.php.net/manual/en/language.oop5.cloning.php
I copy and paste verbatim the most relevant here:
When an object is cloned, PHP will perform a shallow copy of all of the object's properties. Any properties that are references to other variables will remain references.
When I got to this point, I understood why it wasn't working ... I was cloning a collection of type Illuminate\Database\Eloquent\Collection and this collection contained multiple objects of type Role, but according to the documentation, it is normal behavior that when I modify any property in the cloned instance, the changes will be reflected back to me in the original instance.
From here, I looked for more documentation about it and I found an article that definitely helped me solve my problem:
Don't clone your php objects, DeepCopy them
The article is quite descriptive and basically what it does is advise the use of the package DeepCopy
Fortunately, the package is already a Laravel dependency by default, so I didn't have to install it with composer. I just used the deep_copy method to clone the instance.
Thank you very much to everyone who helped me in some way. I hope that the links to the documentation that I have shared will be of use to those who come here looking for similar information.
Why don't you just get the permissions collection (outside of model):
$userId = $user->id;
$premissions = Permissions::whereHas('users', function($userQuery) use ($userID) {
$userQuery->where('id',$userId);
})
->get();
Or do the flatmap:
$result = Roles::with('permissions')
->whereHas('users', function($userQuery) use ($userID) {
$userQuery->where('id',$userId);
})
->get()
->flatMap(function ($role) {
return $role->permissions
});

MorphToMany relation empty in `created` Model Observer

I am using Laravel Nova and have an Entry model that uses spatie/nova-tags-field. (Under the hood this package uses a morphToMany relation between models and tags via a trait)
I attached a model observer on the created event.
I want to access the entry's tags in the observer like this:
public function created(Entry $entry)
{
$tags = $entry->tags;
}
But $tags is always an empty array [], yet I can access the models tags later (not in the observer, but anywhere else) using Entry::find($id)->tags. My guess is that the morphToMany pivot table entry for the attached tags is being created after the observer fires?
Thank for your input.
Because the model hasn't been created yet, try the created method in your observer instead of creating
You could override the attachTags method in your Entry and fire a custom event when $this->wasRecentlyCreated is true.
Also you could listen to the created event (or any other) of your own tag model.
In combination with $touches something close to your desired behaviour may be possible. But obviously the created event of your Entry will always be fired before any tags are attached.

Laravel observer pattern for keeping two related models in sync

I'm using Laravel 5.4. I have two related eloquent models:
ImageFile and ClipartImage.
ClipartImage contains a belongsTo relationship as follows:
class ClipartImage extends Model
{
public function image_file()
{
return $this->belongsTo(ImageFile::class, 'image_file_id');
}
public function promote()
{
$this->image_file->promote();
}
}
On ClipartImage there is a "promote" method which will pass the promote call through to the ImageFile object as it knows how to promote itself.
The problem I'm running into is if properties change on ImageFile as a result of the method call, there are a couple of properties on the related ClipartImage which may need to be updated as a result. In the ImageFile class I could search the database for any related ClipartImages and update the relevant fields, but this doesn't update the existing ClipartImage instance that I am working with. What's the normal way to solve this sort of dependency between models and keeping the object instances in memory in sync?
I guess I'm in need of some sort of Observer pattern but in Laravel these things seem to be handled at the static/class level not for individual object instances - ie I could create an observer class that looked for any changes to ImageFile models, but that isn't going to help me refresh or update my existing ClipartImage object.
Edit: I've used a single "promote" method to illustrate, but there's a bunch of interaction between ClipartImage and the ImageFile class and it's not obvious from the point of view of ClipartImage when it may need to refresh or update itself. Ideally ImageFile would inform it when key fields have changed so it can respond accordingly.

What's the most reliable way to catch saves and deletes in Laravel 5.2?

I need to run some code when one of my models is saved (created/updated) or deleted. What's the best way to do that?
There's three different ways that I'm aware of:
Override the save and delete methods on the model
Add creating/updating/deleting callbacks in the boot method
Bind an observer in the boot method
I haven't seen these compared and contrasted, so I don't know what the differences are. I'm worried that the events won't fire under certain conditions.
For example, in Django, deletes only fire if you delete the models one-by-one, but not in a mass delete.
To be clear, I'm looking for answers that compare and contrast these (or other) methods -- not simply suggest even more ways of doing the same thing.
It's just my opinion for several methods you mention previously.
Override the save and delete methods on the model ( If you override it then next update of Laravel change visibility of method your code does not work again. It would throw Exception or PHP error. You have to modify it to work again )
Add creating/updating/deleting callbacks in the boot method ( exist in Laravel 4 you should check it again in Laravel 5 maybe different implementation using Event and Listener )
Bind an observer in the boot method ( exist in Laravel 4 you should check it again in Laravel 5 maybe different implementation using Event and Listener )
I think you should using Event and Listener provided by Laravel. It maybe still work on next Laravel Update. I assume Event and Listener as minor change area in Laravel and changed maybe just different method implementation.
Laravel should have plan of development assign which part of Laravel will be developed as major change area ( big modification ) or minor change area ( little modification ). If you try to change or override major change area it would can't be used on next Laravel Update.
You can register Event and Listener for save and delete record. Laravel have fireModelEvent method on Model ( Illuminate\Database\Eloquent\Model ) which trigger specific Laravel Event. If you've registered Event, Dispatcher ( Illuminate\Events\Dispatcher ) will execute Listener of Event.
Documentation about Laravel Events:
https://laravel.com/docs/5.3/events
https://laravel.com/docs/5.2/events
I assume you have YourModel as Model then do the following action on the below.
Register Event and Listener. Open app\Providers\EventServiceProvider.php then Add Event and Listener to EventServiceProvider.listen properties for YourModel or follow Laravel Documentation to create event using other way.
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
...
'eloquent.saved: App\YourModel' => [
'App\YourModel#eventSaved',
],
];
}
Adding eventSaved method on App\YourModel as Listener for Event so you can do specific action after save or delete.
class YourModel extends Model
{
public function eventSaved(){
// You can add your code to catch save here
}
}
The three methods and 4th referred by #joko. There may be more as well but lets focus on the 4 methods.
Let me describe you them one by one:
1) Override the save and delete methods on the model
In this method you are using OOPD method overriding. You are overriding Laravel's interal save method and adding your additional code by defining your own save method on top of it. This should be avoided as Laravel keep evolving and it may happen that thing start to fail if major change is done like Suppose in future laravel replace save method with any other method to save the records. Then again you will have to create another method to override that new method. Also writing code here may grow your model class file. You model may keep handling things like he shouldn't handle(Example: Sending Email). This method should be avoided.
2) Add creating/updating/deleting callbacks in the boot method
Here you are defining code on the Boot method of the Model. This method should only be used if there is much little code/things that you need to handle on event. The drawback of this method is that it make code more complicated and cluttered as you may write all logic in one like like functional programming. Suppose if you have to do some stuff on before creating and after created. You boot method will grow.
3) Bind an observer in the boot method
This method is pretty good. You create one observer class which handles such stuff that what should happen on Laravel events. It makes code more cleaner and easy to maintain.
Example: Suppose you have to write code in creating, saving, saved, deleting in these methods. In this case, method 1) and method 2) won't be good practice because in
Method 1: We will have to create this 4 methods and override them as well and support them in future releases of Laravel. In this case, code in your Model will also grow because of overriding this methods
Method 2: In this case your boot method will grow as well so you Model file will become a junk of code.
In method 1 and 2 also remember that its not responsibility of your Model to do many of the stuff that you going to write. Like sending email when user is created. These codes you may end up writing in created method.
Suppose now you have scenario where you need to send email to user on created event as well as you need to make user's entry log user in customer CRM. then you will have to write code for both in same method. Probably, you may not following single responsibility principle there. What should we do in the case? See method 4.
4) Other method suggested by #joko
The scenario that i suggested in method 4's end. You may send email to user and log him in Customer CRM whenever it is created. Then your method will do 2 things(Sending email and logging in CRM). It may not following single responsibility principle. What if better we can decouple both of them. Then comes this method.
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
'eloquent.saved: App\User' => 'App\Listeners\SendWelcomeEmailToUser'
'eloquent.saved: App\User' => 'App\Listeners\LogUserInCRM'
];
}
Create two listener classes:
class SendWelcomeEmailToUser
{
public function handle(User $user){
// Write code to send email
}
}
class LogUserInCRM
{
public function handle(User $user){
// Write code to log
}
}
Through this you can separate out codes and make them more cleaner.
I generally prefer this method its mode clean. It also gives you much better idea that what actually happen when event happens. It becomes one single point for Event to Listener mapping.
You can create event handlers, for every create/update of model, for example to add to cache the model data which is just saved to database or going to save to database, easier to retrieve without select query call,
while delete call, use forget for given key on cache handler event to delete cache as well as to delete from database too.
I'm partial to doing things manually when you need to know exactly how they're done. I recently used this Laravel Boilerplate to start a project and I like the way they manually fire events in the repository when a model is updated:
https://github.com/rappasoft/laravel-5-boilerplate/blob/master/app/Repositories/Backend/Access/User/EloquentUserRepository.php
Since models should always be updated through the repository, you always get to manually decide how events are handled. You could fire your own event when multiple models are deleted, and act accordingly. All of your options will work though, you just need to find the option that suits your needs best.
You can create abstract Model class that extends Illuminate\Database\Eloquent\Model class and all your model will extend this class. With implementation like this you can have more control on the models. For example
<?php
namespace App\Base\Database;
use Illuminate\Database\Eloquent\Model as BaseModel;
abstract class Model extends BaseModel
{
public function save(array $options = [])
{
//your code here
return parent::save($options);
}
}
You can do this for all the methods of the Model class and also you can add additional methods that are relevant for all models in your application

Overriding backbone model prototype vs extending model

I'm new to Backbone.js and in my recent project I need a custom validation mechanism for models. I see two ways I could do that.
Extending the Backbone.Model.prototype
_.extend(Backbone.Model.prototype, {
...
});
Creating custom model that inherit from Backbone model
MyApp.Model = Backbone.Model.extend({ ... });
I quite unsure which one is a good approach in this case. I'm aware that overriding prototype is not good for native objects but will that applies to backbone model prototype as well? What kind of problems I'll face if I go with the first approach?
You are supposed to use the second approach, that's the whole point of Backbone.Model.extend({}).
It already does your first approach + other near tricks to actually setup a proper inheritance chain (_.extend is only doing a copy of the object properties, you can look up the difference in code for Backbone's extend() and Underscore's _.extend, they are very large and not very interesting. Just extending the .prototype isn't enough for 'real' inheritance).
When I first read your question, I misunderstood and thought you were asking whether to extend from your own Model Class or directly extend from Backbone Extend. It's not your question, so I apologize for the first answer, and just to keep a summary here: you can use both approach. Most large websites I saw or worked on first extend from Backbone.Model to create a generic MyApp.Model (which is why I got confused, that's usually the name they give to it :)), which is meant to REPLACE the Backbone.Model. Then for each model (for instance User, Product, Comment, whatever..), they'll extend from this MyApp.Model and not from Backbone.Model. This way, they can modify some global Backbone behavior (for all their Models) without changing Backbone's code.
_.extend(Backbone.Model.prototype, {...mystuff...}) would add your property/ies to every Backbone.Model, and objects based on it. You might have meant to do the opposite, _.extend({...mystuff...}, Backbone.Model) which won't change Backbone itself.
If you look at the annotated Backbone source you'll see lines like
_.extend(Collection.prototype, Events, { ... Collection functions ...} )
This adds the Events object contents to every Collection, along with some other collection functions. Similarly, every Model has Events:
_.extend(Model.prototype, Events, { ... Model functions ...})
This seems to be a common pattern of making "classes" in Javascript:
function MyClass(args) {
//Do stuff
}
MyClass.prototype = {....}
It's even used in the Firefox source code.

Resources