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.
Related
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.
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
I have the following situation:
I one of the application modules I trigger an event, in the same module I create an event listener which listen to that trigger via the shared event manager system, by this step all works fine, I tried to create more listeners in different modules to that event and they also works fine, all listeners are called.
What I want is to have a system where I can be able to trigger the same event from multiple places. For example, I create a Send Mail module, in which I'll have a listener which will listen to the "sendMail" trigger and will do some actions, is it possible to trigger the same event from different modules?
I tried to trigger the same event from 2 places but the attach method of the shared event takes the first parameter as the id of the caller class, i.e. the id of the class that triggers the event, in such way I can trigger the event only from the specified class.
What I want is to trigger that event from as many places as I want.
Thank you all very much!
The shared event manager allows attaching to wildcards, so to attach to a foo event no matter it's source you would do this:
$sharedEventManager->attach('*', 'foo', ...);
For more details check out the EventManager doc page on wildcards: http://framework.zend.com/manual/current/en/tutorials/tutorial.eventmanager.html#wildcards
If you use the Zend\EventManager\EventManagerAwareTrait or inherit from ZfcBase\EventManager\EventProvider you can supply your own event identifiers to listen to.
If you take a look at the code contained in the EventManagerAwareTrait::setEventManager() method here
https://github.com/zendframework/zf2/blob/master/library/Zend/EventManager/EventManagerAwareTrait.php#L44-L54
and also in ZfcBase\EventManager\EventProvider::setEventManager() here
https://github.com/ZF-Commons/ZfcBase/blob/master/src/ZfcBase/EventManager/EventProvider.php#L26-L37
you'll notice that the method automatically looks for a property named $eventIdentifier, expecting it to be a string or an array of strings, which if present, gets merged with the default identifiers of FQCN and class name.
So basically, you can add your own identifiers by simply adding a property to your event manager aware classes (the ones triggering events)
<?php
namespace Somenamespace;
use Zend\EventManager\EventManagerAwareInterface;
class EventTriggeringClass implements EventManagerAwareInterface
{
use \Zend\EventManager\EventManagerAwareTrait;
protected $eventIdentifier = 'SendMailIdentifier';
}
and
namespace Someothernamespace;
use Zend\EventManager\EventManagerAwareInterface;
class SomeOtherEventTriggeringClass implements EventManagerAwareInterface
{
use \Zend\EventManager\EventManagerAwareTrait;
protected $eventIdentifier = 'SendMailIdentifier';
}
Your listeners would now just need to attach to the SendMailIdentifier rather than target the FQCN of any specific class.
Of course, you can also still attach to the FQCN where necessary, since it's one of the identifiers that was merged by the setEventManager method.
Why do you use the shared event manager?
If you create a service for your "operations" you can initialize a
new eventmanager where you can attach an event whereever you want to.
And ofc pull the trigger from everywhere.
I also would recommend you to attach an event aggregate for easier code review and readability
Should domain events be dispatched according to event classes, or classes and a topic?
For example, I have the following event:
class UserRegisteredEvent implements INonTransactionalEvent{
public Timestamp: TTimestamp;
}
And an event manager,
class EventManager {
/** Omitted **/
}
Udi Dahan proposes that events are first class objects, and I like that. There is a UserRegisteredEvent object, an OrderComplete object. My question is, does the type itself get bound to the event handlers? For example, should I just pass the event object to the publish method?
EventManager.Publish(new UserRegisteredEvent(1));
This means each handler is bound to a single class type, which seems limiting. While YAGNI may be true, is the following not more powerful:
EventManager.Publish(new UserRegisteredEvent(1), Events.UserRegistered)
Whereby the event topic is what the handlers bind to. This way, handlers can benefit from inheritance. If type safety or usage of a ubiquitous language was an issue, one could do:
EventManager.UserRegisteredEvent(1)
Which is simply a short method to the longer publish version. This annoys me a little, as it means the class must be altered for every new event, which seems unnecessary (and indeed, not necessary if using the above method).
While I've only seen events get published as classes with no topic, is this limiting, or has anyone run into issues with this?
Domain events don't really require a specific implementation. After all they are just semantic DTOs. I don't understand what you're trying to accomplish, but unless you're building a service bus you just send the event object to be handled by whatever handlers are configured.
The Event doesn't know about the handlers, the handlers don't know about each other, the bus knows how to send the event to the handlers. And you want a handler to explicitly handle only 1 event, SRP . You can combine implementations of related handlers into one class, but that's an implementation detail.
If you really feel like introducing topics, I'd do it through interface inheritance. For instance:
interface IUserEvent : INonTransactionalEvent { ... }
class UserRegisteredEvent implements IUserEvent {
public Timestamp: TTimestamp;
}
The impact in your code and language would be negligible, you can keep using a generic Publish method accepting INonTransactionalEvent and you can also easily refactor your topics.
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))