CakePHP 3中Model->link(...)事件的关联变更日志实现咨询
记录用户与应用权限关联的变更日志
刚好之前做过类似的需求,给你两个靠谱的实现思路,你可以根据自己的代码结构选一个:
思路1:给中间表(apps_users)添加行为监听
多对多的link()和unlink()本质上是在操作中间表的增删,所以直接监听中间表的生命周期事件是最直接的方式:
创建中间表的Table类
如果还没创建AppsUsersTable.php,先在src/Model/Table目录下新建它,关联好Users和Apps模型:// src/Model/Table/AppsUsersTable.php namespace App\Model\Table; use Cake\ORM\Table; use Cake\Event\Event; use Cake\Datasource\EntityInterface; use ArrayObject; class AppsUsersTable extends Table { public function initialize(array $config): void { parent::initialize($config); $this->setTable('apps_users'); $this->setPrimaryKey(['user_id', 'app_id']); // 复合主键 $this->belongsTo('Users', [ 'foreignKey' => 'user_id', 'joinType' => 'INNER' ]); $this->belongsTo('Apps', [ 'foreignKey' => 'app_id', 'joinType' => 'INNER' ]); // 添加上自定义日志行为 $this->addBehavior('LogAssociation'); } }编写自定义日志行为
新建LogAssociationBehavior.php,监听中间表的afterSave和afterDelete事件:// src/Model/Behavior/LogAssociationBehavior.php namespace App\Model\Behavior; use Cake\ORM\Behavior; use Cake\Event\Event; use Cake\Datasource\EntityInterface; use ArrayObject; class LogAssociationBehavior extends Behavior { public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) { // 新增关联(权限授予) if ($entity->isNew()) { $user = $this->getTable()->Users->get($entity->user_id); $app = $this->getTable()->Apps->get($entity->app_id); // 这里替换成你现有的日志记录逻辑,比如写入操作日志表 $this->logAction($user, $app, 'grant'); } // 如果中间表有权限等级字段,还可以处理更新场景 else if ($entity->isDirty('permission_level')) { $user = $this->getTable()->Users->get($entity->user_id); $app = $this->getTable()->Apps->get($entity->app_id); $oldLevel = $entity->getOriginal('permission_level'); $newLevel = $entity->permission_level; $this->logAction($user, $app, 'update_permission', compact('oldLevel', 'newLevel')); } } public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options) { // 删除关联(权限撤销) $user = $this->getTable()->Users->get($entity->user_id); $app = $this->getTable()->Apps->get($entity->app_id); $this->logAction($user, $app, 'revoke'); } // 封装日志写入逻辑,和你现有的日志体系对齐 private function logAction($user, $app, $action, $extra = []) { // 示例:假设你有个LogEntries表来存操作日志 $logEntry = $this->getTable()->LogEntries->newEmptyEntity(); $logEntry->user_id = $user->id; $logEntry->action_type = match($action) { 'grant' => 'APP_PERMISSION_GRANTED', 'revoke' => 'APP_PERMISSION_REVOKED', 'update_permission' => 'APP_PERMISSION_UPDATED', }; $logEntry->details = match($action) { 'grant' => sprintf('用户「%s」授予了应用「%s」的访问权限', $user->username, $app->name), 'revoke' => sprintf('用户「%s」撤销了应用「%s」的访问权限', $user->username, $app->name), 'update_permission' => sprintf('用户「%s」将应用「%s」的权限等级从「%s」更新为「%s」', $user->username, $app->name, $extra['oldLevel'], $extra['newLevel']), }; $this->getTable()->LogEntries->save($logEntry); } }
思路2:重写Users/Apps模型的link()和unlink()方法
如果你不想单独处理中间表,也可以直接在UsersTable(或AppsTable)里重写关联操作方法,在调用父类方法前后记录日志:
// src/Model/Table/UsersTable.php namespace App\Model\Table; use Cake\ORM\Table; use Cake\Datasource\EntityInterface; class UsersTable extends Table { // ... 其他初始化代码 ... public function link(EntityInterface $entity, $associated, array $options = []) { // 处理单个或多个关联的情况 $apps = is_array($associated) ? $associated : [$associated]; // 先执行父类的link操作 $result = parent::link($entity, $associated, $options); if ($result) { foreach ($apps as $app) { $this->logPermissionChange($entity, $app, 'grant'); } } return $result; } public function unlink(EntityInterface $entity, $associated, array $options = []) { $apps = is_array($associated) ? $associated : [$associated]; $result = parent::unlink($entity, $associated, $options); if ($result) { foreach ($apps as $app) { $this->logPermissionChange($entity, $app, 'revoke'); } } return $result; } // 复用你现有的日志记录逻辑 private function logPermissionChange($user, $app, $action) { // 这里直接调用你已有的日志方法,比如之前用于afterSave的日志逻辑 $this->LogEntries->save([ 'user_id' => $user->id, 'action' => $action === 'grant' ? 'APP_PERMISSION_GRANT' : 'APP_PERMISSION_REVOKE', 'details' => sprintf( $action === 'grant' ? '授予用户「%s」应用「%s」的权限' : '撤销用户「%s」应用「%s」的权限', $user->username, $app->name ) ]); } }
注意事项
- 事务一致性:如果你的关联操作是在事务中执行的,确保日志写入也加入同一个事务,避免操作回滚但日志已写入的情况。
- 批量操作处理:不管用哪种方式,都要考虑
link()/unlink()传入多个关联实体的场景(比如一次给用户关联多个应用)。 - 权限字段扩展:如果中间表有额外的权限配置字段(如读写权限等级),记得在
afterSave中监听字段变更,记录权限更新的日志。
内容的提问来源于stack exchange,提问作者Roberto
相关产品推荐
相关产品推荐

