Files
2meet-data-optimizer/includes/class-tmdo-schema-registry.php
T
wpdev 76c01e44df refactor: 全部 128 個生產檔加入 declare(strict_types=1)(PR-H)
對齊 A v3.2.0。型別強制會把隱式轉換變成 TypeError,所以一次全檔加入
並跑完整測試(unit 451 / integration 398 全綠,無迴歸)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 06:13:33 +08:00

326 lines
8.7 KiB
PHP

<?php
/**
* Central registry for zone field configurations.
*
* @package WP_Data_Optimizer
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Central registry for zone field configurations.
*
* Plugins register their meta_key → zone mappings here.
* The registry drives table creation, migration, interceptors, and query routing.
*
* Usage:
* $registry = TMDO_Schema_Registry::instance();
* $registry->register( 'hivepress', [
* 'post_type' => 'hp_listing',
* 'meta_key' => 'hp_price',
* 'zone' => 'hot',
* 'data_type' => 'decimal(10,2) NOT NULL DEFAULT 0',
* 'column' => 'hp_price',
* 'indexed' => true,
* ]);
*/
class TMDO_Schema_Registry {
/**
* Singleton instance.
*
* @var self|null
*/
private static ?self $instance = null;
/**
* Registered field mappings.
*
* Structure: [ 'post_type:meta_key' => field_config, ... ]
*
* @var array<string, array>
*/
private array $fields = array();
/**
* Zone → post_type → columns map for Zone A (hot) table creation.
*
* @var array<string, array<string, array>>
*/
private array $hot_columns = array();
/**
* Zone C (cold) fields grouped by post_type.
*
* @var array<string, string[]>
*/
private array $cold_fields = array();
/**
* Zone B (warm) fields with TTL config.
*
* @var array<string, array>
*/
private array $warm_fields = array();
/**
* Private constructor — use instance() to get the singleton.
*/
private function __construct() {}
/**
* Get the singleton instance.
*/
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Register a field mapping.
*
* @param string $provider Provider name (e.g. 'hivepress', 'woocommerce').
* @param array $config Field configuration:
* - post_type (string) WordPress post type.
* - meta_key (string) The wp_postmeta meta_key.
* - zone (string) hot|warm|cold|archive
* - data_type (string) SQL column type for Zone A (e.g. 'decimal(10,2) NOT NULL DEFAULT 0').
* - column (string) Column name in zone table (defaults to sanitized meta_key).
* - indexed (bool) Whether to add an index (Zone A only).
* - ttl (int) TTL in seconds (Zone B only, null = no expiry).
* - cache_group (string) Object cache group (Zone C only).
* - cache_ttl (int) Cache TTL in seconds (Zone C only).
* - show_in_rest (bool) Whether to expose via GET /wpdo/v1/listings. Default true.
*/
public function register( string $provider, array $config ): void {
$config = wp_parse_args(
$config,
array(
'post_type' => '',
'entity_type' => '',
'meta_key' => '',
'zone' => 'hot',
'data_type' => 'longtext',
'column' => '',
'indexed' => false,
'ttl' => null,
'cache_group' => '',
'cache_ttl' => HOUR_IN_SECONDS,
'provider' => $provider,
'show_in_rest' => true,
)
);
// v2.1.2 fix: normalize post_type vs entity_type. Some partner plugins
// (e.g. 2meet-liff) register fields against entities other than posts
// (user / term / comment) using `entity_type` instead of `post_type`.
// Without normalization, those fields end up with empty-string post_type,
// polluting `get_hot_post_types()` output. Use entity_type as bucket key.
if ( empty( $config['post_type'] ) && ! empty( $config['entity_type'] ) ) {
$config['post_type'] = sanitize_key( $config['entity_type'] );
}
if ( empty( $config['column'] ) ) {
$config['column'] = sanitize_key( $config['meta_key'] );
}
$key = $config['post_type'] . ':' . $config['meta_key'];
$config['provider'] = $provider;
$this->fields[ $key ] = $config;
// Index by zone for efficient lookup.
switch ( $config['zone'] ) {
case 'hot':
$this->hot_columns[ $config['post_type'] ][ $config['column'] ] = $config['data_type'];
break;
case 'warm':
$this->warm_fields[ $config['meta_key'] ] = $config;
break;
case 'cold':
$this->cold_fields[ $config['post_type'] ][] = $config['meta_key'];
break;
}
}
/**
* Bulk register multiple fields.
*
* @param string $provider Provider name.
* @param array $configs Array of field configs.
*/
public function register_many( string $provider, array $configs ): void {
foreach ( $configs as $config ) {
$this->register( $provider, $config );
}
}
/**
* Get the zone configuration for a specific field.
*
* @param string $post_type Post type.
* @param string $meta_key Meta key.
* @return array|null Field config or null if not registered.
*/
public function get_field( string $post_type, string $meta_key ): ?array {
$key = $post_type . ':' . $meta_key;
return $this->fields[ $key ] ?? null;
}
/**
* Get all fields for a specific zone.
*
* @param string $zone Zone identifier (hot/warm/cold/archive).
* @return array Array of field configs.
*/
public function get_zone_fields( string $zone ): array {
return array_filter( $this->fields, fn( $f ) => $f['zone'] === $zone );
}
/**
* Get all fields for a specific zone and post type.
*
* @param string $zone Zone identifier.
* @param string $post_type Post type.
* @return array
*/
public function get_zone_fields_for_type( string $zone, string $post_type ): array {
return array_filter(
$this->fields,
fn( $f ) => $f['zone'] === $zone && $f['post_type'] === $post_type
);
}
/**
* Get hot column definitions for a post type (for table creation).
*
* @param string $post_type Post type.
* @return array<string, string> Column name => SQL type.
*/
public function get_hot_columns( string $post_type ): array {
return $this->hot_columns[ $post_type ] ?? array();
}
/**
* Get all post types that have hot zone fields.
*
* @return string[]
*/
public function get_hot_post_types(): array {
return array_keys( $this->hot_columns );
}
/**
* Get all post types that have cold zone fields.
*
* @return string[]
*/
public function get_cold_post_types(): array {
return array_keys( $this->cold_fields );
}
/**
* Get cold field meta keys for a post type.
*
* @param string $post_type Post type.
* @return string[] Array of meta keys.
*/
public function get_cold_meta_keys( string $post_type ): array {
return array_unique( $this->cold_fields[ $post_type ] ?? array() );
}
/**
* Returns hot column names for a post type where show_in_rest is true.
* Used by REST endpoints to restrict publicly exposed fields.
*
* @param string $post_type Post type.
* @return string[] Column names that are REST-visible.
*/
public function get_rest_visible_hot_columns( string $post_type ): array {
$visible = array();
foreach ( $this->fields as $field ) {
if ( 'hot' === $field['zone'] && $field['post_type'] === $post_type && ! empty( $field['show_in_rest'] ) ) {
$visible[] = $field['column'];
}
}
return $visible;
}
/**
* Returns cold meta keys for a post type where show_in_rest is true.
* Used by REST endpoints to restrict publicly exposed fields.
*
* @param string $post_type Post type.
* @return string[] Meta keys that are REST-visible.
*/
public function get_rest_visible_cold_keys( string $post_type ): array {
$visible = array();
foreach ( $this->fields as $field ) {
if ( 'cold' === $field['zone'] && $field['post_type'] === $post_type && ! empty( $field['show_in_rest'] ) ) {
$visible[] = $field['meta_key'];
}
}
return array_unique( $visible );
}
/**
* Get warm field configuration.
*
* @param string $meta_key Meta key.
* @return array|null
*/
public function get_warm_field( string $meta_key ): ?array {
return $this->warm_fields[ $meta_key ] ?? null;
}
/**
* Check which zone a meta_key belongs to for a given post type.
*
* @param string $post_type Post type.
* @param string $meta_key Meta key.
* @return string|null Zone name or null if not registered.
*/
public function get_field_zone( string $post_type, string $meta_key ): ?string {
$field = $this->get_field( $post_type, $meta_key );
return $field['zone'] ?? null;
}
/**
* Get all registered fields (for admin dashboard / analysis).
*
* @return array<string, array>
*/
public function all(): array {
return $this->fields;
}
/**
* Get summary statistics for the admin dashboard.
*
* @return array{hot: int, warm: int, cold: int, archive: int, total: int}
*/
public function get_stats(): array {
$stats = array(
'hot' => 0,
'warm' => 0,
'cold' => 0,
'archive' => 0,
'total' => 0,
);
foreach ( $this->fields as $field ) {
if ( isset( $stats[ $field['zone'] ] ) ) {
++$stats[ $field['zone'] ];
}
++$stats['total'];
}
return $stats;
}
}