Drupal 7 节点Presave前如何获取段落字段的未更新版本?
获取Drupal段落字段保存前的版本(节点hook_presave场景)
碰到这个问题太正常了——Drupal里段落作为独立实体,它的保存流程和节点是联动的,导致在节点的hook_presave()里,你拿到的$node->original里的段落已经是更新后的状态,根本没法直接对比。我给你几个靠谱的解决思路:
思路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(); } }
- 在节点的
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()里对比。
步骤:
- 实现
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); } } }
- 在节点
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
相关产品推荐
相关产品推荐

