Events - what are the benefits?

So I’m just looking in to events and trying to figure out the benefits of using events. Taking the example shown in the docs (https://www.yiiframework.com/doc/guide/2.0/en/concept-events):

public function send($message)
{
    // ...sending $message...

    $event = new MessageEvent;
    $event->message = $message;
    $this->trigger(self::EVENT_MESSAGE_SENT, $event);
}

Normally I would just do this:

public function send($message)
{
    // ...sending $message...

    $this->processMessageSent($message);
}

public function processMessageSent($message)
{
    // message sent logic here
}

So, can someone clarify this for me please?

Only one benefit: less explicit coupling i.e. you don’t need to modify a class in order to add more processing when it finishes its job.

1 Like

Hi,

In addition to what Alexander said, imagine you want to do 10 things when the email is sent, and not just the one thing from your example.

Without events, you would end up cluttering your code with a bunch of things that doesn’t really belong in your controller/form.

ActiveRecord events (before/after save) is a good example. They are triggered by the core, and you are able to react to then in your own app, where your logic belongs.

Hope this clears things out for you

2 Likes

To give a real-world example: I am using events for a Telegram Bot of mine which runs on on the command line. One feature of this bot is to notify all subscribed users when a goal was achieved in a (Bundesliga) soccer game.
This routine is executed once during the startup of the bot:

private function registerEventHandlers()
{
    Event::on(Score::class, Score::EVENT_AFTER_INSERT, function ($event) { $this->run('score-notification', [$event]); });
    Event::on(LiveScore::class, LiveScore::EVENT_AFTER_INSERT, function ($event) { $this->run('score-notification', [$event]); });
    Event::on(LiveScore::class, LiveScore::EVENT_AFTER_UPDATE, function ($event) { $this->run('score-notification', [$event]); });
    Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_FIND, function ($event) { $this->_lastDbQuery = time(); });
}

The advantage is, like already written by Alex and Mehdi, that the logic of what happens when a goal was achieved is totally decoupled from the model itself. Furthermore, one could easily change (i.e. correct) existing records from a web interface without triggering a score notification that is send to hundreds of users. The alternative would be to have additional logic in you model to determine whether a score notification should be send (which is not what you want to have in your model).

Another advantage, when you look at the last event subscriber, is that you can have a “catch-all” event for all sub-classes of a particular class (e.g. ActiveRecord). In my case, I use this subscriber to keep track of when the last db query was executed. This would be hard to track otherwise because db queries are only triggered when users are sending commands to the bot (e.g. to see their bets or the scoreboard). This timestamp is used to keep the database connection alive (which will terminate after it idles for too long of a time, depending on the server settings). The following routine is executed once on every tick of the bot:

private function keepAliveDbConnection()
{
    $lastQuery = time() - $this->_lastDbQuery;

    if ($lastQuery >= 7 * 3600) {
        Yii::debug('Send dummy query to keep MySQL connection alive.');
        // There is no try-catch block arround this statement. A failed
        // query will cause the daemon to abort. The system will restart the
        // daemon automatically.
        Yii::$app->db->createCommand('SELECT 1')->queryOne();
        $this->_lastDbQuery = time();
    }
}
3 Likes

Please let me join in this discussion rather than open new thread since my question is the same.

One of the good things in yii is Events. I developed some apps from yii 1.0 to 2.0. However, I did not have this good feature in my coding.

When I had some series of things to do, I just wrote down on the function. The best I did were wrapping in the db transaction if it involved db queries or factorised them into other functions. Any suggestion on the best practices for these kinds of scenarios?

Is it related to the discussion on fat controllers vs fat models?

In my understanding, putting series of actions inside controller is acceptable, does business logic should be put on the controller part?

  1. If you don’t have a use case for events, do not introduce them. Simply calling methods one by one is more straightforward.
  2. Try extracting logic from controller into separate classes not inherited from anything.
  1. Try extracting logic from controller into separate classes not inherited from anything.

Do you mean controller should be clean? Create another folder and put those classes that doing the logic there? Hence controllers and models are simple?

For example, I have email invoice feature, the process is

  1. get the transaction from db, it is as simple as $model = $this->findModel($id);
  2. create pdf
  3. store pdf in temporary folder
  4. create email and attach the pdf file
  5. send email and display success or error message.

the controller just contains instructions to call create and send email from other class, say InvoiceHelper.php?

Yes.

Yes. Something like:

public function actionInvoice()
{
    $id = Yii::$app->request->post('id') ?? null;
    if ($id === null) {
        throw new HttpException(400);
    }

    $model = $this->findModel($id);
    if ($model === null) {
        throw new HttpException(404);
    }

    $generator = new InvoiceGenerator($model);
    $invoiceMailer = new InvoiceMailer(Yii::$app->user);
    $invoiceMailer->send($generator->createInvoicePdf());
    $generator->cleanup();
}

This way you’ll be able to test invoice generation separately from sending emails.

@samdark
Thank you so much. I do really appreciate your guidance. You still remember me asking on different thread about testing and relate this to my other questions.

I remembered 8 years ago, when doing Java Spring framework application. My mentor setup a Utility folder besides mvc folders. He created one Utility class for each model when it has calculations need to be done so basically models are just models and controllers just routing/actions.

I wish I could find something to do to start practising all of these new best practices.

Well, one utility class per model does not sound particularly good. Structure your code as you find it good for understanding it later.

Sorry, I should not say model. But then I am confused myself. I dont think I can say it module as well. It was like we had advisors pages and investors pages. My mentor created One utillity class for advisor and one for investor. In AdvisorUtility, it contained a method to get investors of a given advisor, another method to get the characteristics category of a given advisor based on the survey he or she was entered, a method to get match investors based on their characteristics.

In InvestorUtility, we had a method to match best advisor for a given investor, etc…Basically, method we needed and used in the investor pages.

But the, I agree with you. The point taken is good for understanding it later