feat(interceptors): 核心加入 Standard_Post_Interceptor 抽象基底(F1)

Template Method 基底(A v3.0.1),收斂 action_delete_post /
filter_update_meta / action_insert_post 三件套。子類只需宣告
public const FIELD_MAP + get_post_type() + get_table_key() +
build_insert_data()。

HP AddOn 的 4 個 interceptor 隨即改為繼承它(573 → 344 行);未來
LatePoint / bookings / quotation 等 post↔自訂表 1:1 鏡射也可直接用。
back-compat 補 WPDO_Standard_Post_Interceptor alias。

unit 451 / integration 398 GREEN

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 06:07:20 +08:00
parent 17c001e451
commit 56d534619e
5 changed files with 153 additions and 0 deletions
+1
View File
@@ -133,6 +133,7 @@ require_once TMDO_PATH . 'includes/class-tmdo-comment-stress-tester.php';
// ── Interceptor base + Sync Bridge(核心唯一保留 interceptor)─────────────
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php';
// ── Zone handlers ─────────────────────────────────────────────────────────
+1
View File
@@ -127,6 +127,7 @@ $tmdo_class_aliases = array(
'TMDO_Phase_Completed' => 'WPDO_Phase_Completed',
'TMDO_Zone_Archive' => 'WPDO_Zone_Archive',
'TMDO_Interceptor_Base' => 'WPDO_Interceptor_Base',
'TMDO_Standard_Post_Interceptor' => 'WPDO_Standard_Post_Interceptor',
'TMDO_Query_Interceptor_Base' => 'WPDO_Query_Interceptor_Base',
'TMDO_Abstract_Notifier' => 'WPDO_Abstract_Notifier',
);
@@ -0,0 +1,149 @@
<?php
/**
* Abstract base for standard post→custom-table interceptors.
*
* Consolidates the identical action_delete_post / filter_update_meta /
* action_insert_post pattern shared by Reviews, Messages, Memberships,
* and Requests interceptors. Subclasses supply the three varying pieces:
* - post_type string
* - table key (passed to TMDO_DB::table())
* - INSERT columns via build_insert_data()
*
* Subclasses MUST also define:
* public const FIELD_MAP = [ 'meta_key' => 'column', ... ];
*
* @package TMDO
* @since 3.0.1
*/
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from TMDO_DB::table(); column validated by subclass FIELD_MAP constant.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Abstract base for standard post→custom-table interceptors.
*
* @since 3.0.1
*/
abstract class TMDO_Standard_Post_Interceptor extends TMDO_Interceptor_Base {
// ── Subclass contracts ────────────────────────────────────────────────────
/** WordPress post_type slug this interceptor handles. */
abstract protected function get_post_type(): string;
/** Key passed to TMDO_DB::table() to resolve the custom table name. */
abstract protected function get_table_key(): string;
/**
* Build the INSERT row for a newly created post.
*
* @param int $post_id Post ID.
* @param \WP_Post $post Post object.
* @param string $now Formatted datetime string from TMDO_DB::now().
* @return array{values: array, formats: array}
* 'values' — associative array of column => value.
* 'formats' — ordered format string array (%d/%s) for $wpdb->insert.
*/
abstract protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array;
// ── Shared implementation ─────────────────────────────────────────────────
/**
* Register the three standard hooks. Subclasses that need additional
* hooks (e.g. get_post_metadata) should call parent::register_hooks()
* first and then add their own.
*/
public function register_hooks(): void {
add_filter( 'update_post_metadata', array( $this, 'filter_update_meta' ), 10, 5 );
add_action( 'wp_insert_post', array( $this, 'action_insert_post' ), 10, 3 );
add_action( 'before_delete_post', array( $this, 'action_delete_post' ), 10, 2 );
}
/**
* Deletes the custom-table record when the post is deleted.
*
* @param int $post_id Post ID.
* @param \WP_Post $post Post object.
* @return void
*/
final public function action_delete_post( int $post_id, \WP_Post $post ): void {
if ( $this->get_post_type() !== $post->post_type || ! $this->is_active() ) {
return;
}
try {
global $wpdb;
$wpdb->delete( TMDO_DB::table( $this->get_table_key() ), array( 'post_id' => $post_id ), array( '%d' ) );
} catch ( \Throwable $e ) {
TMDO_Logger::error( $this->module, 'before_delete_post', $e->getMessage() );
}
}
/**
* Syncs a meta update to the custom table column.
*
* @param mixed $check Short-circuit flag.
* @param int $post_id Post ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
* @param mixed $prev_value Previous value (unused).
* @return mixed Unmodified $check.
*/
final public function filter_update_meta( $check, int $post_id, string $meta_key, $meta_value, $prev_value ) {
$field_map = static::FIELD_MAP; // late static binding — picks up subclass const.
if ( $this->get_post_type() !== get_post_type( $post_id ) || ! isset( $field_map[ $meta_key ] ) ) {
return $check;
}
if ( ! $this->is_active() ) {
return $check;
}
try {
global $wpdb;
$col = $field_map[ $meta_key ];
$table = TMDO_DB::table( $this->get_table_key() );
$wpdb->query(
$wpdb->prepare(
"UPDATE `{$table}` SET `{$col}` = %s, updated_at = %s WHERE post_id = %d",
$meta_value,
TMDO_DB::now(),
$post_id
)
);
} catch ( \Throwable $e ) {
TMDO_Logger::error( $this->module, 'update_post_metadata', $e->getMessage() );
}
return $check;
}
/**
* Inserts the custom-table record when a new post is created.
*
* @param int $post_id Post ID.
* @param \WP_Post $post Post object.
* @param bool $update Whether this is an update (not a new post).
* @return void
*/
final public function action_insert_post( int $post_id, \WP_Post $post, bool $update ): void {
if ( $update || $this->get_post_type() !== $post->post_type || ! $this->is_active() ) {
return;
}
try {
$data = $this->build_insert_data( $post_id, $post, TMDO_DB::now() );
global $wpdb;
$wpdb->insert(
TMDO_DB::table( $this->get_table_key() ),
$data['values'],
$data['formats']
);
} catch ( \Throwable $e ) {
TMDO_Logger::error( $this->module, 'wp_insert_post', $e->getMessage() );
}
}
}
+1
View File
@@ -510,6 +510,7 @@ require_once TMDO_PATH . 'includes/class-tmdo-compatibility.php';
// Interceptors.
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php';
// Zones.
+1
View File
@@ -526,6 +526,7 @@ require_once TMDO_PATH . 'includes/class-tmdo-hook-bus-bridge.php';
require_once TMDO_PATH . 'includes/class-tmdo-conflict-monitor.php';
require_once TMDO_PATH . 'includes/class-tmdo-compatibility.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php';
require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php';
require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-router.php';
require_once TMDO_PATH . 'includes/class-tmdo-routing-predicate.php';