fix(2meet-data-optimizer): route post entity keys by post_type

- Entity groups can declare post_types; get_field() resolves a post's key against its own type's groups first, then unscoped ones (legacy last-wins lookup kept for callers without a post type)
- Hook Bus and the post query router pass the post type, so a listing's hp_rating / hp_status / hp_verified map to hp_listing_core instead of the vendor/request tables; core HP groups declare their post types (schema hashes unchanged)
- Behind tmdo_post_scoped_routing: on for sites without post flat rows, off where rows may sit in the wrong table until PR-034 reconciles them (dry-run: dev20–22 off, dev23/24/30/31/40 on)
- Verify: EntityRegistryScopeTest (8), PostScopedRoutingTest (4); core unit 630 / integration 443, HP 144/49, WC 10/22 OK
- PR: PR-033
This commit is contained in:
2026-09-25 23:55:49 +08:00
parent 1653011f4f
commit 3f4fe52e1b
7 changed files with 340 additions and 14 deletions
+50 -5
View File
@@ -31,6 +31,12 @@ final class TMDO_Entity_Registry {
/** @var array<int, array{type:string, group:string, fields:array}> 待建表的 Schema 清單 */
private static array $pending_schemas = array();
/** @var array<string, array<string, array>> 實體類型 → meta_key → 欄位定義(僅未限定 post_type 的群組)*/
private static array $unscoped_index = array();
/** @var array<string, array<string, array<string, array>>> 實體類型 → post_type → meta_key → 欄位定義 */
private static array $scoped_index = array();
/** @var array<string, array> "entity/group" → 同名群組以不同定義重複註冊的紀錄 */
private static array $collisions = array();
@@ -55,10 +61,15 @@ final class TMDO_Entity_Registry {
/** 合法的實體類型 */
public const VALID_ENTITY_TYPES = array( 'post', 'user', 'term', 'comment' );
/** Option:'1' 時 get_field() 依傳入的 post_type 解析(見 get_field())。 */
public const SCOPED_ROUTING_OPTION = 'tmdo_post_scoped_routing';
public static function init(): void {
self::$adapters = array();
self::$groups = array();
self::$field_index = array();
self::$unscoped_index = array();
self::$scoped_index = array();
self::$pending_schemas = array();
self::$collisions = array();
self::$restored = array();
@@ -100,10 +111,14 @@ final class TMDO_Entity_Registry {
* - unique (bool) 唯一索引
* - options (array) enum 選項
* - label (string) 顯示名稱
* @param array $args 選填:post_types (string[]) — 群組只套用於這些 post_type。
* 多個群組共用 meta_key 時,依 post 本身的類型決定歸屬;
* 不屬於任何宣告 post_type 的 post 不受管理(走原生 meta)。
* 不列入欄位定義,所以不影響 schema hash 與衝突比對。
*
* @return bool
*/
public static function register_group( string $entity_type, string $group_name, array $fields ): bool {
public static function register_group( string $entity_type, string $group_name, array $fields, array $args = array() ): bool {
if ( ! in_array( $entity_type, self::VALID_ENTITY_TYPES, true ) ) {
return false;
@@ -128,8 +143,9 @@ final class TMDO_Entity_Registry {
}
// 建立索引以供快速查詢
$post_types = array_values( array_filter( array_map( 'strval', (array) ( $args['post_types'] ?? array() ) ) ) );
foreach ( $normalized as $normalized_field ) {
self::$field_index[ $entity_type ][ $normalized_field['key'] ] = $normalized_field;
self::index_field( $entity_type, $normalized_field, $post_types );
}
self::$groups[ $entity_type ][ $group_name ] = $normalized;
@@ -229,7 +245,7 @@ final class TMDO_Entity_Registry {
}
foreach ( $kept as $field ) {
self::$field_index[ $entity_type ][ $field['key'] ] = $field;
self::index_field( $entity_type, $field, array() );
}
self::$groups[ $entity_type ][ $group_name ] = $kept;
@@ -244,6 +260,24 @@ final class TMDO_Entity_Registry {
return true;
}
/**
* 把欄位加入索引。field_index 保留舊語意(整個實體共用、後註冊者覆蓋),
* 供不帶 post_type 的查詢使用;另外依是否限定 post_type 分別建索引。
*
* @param string[] $post_types 群組限定的 post_type;空陣列表示不限定。
*/
private static function index_field( string $entity_type, array $field, array $post_types ): void {
self::$field_index[ $entity_type ][ $field['key'] ] = $field;
if ( empty( $post_types ) ) {
self::$unscoped_index[ $entity_type ][ $field['key'] ] = $field;
return;
}
foreach ( $post_types as $post_type ) {
self::$scoped_index[ $entity_type ][ $post_type ][ $field['key'] ] = $field;
}
}
/**
* 本請求從 wpdo_registry_meta 還原的孤兒群組。
*
@@ -305,10 +339,21 @@ final class TMDO_Entity_Registry {
/**
* 依 meta_key 查詢欄位定義
*
* 傳入 $post_type 且 SCOPED_ROUTING_OPTION 開啟時:先找限定該 post_type 的群組,
* 再找不限定的群組,都沒有則為 null。否則沿用舊語意(整個實體共用索引,
* 共用 key 由最後註冊的群組擁有)。舊資料可能已依舊語意散落在別的群組表,
* 所以既有站要等資料對齊後才開啟 option。
*
* @param string $post_type 選填:post 的 post_type。
* @return array|null 欄位定義,或 null(非 UAE 管理欄位)
*/
public static function get_field( string $entity_type, string $meta_key ): ?array {
return self::$field_index[ $entity_type ][ $meta_key ] ?? null;
public static function get_field( string $entity_type, string $meta_key, string $post_type = '' ): ?array {
if ( '' === $post_type || '1' !== (string) get_option( self::SCOPED_ROUTING_OPTION, '0' ) ) {
return self::$field_index[ $entity_type ][ $meta_key ] ?? null;
}
return self::$scoped_index[ $entity_type ][ $post_type ][ $meta_key ]
?? self::$unscoped_index[ $entity_type ][ $meta_key ]
?? null;
}
/**
+18 -5
View File
@@ -80,7 +80,7 @@ final class TMDO_Hook_Bus {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
$field_def = self::field_for( $type, (int) $object_id, $meta_key );
if ( ! $field_def ) {
return $check; // 非管理欄位,放行
}
@@ -133,7 +133,7 @@ final class TMDO_Hook_Bus {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
$field_def = self::field_for( $type, (int) $object_id, $meta_key );
if ( ! $field_def ) {
return $check;
}
@@ -374,7 +374,7 @@ final class TMDO_Hook_Bus {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
$field_def = self::field_for( $type, (int) $object_id, $meta_key );
if ( ! $field_def ) {
return $check;
}
@@ -471,7 +471,7 @@ final class TMDO_Hook_Bus {
return $check;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
$field_def = self::field_for( $type, (int) $object_id, $meta_key );
if ( ! $field_def ) {
return $check;
}
@@ -622,6 +622,19 @@ final class TMDO_Hook_Bus {
return null;
}
/**
* 欄位定義查詢;post meta 依該 post 本身的 post_type 解析(見 TMDO_Entity_Registry::get_field())。
*
* 先做不帶 post_type 的查詢:大多數 key 不受管理,可省下 get_post_type()。
*/
private static function field_for( string $type, int $object_id, string $meta_key ): ?array {
$field_def = TMDO_Entity_Registry::get_field( $type, $meta_key );
if ( null === $field_def || 'post' !== $type ) {
return $field_def;
}
return TMDO_Entity_Registry::get_field( $type, $meta_key, (string) get_post_type( $object_id ) );
}
/**
* 直接讀取(繞過 WP filter 系統)
* 供 wpdo_get_meta() 便利函式使用,性能更好
@@ -646,7 +659,7 @@ final class TMDO_Hook_Bus {
return $result;
}
$field_def = TMDO_Entity_Registry::get_field( $type, $key );
$field_def = self::field_for( $type, $entity_id, $key );
if ( ! $field_def ) {
return null;
}