From efab02328924349bc8f81f7bf312db2e00cea070 Mon Sep 17 00:00:00 2001 From: wpdev Date: Fri, 31 Jul 2026 06:00:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(back-compat):=20hook=20=E6=A9=8B=E6=94=B9?= =?UTF-8?q?=E9=9B=99=E5=90=91=20+=20alias=20=E8=A1=A8=E4=BF=AE=E8=A3=9C=20?= =?UTF-8?q?+=20=E5=87=BD=E5=BC=8F=20shim=EF=BC=88E1/E2/E5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E1 雙向 hook 橋 原本只做 tmdo_* → wpdo_* 單向轉發,但核心一律 do_action('wpdo_*') (core:69,275、custom-table-registry:110、options-manager:59), 所以任何 add_action('tmdo_after_write') 之類的新契約永遠收不到事件。 改為雙向,並以 $GLOBALS['tmdo_hook_relay_active'] 共用旗標防止 A→B→A 迴圈與 listener 重複觸發。 連帶:兩個 test bootstrap 補 do_action_ref_array() stub(單向時不會走到)。 E2 alias 表 - 刪死條目 'TMDO_Notifier'(B 沒有這個類別,此行永不生效) - 補 'TMDO_Abstract_Notifier' => 'WPDO_Abstract_Notifier'(自訂 notifier 基底) - trait-wpdo-anti-eav-aware-alias.php 移除與 interface-wpdo-entity-adapter-alias.php 重複的 WPDO_Entity_Adapter_Interface 宣告(composer 曾警告 Ambiguous class resolution) E5 函式 shim:wpdo_run() / wpdo_load_textdomain() 委派到 tmdo_ 版本 unit 451 / integration 398 GREEN Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY --- .../trait-wpdo-anti-eav-aware-alias.php | 11 ++-- includes/class-tmdo-back-compat.php | 51 +++++++++++++++---- tests/bootstrap.php | 5 ++ tests/integration/bootstrap.php | 5 ++ 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/includes/back-compat/trait-wpdo-anti-eav-aware-alias.php b/includes/back-compat/trait-wpdo-anti-eav-aware-alias.php index 2d54ec6..c42c5ae 100644 --- a/includes/back-compat/trait-wpdo-anti-eav-aware-alias.php +++ b/includes/back-compat/trait-wpdo-anti-eav-aware-alias.php @@ -30,11 +30,6 @@ if ( ! trait_exists( 'WPDO_Anti_EAV_Aware', false ) && trait_exists( 'TMDO_Anti_ } } -if ( ! interface_exists( 'WPDO_Entity_Adapter_Interface', false ) && interface_exists( 'TMDO_Entity_Adapter_Interface', false ) ) { - /** - * Backward-compat alias interface. PHP allows interface inheritance, so - * extending the new interface gives implementing classes the same - * obligations under the old name. - */ - interface WPDO_Entity_Adapter_Interface extends TMDO_Entity_Adapter_Interface {} -} +// NOTE: WPDO_Entity_Adapter_Interface is declared in +// back-compat/interface-wpdo-entity-adapter-alias.php — it used to be duplicated +// here, which made composer's classmap ambiguous. diff --git a/includes/class-tmdo-back-compat.php b/includes/class-tmdo-back-compat.php index 8f5be88..e32a644 100644 --- a/includes/class-tmdo-back-compat.php +++ b/includes/class-tmdo-back-compat.php @@ -128,7 +128,7 @@ $tmdo_class_aliases = array( 'TMDO_Zone_Archive' => 'WPDO_Zone_Archive', 'TMDO_Interceptor_Base' => 'WPDO_Interceptor_Base', 'TMDO_Query_Interceptor_Base' => 'WPDO_Query_Interceptor_Base', - 'TMDO_Notifier' => 'WPDO_Notifier', + 'TMDO_Abstract_Notifier' => 'WPDO_Abstract_Notifier', ); foreach ( $tmdo_class_aliases as $tmdo_class => $wpdo_alias ) { @@ -172,7 +172,21 @@ if ( ! defined( 'WPDO_IS_MYSQL' ) ) { } // ── Hook dual-fire bridge ───────────────────────────────────────────────── -// Phase 1 過渡期:tmdo_* 與 wpdo_* hook 雙向轉發,讓既有 listener 仍能接收事件。 +// 核心一律 do_action( 'wpdo_*' ),所以單向的 tmdo_* → wpdo_* 轉發等於讓 tmdo_* +// 契約永遠收不到事件。這裡做雙向轉發,並用共用旗標防止 A→B→A 無限迴圈與 +// listener 被觸發兩次。 +$tmdo_make_relay = static function ( string $source, string $target ): callable { + return static function ( ...$args ) use ( $source, $target ) { + $base = preg_replace( '/^(wpdo|tmdo)_/', '', $source ); + if ( ! empty( $GLOBALS['tmdo_hook_relay_active'][ $base ] ) ) { + return; + } + $GLOBALS['tmdo_hook_relay_active'][ $base ] = true; + do_action_ref_array( $target, $args ); + $GLOBALS['tmdo_hook_relay_active'][ $base ] = false; + }; +}; + foreach ( array( 'wpdo_register_fields', 'wpdo_register_entity_fields', @@ -185,12 +199,29 @@ foreach ( array( 'wpdo_auto_promoted', ) as $tmdo_back_compat_hook ) { $tmdo_new_hook = preg_replace( '/^wpdo_/', 'tmdo_', $tmdo_back_compat_hook ); - add_action( - $tmdo_new_hook, - static function ( ...$args ) use ( $tmdo_back_compat_hook ) { - do_action_ref_array( $tmdo_back_compat_hook, $args ); - }, - 1 - ); + add_action( $tmdo_new_hook, $tmdo_make_relay( $tmdo_new_hook, $tmdo_back_compat_hook ), 1 ); + add_action( $tmdo_back_compat_hook, $tmdo_make_relay( $tmdo_back_compat_hook, $tmdo_new_hook ), 1 ); +} +unset( $tmdo_back_compat_hook, $tmdo_new_hook, $tmdo_make_relay ); + +// ── 函式別名(sister plugin 可能 remove_action 'plugins_loaded', 'wpdo_run')── +if ( ! function_exists( 'wpdo_run' ) ) { + /** + * Back-compat shim for the old boot function name. + * + * @return void + */ + function wpdo_run(): void { + tmdo_run(); + } +} +if ( ! function_exists( 'wpdo_load_textdomain' ) ) { + /** + * Back-compat shim for the old textdomain loader name. + * + * @return void + */ + function wpdo_load_textdomain(): void { + tmdo_load_textdomain(); + } } -unset( $tmdo_back_compat_hook, $tmdo_new_hook ); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5b591bf..1c4a5a2 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -174,6 +174,11 @@ if ( ! function_exists( 'do_action' ) ) { } } } +if ( ! function_exists( 'do_action_ref_array' ) ) { + function do_action_ref_array( string $hook, array $args ): void { + do_action( $hook, ...$args ); + } +} if ( ! function_exists( 'wp_next_scheduled' ) ) { function wp_next_scheduled( string $hook ): int|false { return false; } } diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php index 78022a7..6ed4d1b 100644 --- a/tests/integration/bootstrap.php +++ b/tests/integration/bootstrap.php @@ -272,6 +272,11 @@ if ( ! function_exists( 'do_action' ) ) { } } } +if ( ! function_exists( 'do_action_ref_array' ) ) { + function do_action_ref_array( string $hook, array $args ): void { + do_action( $hook, ...$args ); + } +} if ( ! function_exists( 'is_admin' ) ) { function is_admin(): bool { return ! empty( $GLOBALS['_wp_is_admin'] ); } } if ( ! function_exists( 'is_singular' ) ) { function is_singular( $t = '' ): bool { return false; } }