First, I'll describe the context.
I have a lot of notifications that refering to changes in the Offer, for example: offerEditedBySeller, offerEditedByAdmin, offerTurnOff, etc. They are triggered in many places in controllers.
Now I'm implementing an expanded cache and I want to make the event OfferChange in which the cache for specific offer will be reloaded. First, I wanted to trigger these notifications in events, but I realized that to make it work, I'll have to duplicate every notification.
For example, let's assume I have the event( new OfferEdited() ). Its listeners will be RefreshCacheForOffer, SendNotificationsAboutOfferEdit etc. For every notification like offerEdited I need to create listener SendNotificationsAboutOfferEdit, which just trigger specific Notification. I think it's not what I want to reach.
Is there a way to bind events/listeners with a notification? No matter if it would be offerEdited, offerApproved or offerDisapproved it would be bind with the event OfferChange, which would trigger the listener RefreshCacheForOffer and the specific job next. This way I wouldn't have to change the code in every controller's action and create a lot of unnecessary events and listeners. Is it possible?
Edit:
I know I can just do:
$user->notify(new offerEdited($offer));
event( new OfferChange($offer) );
But I hope there is a way to better organize it.
To avoid changing lots of code in your controllers you could model it inside your Model and pick up on the 'created' or 'updated' events of the model, and then call subsequent events.
class Offer extends Model
{
protected static function booted()
{
static::updated(function ($user) {
// check for condition and trigger event such as OfferEditedBySeller
});
}
}
If you want to use event/listener architecture then I think one event and listener for ever notification is the way you have to go.
Alternatively, don't bother with events/listeners - just send your notifications from the controller or from the 'created' or 'updated' events of the model. Cut out the middleman (events/listeners) and you'll have more explicit code which is easier to follow.
Events/listeners are good when you need to decouple and abstract - but if you are doing explicit things then not using them might be simpler for you.
Related
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
Whenever I modify a relationship (many to many) I want to do another action. As far as I know this can't be done using event listeners (see https://github.com/laravel/framework/issues/2303). This works,
function setUserGroups($ids){
$this->groups->sync($ids);
doSomethingElse();
}
The downside is that it's not intuitive for other developers to remember to use this function. Generally I'm able to attach behavior to change in other attributes using mutators, defining them as guarded or adding events, and I just want to be able to do something similar with syncing/attaching.
I don't see creating a repository as a solution for this. We're not using the repository pattern in our application and honestly I see this issue coming up there as well.
You may register your custom event handlers and listeners:
// register listener
Event::listen('user.groupsModified', 'User#onGroupsModified');
Event Handler:
// Handle event in User class
function onGroupsModified($event)
{
}
Then fire the event in setUserGroups function:
function setUserGroups($ids){
$this->groups->sync($ids);
// Fire the event
Event->fire('user.groupsModified');
}
This way, you can abstract the dependency from the setUserGroups method, now you only need to fire the event and no need to know the handler's name.
I have some problems with events in GWTP.
I have a MainPresenter which extends TabContainerPresenter.
This presenter is linked to a MainView which contains some ui components + some com.gwtplatform.mvp.client.Tab : HomeTab, ContactTab and so on.
MainPresenter is supposed to react to some events "MyEvent"
MyEvent has a corresponding MyHandler and has been created following those good practices http://arcbees.wordpress.com/2010/08/24/gwt-platform-event-best-practice/
When I fire an event from a ui component of MainView like this :
MyEvent.fire(this, new MyEventContext(..));
I correctly catch the event in MainPresenter.
But When I do exactly the same in one of the "Tab Presenter", the event is not caught by the MainPresenter.
For example, in HomePresenter which is the "HomeTab" of MainPresenter, when I do
MyEvent.fire(this, new MyEventContext(..));
I can catch the event from the HomePresenter but not from the MainPresenter.
Any idea?
Make sure you respect those rules:
The EventBus you inject in your View is com.google.web.bindery.event.shared.EventBus (and not com.google.gwt.event.shared.EventBus)
In the Presenter that handles the event (HomePresenter or MainPresenter), register to the event using the addRegisteredHandler method, inside the onBind lifecyle method:
#Override
protected void onBind() {
super.onBind();
addRegisteredHandler(MyEvent.getType(), this);
}
I don't know what is your particular mistake that you've done. To help you, I made a quick proof of concept which shows that events can be sent from a tabbed presenter to a TabContainerPresenter. Clone this project, and head to the #!settingsPage. You'll see two "Fire true" and "Fire false" buttons, which will fire events that will be caught by the ApplicationPresenter.
First, let me thank you for this awesome POC, it is an excellent basis to understand what was going wrong. I saw that I actually did not use GWTP in the good way.
The root problem was that I had 2 differents eventBus
I saw it by trying
Log.info("eventBus: "+eventBus)
in the MainPresenter and in the HomePresenter. The logs showed that they did not have the same hashcode.
This was due to the fact that sometimes, I accessed directly the EventBus by doing:
EventBus.Util.getInstance();
Whereas the placeManager instantiated with:
DelayedBindRegistry.bind(GWT.create(MyInjector.class))
Looking for a decent observer of the Mage_Sendfriend module, when the product is sent to a friend. I just traced it and don't see anything immediately useful. There is one dispatch written in the module, which actually fires when the send to friend form is loaded, not when it's actually submitted.
Looking at the events triggered, here are some events you might use:
controller_action_postdispatch_sendfriend_product_sendmail
controller_action_postdispatch_sendfriend
controller_action_postdispatch
And in the function you want to use for this event:
$controller = $observer->getControllerAction();
... your code here
You might have to resort to observing the model_save_after and model_save_before events, then checking $observer->getEvent()->getObject() to see if it's the Mage_Sendfriend model you're looking for.
An ugly solution, but sometimes the events just don't line up to solve a problem nicely.
Good luck! Let us know if you find anything good!
Edit: I would also suggest against observing a controller for this, though, since that isn't very modular. If a third-party module provides another interface for the Sendfriend feature, it wouldn't work with your module if you observe controller actions.
You can dispatch custom events when and where you want.
Mage::dispatchEvent('any_name_for_your_custom_event',
array('key'=>$value,'key'=>$value,'key'=>$value)); //can pass how many values you want in this array.
And in your config you just make your nodes to look for this event, and call one method from observer.
Its simple.
In my app, I want to add a user notification each time a user receives a comment on an image or other page. Therefore in my add action in my images controller, I'd like to also call the addNotifications action which is in my Notifications controller. I'm trying to stay away from requestAction based on the warnings, but is there another way?
Workflow is this:
New event occurs -> trigger addition of notification in notifications table -> email user that notification exists.
If it's going to be a notification for all sorts of things, then I would consider something in the app_controller as this will make it available across your whole application. Meaning you'll be able to call something like
$this->Notify($user['User']['email'], 'MyNotifyType', 'MyTemplateName');
Then you can deal with the other bits in your app controllers notify function. You might need to add your User model to your app_controller, which could be tricky.
I would try using uses() as this could allow you to add the model and thus pull user data from your app_controller if you wanted to say include the users last login details, username or formal greeting etc. http://api.cakephp.org/class/controller
If you want to call a method that is based on another model, you need to place it in the model class, so in your example in the the Notification model. You can then call it from your Images controller with
$this->Image->Notification->add($params);
if the Models are associated. If they are not, you could either connect them on the fly or go with the previous proposal and add the function in the appController (which is not really perfect, because functions in the AppController should not depend on a certain model but be generic)