Class-Level Event Handlers

Hi

As mentioned in the documentation (Class-Level Event Handlers):

you may want to respond to an event triggered by every instance of a class instead of only by a specific instance. Instead of attaching an event handler to every instance, you may attach the handler on the class level by calling the static method yii\base\Event::on().

For example, an Active Record object will trigger an EVENT_AFTER_INSERT event whenever it inserts a new record into the database. In order to track insertions done by every Active Record object, you may use the following code:




use Yii;

use yii\base\Event;

use yii\db\ActiveRecord;


Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {

    Yii::trace(get_class($event->sender) . ' is inserted');

});



I checked the yii\db\ActiveRecord class and his parent yii\db\BaseActiveRecord class and I found the following code:




public function delete()

    {

        $result = false;

        if ($this->beforeDelete()) {

            // we do not check the return value of deleteAll() because it's possible

            // the record is already deleted in the database and thus the method will return 0

            $condition = $this->getOldPrimaryKey(true);

            $lock = $this->optimisticLock();

            if ($lock !== null) {

                $condition[$lock] = $this->$lock;

            }

            $result = static::deleteAll($condition);

            if ($lock !== null && !$result) {

                throw new StaleObjectException('The object being deleted is outdated.');

            }

            $this->_oldAttributes = null;

            $this->afterDelete();

        }


        return $result;

    }


public function beforeDelete()

    {

        $event = new ModelEvent;

        $this->trigger(self::EVENT_BEFORE_DELETE, $event);


        return $event->isValid;

    }


public function afterDelete()

    {

        $this->trigger(self::EVENT_AFTER_DELETE);

    }



In the above code I didn’t see any trace of Event::on() to attach event handlers to the events. beforeDelete() and afterDelete() methods will be run normally without need for event handler because they will be called whenever the delete() method is called. Where is the benefit of trigging the EVENT_BEFORE_DELETE and EVENT_AFTER_DELETE events without using Event::on() method?

Event::on() could be used in the application. It doesn’t have to be in the class itself.

Thank you but as mentioned in the documentation, for example to do something before row deleting, we just need to override the beforeDelete() method. Actually no need to use Event::on() in application because the beforeDelete() and afterDelete() methods explicitly called on delete() method:




abstract class BaseActiveRecord extends Model implements ActiveRecordInterface

{

   public function delete()

   {

      // call beforeDelete() method

      $this->beforeDelete();


      // doing something to delete row

      // ...


      // call afterDelete() method

      $this->afterDelete();

   }


   public function beforeDelete()

   {

      $this->trigger(self::EVENT_BEFORE_DELETE);

   }


   public function afterDelete()

   {

      $this->trigger(self::EVENT_AFTER_DELETE);

   }

}


class ActiveRecord extends BaseActiveRecord

{

}


class Student extends ActiveRecord

{

   //just overriding beforeDelete() method to custom code. No need event handler!!

   public function beforeDelete()

   {

      if (parent::beforeDelete()) {

        // ...custom code here...

        return true;

      } else {

        return false;

      }

   }

}



My question is: Where is the benefit of trigging the EVENT_BEFORE_DELETE and EVENT_AFTER_DELETE events? We can just overriding the beforeDelete() and afterDelete() methods to put custom code.

The benefit is because you may handle these events w/o touching class itself. From outside of it.

I think the best approach to define and handling event in class-level is:

(I use only the necessary code without namespace and use phrases and other stuff)




abstract class BaseActiveRecord extends Model implements ActiveRecordInterface

{

   const EVENT_BEFORE_DELETE = 'beforeDelete';

   const EVENT_AFTER_DELETE = 'afterDelete';


   public function init()

   {

      parent::init();

      $this->on(self::EVENT_BEFORE_DELETE, [$this, 'beforeDelete']);

      $this->on(self::EVENT_AFTER_DELETE, [$this, 'afterDelete']);

   }


   public function delete()

   {

      $this->trigger(self::EVENT_BEFORE_DELETE);


      // do something to delete row

      // ...


      $this->trigger(self::EVENT_AFTER_DELETE);

   }


   public function beforeDelete($event)

   {

      

   }


   public function afterDelete($event)

   {

      

   }

}




class ActiveRecord extends BaseActiveRecord

{


}




class Student extends ActiveRecord

{

   // override beforeDelete() event handler

   public function beforeDelete($event)

   {

      // put your custom code here

   }


   // override afterDelete() event handler

   public function afterDelete($event)

   {

      // put your custom code here

   }

}


class StudentController extends Controller

{

   public function actionDelete()

   {

      $student = Student::findOne(123);

      

      /*

       * by call this method the EVENT_BEFORE_DELETE and EVENT_AFTER_DELETE will be triggering and so

       * the beforeDelete() and afterDelete() will be run.

       */

      $student->delete();

   }

}



If you’re handling it within a class, there’s no sense in using events. Methods overrides are enough.

In this case, I think it is better to define event handler for EVENT_BEFORE_DELETE event in user models instead of override the yii\db\BaseActiveRecord::beforeDelete() method. It is more logical.

No, it’s not. Events are for external handling and behaviors.

Thanks a lot alex. Your explanation was very useful.