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

Drupal 7 节点Presave前如何获取段落字段的未更新版本?

获取Drupal段落字段保存前的版本(节点hook_presave场景)

碰到这个问题太正常了——Drupal里段落作为独立实体,它的保存流程和节点是联动的,导致在节点的hook_presave()里,你拿到的$node->original里的段落已经是更新后的状态,根本没法直接对比。我给你几个靠谱的解决思路:


思路1:监听段落实体的保存事件,提前记录旧数据

因为段落是独立的实体,我们可以在段落被保存前,先捕获它的原始状态,存到临时存储里,等节点保存时再拿出来用。

步骤:

  1. 实现hook_entity_presave()针对段落实体:
function mymodule_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() === 'paragraph' && !$entity->isNew()) {
    // 加载段落的原始版本(数据库里的旧数据)
    $original_paragraph = \Drupal::entityTypeManager()
      ->getStorage('paragraph')
      ->loadUnchanged($entity->id());
    
    // 把旧段落数据存到$_SESSION里,用段落ID做键
    $_SESSION['mymodule_old_paragraphs'][$entity->id()] = $original_paragraph->toArray();
  }
}
  1. 在节点的hook_presave()里,取出这些旧段落数据对比:
function mymodule_node_presave(Drupal\node\NodeInterface $node) {
  // 假设你的段落字段机器名是field_my_paragraphs
  if ($node->hasField('field_my_paragraphs')) {
    $current_paragraphs = $node->get('field_my_paragraphs')->referencedEntities();
    
    foreach ($current_paragraphs as $paragraph) {
      // 从SESSION里拿旧数据
      if (isset($_SESSION['mymodule_old_paragraphs'][$paragraph->id()])) {
        $old_paragraph_data = $_SESSION['mymodule_old_paragraphs'][$paragraph->id()];
        $new_paragraph_data = $paragraph->toArray();
        
        // 这里对比新旧数据,找出变化的内容
        $changes = mymodule_compareParagraphData($old_paragraph_data, $new_paragraph_data);
        
        // 更新节点的日志字段(假设字段是field_change_log)
        if (!empty($changes)) {
          $log_entry = "段落ID {$paragraph->id()} 更新:" . implode('; ', $changes);
          $node->set('field_change_log', $node->get('field_change_log')->value . "\n" . $log_entry);
        }
        
        // 用完就删掉SESSION里的记录,避免污染后续请求
        unset($_SESSION['mymodule_old_paragraphs'][$paragraph->id()]);
      }
    }
  }
}

// 辅助函数:对比段落数据差异
function mymodule_compareParagraphData($old, $new) {
  $changes = [];
  // 这里根据你的段落字段结构做对比,比如标题字段field_title
  if ($old['field_title'][0]['value'] !== $new['field_title'][0]['value']) {
    $changes[] = "标题从「{$old['field_title'][0]['value']}」改为「{$new['field_title'][0]['value']}」";
  }
  // 其他字段同理...
  return $changes;
}

思路2:在表单阶段保存原始段落数据

另一种方式是在节点编辑表单加载时,就把当前的段落原始数据存起来,等提交后在hook_presave()里对比。

步骤:

  1. 实现hook_form_alter():
function mymodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  // 匹配节点编辑表单,比如article节点的编辑表单是node_article_edit_form
  if (strpos($form_id, 'node_') !== false && strpos($form_id, '_edit_form') !== false) {
    /** @var \Drupal\node\NodeInterface $node */
    $node = $form_state->getFormObject()->getEntity();
    
    if ($node->hasField('field_my_paragraphs') && !$node->isNew()) {
      // 加载原始段落数据,存到表单状态里
      $original_paragraphs = [];
      foreach ($node->get('field_my_paragraphs')->referencedEntities() as $paragraph) {
        $original_paragraphs[$paragraph->id()] = $paragraph->toArray();
      }
      $form_state->set('mymodule_original_paragraphs', $original_paragraphs);
    }
  }
}
  1. 在节点hook_presave()里取出表单状态的旧数据:
function mymodule_node_presave(Drupal\node\NodeInterface $node) {
  // 从表单状态获取旧段落数据(需要注意:只有通过表单保存的节点才会有这个数据)
  $form_state = \Drupal::service('form_state.manager')->getFormState();
  if (!$form_state) {
    return;
  }
  
  $original_paragraphs = $form_state->get('mymodule_original_paragraphs');
  if (empty($original_paragraphs)) {
    return;
  }
  
  $current_paragraphs = $node->get('field_my_paragraphs')->referencedEntities();
  foreach ($current_paragraphs as $paragraph) {
    if (isset($original_paragraphs[$paragraph->id()])) {
      $old_data = $original_paragraphs[$paragraph->id()];
      $new_data = $paragraph->toArray();
      $changes = mymodule_compareParagraphData($old_data, $new_data);
      
      // 更新日志字段逻辑同思路1
      if (!empty($changes)) {
        $log_entry = "段落ID {$paragraph->id()} 更新:" . implode('; ', $changes);
        $node->set('field_change_log', $node->get('field_change_log')->value . "\n" . $log_entry);
      }
    }
  }
}

注意事项

  • 思路1适用于所有节点保存场景(包括API保存、批量更新),思路2只适用于通过表单编辑的节点。
  • 如果你的段落开启了修订功能,也可以通过加载段落的旧修订版本来对比,但需要额外处理修订ID的关联,相对复杂一些。
  • 记得用完临时存储(SESSION或表单状态)后清理数据,避免内存泄漏或数据污染。

内容的提问来源于stack exchange,提问作者Just Jeff

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:26:37