0

Has anyone ever needed to bind an ActiveRecord event handler in a way that it only triggers on certain scenarios?

In an ideal world the ActiveRecord on() method would also take a $scenarios parameter and trigger the handler only if the ActiveRecord is using that scenario at the time the event occurs. But since that is not the case I am looking for a clean, reliable way to implement this type of functionality.

Edit: This needs to work with any event, including built-in events triggered by the Yii framework (eg. ActiveRecord::EVENT_AFTER_INSERT).

mae
  • 14,947
  • 8
  • 32
  • 47
  • 1
    I think you can extends `\yii\base\Event` and add your own method `onScenario()`. It will be the same as `on()` but getting one additional argument with scenarios. You can use it like method `scenarios()` in ActiveRecord. Also you'll need to override `trigger()` method and add scenario check before parent call. – SiZE Apr 07 '16 at 12:32

1 Answers1

0

This improvement of my first comment.

class ScenarioEvent extends \yii\base\Event
{
    private static $_events = [];

    public static function onScenario($scenario, $class, $name, $handler, $data = null)
    {
        // You may use foreach if you wil use many scenarios for event
        if (!isset(self::$_scenarios)) {
            self::$_scenarios[$scenario] = [];
        }
        self::$_scenarios[$scenario][$name][] = $class;
        return static::on($class, $name, $handler, $data);
    }

    public static function trigger($class, $name, $event = null)
    {
        if (
            // If event is null assumed that it has no sender with scenario
            $event === null
            || empty(self::$_scenarios[$event->sender->scenario][$name])
        ) {
            parent::trigger($class, $name, $event);
        } else {
            $classes = array_merge(
                [$class],
                class_parents($class, true),
                class_implements($class, true)
            );

            foreach ($classes as $class) {
                if (in_array($class, self::$_scenarios[$event->sender->scenario][$name])) {
                    parent::trigger($class, $name, $event);
                    break;
                }
            }
        }
    }
}
mae
  • 14,947
  • 8
  • 32
  • 47
SiZE
  • 2,217
  • 1
  • 13
  • 24
  • Sadly, this might only work with manually-triggered events (I don't need that). It does not work with any auto-triggered built-in events (such as `\yii\db\AfterSaveEvent` in `\yii\db\ActiveRecord`), which is what I actually need. – mae Jul 02 '17 at 08:03