chore: initial snapshot of 2meet-data-optimizer v0.1.0

Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

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 05:06:36 +08:00
commit d36bb954d1
206 changed files with 66538 additions and 0 deletions
+658
View File
@@ -0,0 +1,658 @@
<?php
/**
* TMDO_Hook_Bus - 統一 Hook 攔截匯流排
*
* 所有 WordPress 的 meta 操作皆通過此匯流排:
* - {type}_metadata 系列 filteradd/get/update/delete
* - 實體刪除 action
* - 原生查詢擴充 hook
*
* @package WP_Data_Optimizer
*/
// phpcs:disable Squiz.Commenting,Generic.Commenting,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,Generic.CodeAnalysis.UnusedFunctionParameter,Generic.CodeAnalysis.EmptyStatement,Squiz.PHP.DisallowMultipleAssignments,Squiz.PHP.DisallowSizeFunctionsInLoops,WordPress.WP.I18n.MissingTranslatorsComment,WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents,Squiz.PHP.CommentedOutCode,Universal.NamingConventions.NoReservedKeywordParameterNames,WordPress.PHP.YodaConditions,Squiz.Commenting.InlineComment.InvalidEndChar -- PR-1 ported from UAE; cleanup PR scheduled.
defined( 'ABSPATH' ) || exit;
final class TMDO_Hook_Bus {
/** 標記:防止在內部 UPSERT 時遞迴觸發 filter */
private static array $internal_ops = array();
public static function init(): void {
// 取得所有已註冊的適配器
$adapters = TMDO_Entity_Registry::get_all_adapters();
foreach ( $adapters as $type => $adapter ) {
self::register_hooks_for_type( $type, $adapter );
}
}
private static function register_hooks_for_type( string $type, TMDO_Entity_Adapter_Interface $adapter ): void {
// ── 寫入攔截 ──────────────────────────────────────────
add_filter( "update_{$type}_metadata", array( self::class, 'intercept_update' ), 10, 5 );
add_filter( "add_{$type}_metadata", array( self::class, 'intercept_add' ), 10, 5 );
// ── 讀取攔截 ──────────────────────────────────────────
add_filter( "get_{$type}_metadata", array( self::class, 'intercept_get' ), 10, 5 );
// ── 刪除攔截 ──────────────────────────────────────────
add_filter( "delete_{$type}_metadata", array( self::class, 'intercept_delete' ), 10, 5 );
// ── 實體刪除時自動清理 ───────────────────────────────
add_action(
$adapter->get_delete_hook(),
function ( $entity_id ) use ( $type, $adapter ) {
self::cleanup_entity( $type, (int) $entity_id );
},
10,
1
);
}
// ─────────────────────────────────────────────────────────
// 寫入:update_{type}_metadata filter
// ─────────────────────────────────────────────────────────
/**
* 短路 WordPress 原生 update_metadata() 流程
*
* @param null|bool $check 若回傳 null 則 WP 繼續原生流程
* @param int $object_id
* @param string $meta_key
* @param mixed $meta_value
* @param mixed $prev_value
*/
public static function intercept_update( $check, $object_id, $meta_key, $meta_value, $prev_value ) {
// 已在短路中 → 避免遞迴
if ( ! empty( self::$internal_ops[ $object_id . ':' . $meta_key ] ) ) {
return $check;
}
$type = self::resolve_type_from_current_filter();
if ( ! $type ) {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
if ( ! $field_def ) {
return $check; // 非管理欄位,放行
}
// ── Mode-aware dispatch ──────────────────────────────
// disabled : 完全放行給 WP 原生 meta(回 null / $check)
// dual_write: 寫 flat,然後 return null 讓 WP 繼續寫 EAV
// shadow_read: 寫 flat,然後 return null 讓 WP 繼續寫 EAV
// aeav_only : 寫 flat,return true 短路 WP(不寫 EAV)
if ( ! TMDO_Mode_Manager::writes_to_flat( $type ) ) {
return $check; // disabled
}
// Route decision (v1.2.0):讓 UAEPG 等外掛正式訂閱 routing,不必搶 priority 5。
$route = self::decide_route( 'update', $type, (int) $object_id, $meta_key, $meta_value );
if ( $route === 'pg' ) {
// 讓其他 listener(例如 UAEPG)接手;原生 EAV 也放行。
return null;
}
if ( $route === 'skip' ) {
// 不寫 flat、不寫 EAV,但告訴 WP 已處理。
return true;
}
// 截取 before value(v1.3.1):audit_logger 等訂閱者需要變更前的值。
// v1.3.2:透過 filter `wpdo_capture_before_value` 可關閉以省一次 DB read。
$before_value = self::maybe_read_before_value( $type, (int) $object_id, $meta_key, $field_def, 'update' );
$flat_result = self::perform_upsert( $type, (int) $object_id, $meta_key, $meta_value, $field_def );
do_action( 'wpdo_after_write', $type, (int) $object_id, $meta_key, $meta_value, $flat_result, 'update', $before_value );
// 若 mode 也要寫 EAV → return null 讓 WP 繼續
if ( TMDO_Mode_Manager::writes_to_eav( $type ) ) {
return null; // dual_write / shadow_read
}
return $flat_result; // aeav_only
}
public static function intercept_add( $check, $object_id, $meta_key, $meta_value, $unique ) {
// 已在短路中 → 避免遞迴
if ( ! empty( self::$internal_ops[ $object_id . ':' . $meta_key ] ) ) {
return $check;
}
$type = self::resolve_type_from_current_filter();
if ( ! $type ) {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
if ( ! $field_def ) {
return $check;
}
if ( ! TMDO_Mode_Manager::writes_to_flat( $type ) ) {
return $check;
}
$route = self::decide_route( 'add', $type, (int) $object_id, $meta_key, $meta_value );
if ( $route === 'pg' ) {
return null;
}
if ( $route === 'skip' ) {
return true;
}
// UAE 的設計:每個 entity 只有 1 row,所以 add 與 update 等效(Upsert)。
// 截取 before value(v1.3.1):add 情境下多半為 null,但若 row 已存在而 user 呼叫 add 也能抓到舊值。
$before_value = self::maybe_read_before_value( $type, (int) $object_id, $meta_key, $field_def, 'add' );
$flat_result = self::perform_upsert( $type, (int) $object_id, $meta_key, $meta_value, $field_def );
do_action( 'wpdo_after_write', $type, (int) $object_id, $meta_key, $meta_value, $flat_result, 'add', $before_value );
if ( TMDO_Mode_Manager::writes_to_eav( $type ) ) {
return null;
}
return $flat_result;
}
/**
* 如 filter `wpdo_capture_before_value` 回 true 才讀 flat table 的 before value,
* 否則直接回 null — 讓沒在用 audit / 其他 listener 的站台省一次 DB read。
*
* filter 參數:(bool $default_true, string $type, string $meta_key, string $op)
* $op ∈ { 'add', 'update', 'delete' }
*
* 使用範例(關閉 audit 的站台):
* add_filter( 'wpdo_capture_before_value', '__return_false' );
*
* @since 1.3.2
*/
private static function maybe_read_before_value(
string $type,
int $entity_id,
string $meta_key,
array $field_def,
string $op
) {
$capture = apply_filters(
'wpdo_capture_before_value',
true,
$type,
$meta_key,
$op
);
if ( ! $capture ) {
return null;
}
return self::read_flat_value( $type, $entity_id, $meta_key, $field_def );
}
/**
* 讀取 flat table 中當前值(before value,供 audit / after_write listener 使用)。
*
* 此方法**不經 cache 加熱**,直接查 DB,以避免快取污染與遞迴。表不存在回 null。
*
* @since 1.3.1
*/
private static function read_flat_value(
string $type,
int $entity_id,
string $meta_key,
array $field_def
) {
global $wpdb;
$adapter = TMDO_Entity_Registry::get_adapter( $type );
if ( ! $adapter ) {
return null;
}
$group = $field_def['group'] ?? '';
if ( $group === '' ) {
return null;
}
$table = TMDO_Schema_Manager::get_table_name( $type, $group );
if ( ! TMDO_Schema_Manager::table_exists( $table ) ) {
return null;
}
$col = TMDO_Schema_Manager::sanitize_column_name( $meta_key );
$id_col = $adapter->get_entity_id_column();
$raw = $wpdb->get_var(
$wpdb->prepare(
"SELECT `{$col}` FROM `{$table}` WHERE `{$id_col}` = %d LIMIT 1",
$entity_id
)
);
return $raw === null ? null : TMDO_Type_Caster::from_db( $raw, $field_def );
}
/**
* 讓外部 listener(例如 UAEPG)透過 `wpdo_route_decision` filter 指定路由。
*
* 回傳值:
* 'flat' (預設) — UAE 寫入 MySQL flat table
* 'pg' — 放行,由其他 listener 接手;UAE 不寫 flat,原生 EAV 依 mode 決定
* 'skip' — 都不寫(用於軟刪除之類特殊情境),但告訴 WP 已處理
*
* 其他非預期值會被 fallback 到 'flat' 以維持安全預設。
*
* @since 1.2.0
*/
private static function decide_route(
string $op,
string $type,
int $object_id,
string $meta_key,
$meta_value
): string {
$route = apply_filters(
'wpdo_route_decision',
'flat',
$type,
$object_id,
$meta_key,
$meta_value,
$op
);
if ( in_array( $route, array( 'flat', 'pg', 'skip' ), true ) ) {
return $route;
}
TMDO_Logger::warning(
'wpdo_route_decision_invalid_return',
array(
'returned' => is_scalar( $route ) ? (string) $route : gettype( $route ),
'op' => $op,
'type' => $type,
'key' => $meta_key,
)
);
return 'flat';
}
/**
* 執行 Upsert 操作
*/
private static function perform_upsert(
string $type,
int $entity_id,
string $meta_key,
$meta_value,
array $field_def
) {
global $wpdb;
$adapter = TMDO_Entity_Registry::get_adapter( $type );
if ( ! $adapter ) {
return null;
}
$group = $field_def['group'];
$table = TMDO_Schema_Manager::get_table_name( $type, $group );
$id_col = $adapter->get_entity_id_column();
$col = TMDO_Schema_Manager::sanitize_column_name( $meta_key );
// 表不存在則讓 WP 走原生流程(降級處理)
if ( ! TMDO_Schema_Manager::table_exists( $table ) ) {
return null;
}
// 型別轉換
$db_value = TMDO_Type_Caster::to_db( $meta_value, $field_def );
$format = TMDO_Type_Caster::get_wpdb_format( $field_def['type'] );
// 鎖防遞迴
$lock_key = $entity_id . ':' . $meta_key;
self::$internal_ops[ $lock_key ] = true;
try {
// 檢查列是否存在
$exists = $wpdb->get_var(
$wpdb->prepare(
"SELECT id FROM `{$table}` WHERE `{$id_col}` = %d",
$entity_id
)
);
if ( $exists ) {
// UPDATE
$result = $wpdb->update(
$table,
array( $col => $db_value ),
array( $id_col => $entity_id ),
array( $format ),
array( '%d' )
);
} else {
// INSERT
$result = $wpdb->insert(
$table,
array(
$id_col => $entity_id,
$col => $db_value,
),
array( '%d', $format )
);
}
// 清除快取
TMDO_Cache_Orchestrator::invalidate( $type, $entity_id, $group );
// 短路回傳 true(WP 認為寫入成功)
return $result !== false;
} finally {
unset( self::$internal_ops[ $lock_key ] );
}
}
// ─────────────────────────────────────────────────────────
// 讀取:get_{type}_metadata filter
// ─────────────────────────────────────────────────────────
/**
* 攔截 get_metadata() 呼叫
*
* @param null|mixed $check 若回傳 null 則 WP 繼續原生流程
* @param int $object_id
* @param string $meta_key 空字串表示取所有 meta
* @param bool $single
* @param string $meta_type 5.5+ 額外參數
*/
public static function intercept_get( $check, $object_id, $meta_key, $single, $meta_type = '' ) {
$type = $meta_type ?: self::resolve_type_from_current_filter();
if ( ! $type ) {
return $check;
}
// 空 keyWP 要求所有 meta,UAE 不攔截(維持相容性)
if ( $meta_key === '' ) {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
if ( ! $field_def ) {
return $check;
}
// ── Mode-aware dispatch ──────────────────────────────
// disabled : 完全不攔截,回傳 $check 讓 WP 走原生 EAV
// dual_write : 讀取仍走 EAV(flat 可能還沒有資料),回傳 $check
// shadow_read : 讀取走 UAE flat,同時與 EAV 比對記錄 diff
// aeav_only : 讀取走 UAE flat,不讀 EAV
if ( ! TMDO_Mode_Manager::reads_from_flat( $type ) ) {
return $check; // disabled / dual_write
}
$group = $field_def['group'];
$row = self::get_or_load_row( $type, (int) $object_id, $group );
$col = TMDO_Schema_Manager::sanitize_column_name( $meta_key );
$has_value = is_array( $row ) && array_key_exists( $col, $row );
$value = $has_value ? TMDO_Type_Caster::from_db( $row[ $col ], $field_def ) : null;
// Shadow-read:與 EAV 比對,記錄差異
if ( TMDO_Mode_Manager::does_shadow_compare( $type ) ) {
try {
TMDO_Shadow_Diff_Logger::compare_and_log(
$type,
(int) $object_id,
$meta_key,
$value,
$field_def
);
} catch ( \Throwable $e ) {
// 比對失敗不該影響讀取
TMDO_Logger::error(
'shadow_compare_exception',
array(
'error' => $e->getMessage(),
)
);
}
}
// 值為空 → 回 WP 原生慣例
if ( empty( $row ) || $value === null || $value === '' ) {
return $single ? '' : array();
}
// WP 的慣例:get_metadata() 即使 $single=true 也回傳陣列包裝
return $single ? array( $value ) : array( $value );
}
/**
* 取得(或載入)完整列資料,並快取
*/
private static function get_or_load_row( string $type, int $entity_id, string $group ): array {
// L1 快取
$cached = TMDO_Cache_Orchestrator::get_row( $type, $entity_id, $group );
if ( is_array( $cached ) ) {
return $cached;
}
global $wpdb;
$adapter = TMDO_Entity_Registry::get_adapter( $type );
if ( ! $adapter ) {
return array();
}
$table = TMDO_Schema_Manager::get_table_name( $type, $group );
$id_col = $adapter->get_entity_id_column();
if ( ! TMDO_Schema_Manager::table_exists( $table ) ) {
return array();
}
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM `{$table}` WHERE `{$id_col}` = %d LIMIT 1",
$entity_id
),
ARRAY_A
);
$row = $row ?: array();
TMDO_Cache_Orchestrator::set_row( $type, $entity_id, $group, $row );
return $row;
}
// ─────────────────────────────────────────────────────────
// 刪除:delete_{type}_metadata filter
// ─────────────────────────────────────────────────────────
public static function intercept_delete( $check, $object_id, $meta_key, $meta_value, $delete_all ) {
$type = self::resolve_type_from_current_filter();
if ( ! $type ) {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
if ( ! $field_def ) {
return $check;
}
// Mode-aware:disabled 完全不攔截
if ( ! TMDO_Mode_Manager::writes_to_flat( $type ) ) {
return $check;
}
$route = self::decide_route( 'delete', $type, (int) $object_id, $meta_key, $meta_value );
if ( $route === 'pg' ) {
return null;
}
if ( $route === 'skip' ) {
return true;
}
// 截取 before value(v1.3.1)
$before_value = self::maybe_read_before_value( $type, (int) $object_id, $meta_key, $field_def, 'delete' );
global $wpdb;
$adapter = TMDO_Entity_Registry::get_adapter( $type );
$group = $field_def['group'];
$table = TMDO_Schema_Manager::get_table_name( $type, $group );
$id_col = $adapter->get_entity_id_column();
$col = TMDO_Schema_Manager::sanitize_column_name( $meta_key );
if ( ! TMDO_Schema_Manager::table_exists( $table ) ) {
return $check;
}
// UAE 的邏輯:刪除 meta = 設該欄位為 NULL
// 因為一個 entity 只對應一列,完整刪除列會丟失其他欄位
$default = $field_def['default'] ?? null;
if ( $delete_all ) {
// Safety cap: refuse mass-null if affected row count exceeds threshold.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$row_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
if ( $row_count > 500 ) {
TMDO_Logger::warning(
'intercept_delete_mass_blocked',
array(
'table' => $table,
'col' => $col,
'rows' => $row_count,
)
);
return false;
}
TMDO_Logger::info(
'intercept_delete_all',
array(
'table' => $table,
'col' => $col,
'rows' => $row_count,
)
);
// 刪除所有 entity 的該欄位
$result = $wpdb->query(
$wpdb->prepare(
"UPDATE `{$table}` SET `{$col}` = %s",
$default
)
);
} else {
$result = $wpdb->update(
$table,
array( $col => $default ),
array( $id_col => $object_id ),
array( TMDO_Type_Caster::get_wpdb_format( $field_def['type'] ) ),
array( '%d' )
);
TMDO_Cache_Orchestrator::invalidate( $type, (int) $object_id, $group );
}
do_action( 'wpdo_after_delete', $type, (int) $object_id, $meta_key, $meta_value, $result, (bool) $delete_all, $before_value );
// 若還要寫 EAV → return null 讓 WP 繼續刪除原生 meta
if ( TMDO_Mode_Manager::writes_to_eav( $type ) ) {
return null;
}
return $result !== false;
}
// ─────────────────────────────────────────────────────────
// 實體刪除清理
// ─────────────────────────────────────────────────────────
public static function cleanup_entity( string $type, int $entity_id ): void {
global $wpdb;
$adapter = TMDO_Entity_Registry::get_adapter( $type );
if ( ! $adapter ) {
return;
}
$id_col = $adapter->get_entity_id_column();
$groups = TMDO_Entity_Registry::get_groups_for_type( $type );
foreach ( $groups as $group ) {
$table = TMDO_Schema_Manager::get_table_name( $type, $group );
if ( TMDO_Schema_Manager::table_exists( $table ) ) {
$wpdb->delete( $table, array( $id_col => $entity_id ), array( '%d' ) );
}
}
TMDO_Cache_Orchestrator::flush_entity( $type, $entity_id );
}
// ─────────────────────────────────────────────────────────
// 工具方法
// ─────────────────────────────────────────────────────────
/**
* 從當前 filter 名稱推斷實體類型
* 例:update_user_metadata → user
*/
private static function resolve_type_from_current_filter(): ?string {
$current = current_filter();
if ( preg_match( '/^(?:add|get|update|delete)_(post|user|term|comment)_metadata$/', $current, $matches ) ) {
return $matches[1];
}
return null;
}
/**
* 直接讀取(繞過 WP filter 系統)
* 供 wpdo_get_meta() 便利函式使用,性能更好
*
* @param string $type
* @param int $entity_id
* @param string $key 若為空字串則回傳整列
* @return mixed
*/
public static function direct_read( string $type, int $entity_id, string $key = '' ) {
if ( $key === '' ) {
// 回傳所有群組的所有欄位
$result = array();
foreach ( TMDO_Entity_Registry::get_groups_for_type( $type ) as $group ) {
$row = self::get_or_load_row( $type, $entity_id, $group );
foreach ( TMDO_Entity_Registry::get_group_fields( $type, $group ) as $field ) {
$col = TMDO_Schema_Manager::sanitize_column_name( $field['key'] );
$result[ $field['key'] ] = TMDO_Type_Caster::from_db( $row[ $col ] ?? null, $field );
}
}
return $result;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $key );
if ( ! $field_def ) {
return null;
}
$row = self::get_or_load_row( $type, $entity_id, $field_def['group'] );
$col = TMDO_Schema_Manager::sanitize_column_name( $key );
return TMDO_Type_Caster::from_db( $row[ $col ] ?? null, $field_def );
}
}