You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Yii框架中实现拉取式事件而非推送式事件?

Got it, let's figure out how to implement a pull-based event system in Yii instead of the default push-based setup you're currently using. The key issue with your current code is that the event trigger (MyUser) is directly binding handlers, which feels more like nested method calls than a properly decoupled event pattern. You want the trigger to just fire the event, and let target models like UserRole independently detect and react to it without pre-registering handlers in MyUser's init() method. Here are two practical approaches to make this happen:


方案1:利用Yii类级事件实现解耦(接近拉取式的关注点分离)

Yii supports class-level event binding, which lets handler models (like UserRole) subscribe to events on their own—no need for the trigger model (MyUser) to know anything about them. This keeps MyUser focused only on firing events, while other models decide if and how to respond.

Step 1: Clean up the MyUser model

Remove the handler binding from init()—we just need the event definition and trigger logic:

<?php
namespace app\models;
use Yii;

class MyUser extends \yii\db\ActiveRecord
{
    const EVENT_NEW_USER = 'new-user';

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'user';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name', 'email'], 'string', 'max' => 255]
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'name' => 'Name',
            'email' => 'Email',
        ];
    }

    // No more handler binding here—we're just the event source
}

Step 2: Let UserRole subscribe to the event on its own

In your UserRole model, add code to subscribe to MyUser's event during its own initialization:

<?php
namespace app\models;
use Yii;

class UserRole extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'user_role';
    }

    public function init()
    {
        parent::init();
        // UserRole decides to listen for new user events
        // Class-level binding means all MyUser instances will trigger this handler
        MyUser::on(MyUser::EVENT_NEW_USER, [$this, 'assignDefaultRole']);
    }

    public function assignDefaultRole($event)
    {
        // Get the user that triggered the event
        $newUser = $event->sender;
        // Create a default role entry for the new user
        $this->user_id = $newUser->id;
        $this->role = 'default'; // Adjust to your default role
        $this->save(false); // Skip validation if needed, or add proper rules
    }

    // Add your other UserRole methods here
}

Step 3: Keep your event trigger logic intact

Your existing action code stays the same—just fire the event, no need to worry about who's listening:

public function actionTestEvent() {
    $model = new MyUser();
    $model->name = "John";
    $model->email = "john@gmail.com";
    if($model->save()) {
        // Just trigger the event—handlers will take care of the rest
        $model->trigger(MyUser::EVENT_NEW_USER);
    }
}

Bonus: Add admin email notifications without touching MyUser

If you still need to send admin emails, create a separate class to handle that—no changes to MyUser required:

<?php
namespace app\models;
use Yii;

class AdminNotificationService
{
    public function __construct()
    {
        // This service subscribes to new user events on its own
        MyUser::on(MyUser::EVENT_NEW_USER, [$this, 'sendNewUserAlert']);
    }

    public function sendNewUserAlert($event)
    {
        $user = $event->sender;
        echo "Alert sent to admin: New user registered - {$user->name} ({$user->email})";
        // Replace with actual email sending logic using Yii's mailer
    }
}

方案2:纯拉取式事件(基于事件日志存储)

If you want a true pull-based system where events are just recorded, and handler models actively check for and process them (no pre-subscription needed), use an event log table to persist events.

Step 1: Create an event log model and table

First, set up a table to store events:

CREATE TABLE `event_log` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `event_name` varchar(255) NOT NULL,
    `event_data` text NOT NULL,
    `created_at` int(11) NOT NULL,
    `processed` tinyint(1) DEFAULT 0,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Then create the corresponding EventLog model:

<?php
namespace app\models;
use Yii;
use yii\db\ActiveRecord;

class EventLog extends ActiveRecord
{
    public static function tableName()
    {
        return 'event_log';
    }

    public function rules()
    {
        return [
            [['event_name', 'event_data', 'created_at'], 'required'],
            [['event_data'], 'string'],
            [['created_at', 'processed'], 'integer'],
            [['event_name'], 'string', 'max' => 255],
        ];
    }

    public function beforeSave($insert)
    {
        if (parent::beforeSave($insert)) {
            if ($insert) {
                $this->created_at = time();
                $this->processed = 0;
            }
            return true;
        }
        return false;
    }

    // Helper method to log events
    public static function logEvent($eventName, $data)
    {
        $logEntry = new self();
        $logEntry->event_name = $eventName;
        $logEntry->event_data = json_encode($data);
        $logEntry->save();
    }
}

Step 2: Log events in MyUser instead of triggering them

Modify MyUser to write to the event log when a new user is created:

// Add this method to MyUser
public function afterSave($insert, $changedAttributes)
{
    parent::afterSave($insert, $changedAttributes);
    if ($insert) {
        // Log the new user event instead of triggering push-based events
        EventLog::logEvent(self::EVENT_NEW_USER, [
            'user_id' => $this->id,
            'name' => $this->name,
            'email' => $this->email
        ]);
    }
}

Step 3: Let UserRole pull and process events

Add a method to UserRole that checks for unprocessed events and handles them. You can call this method via a cron job, console command, or even in your web app's bootstrap:

// Add this static method to UserRole
public static function processNewUserEvents()
{
    // Fetch all unprocessed new user events
    $events = EventLog::find()
        ->where(['event_name' => MyUser::EVENT_NEW_USER, 'processed' => 0])
        ->all();

    foreach ($events as $event) {
        $eventData = json_decode($event->event_data, true);
        // Avoid duplicate roles
        if (!self::find()->where(['user_id' => $eventData['user_id']])->exists()) {
            $role = new self();
            $role->user_id = $eventData['user_id'];
            $role->role = 'default';
            $role->save(false);
        }
        // Mark event as processed
        $event->processed = 1;
        $event->save(false);
    }
}

Which approach should you use?

  • 方案1 is great for most cases: it uses Yii's native event system, keeps code clean, and fully decouples event sources from handlers.
  • 方案2 is ideal for asynchronous or batch processing, or if you need to guarantee events are never lost (even if the system restarts).

内容的提问来源于stack exchange,提问作者Lovlesh Yadav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:32:52