Files
2meet-data-optimizer-hivepr…/includes/hivepress/trait-tmdo-hp-adapter.php
T
wpdev b4400a68e5 chore: initial snapshot of 2meet-data-optimizer-hivepress-addon 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
2026-07-31 05:06:36 +08:00

273 lines
9.3 KiB
PHP

<?php
/**
* Shared implementation trait for HivePress addon adapters.
*
* Composes `TMDO_Anti_EAV_Aware` (the canonical sugar layer for partner
* plugins) and adds HivePress-specific helpers:
*
* - Hot/Warm/Cold zone field registration shortcuts (default `hp_*` post types)
* - Custom-table registration with auto-filled HP conventions
* - 8-dimension suitability self-score skeleton with sensible defaults
* - Default no-op implementations for query/event hooks
* - `on_after_addon_write()` cross-adapter event subscription helper
*
* Concrete adapters `use TMDO_HivePress_Adapter_Trait;` and override only
* what they need. This keeps each adapter file focused on its domain
* (Listing fields, Booking fields, Review aggregations, etc.).
*
* @package WP_Data_Optimizer
* @since 3.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! trait_exists( 'TMDO_HivePress_Adapter_Trait' ) ) {
trait TMDO_HivePress_Adapter_Trait {
// Compose the canonical sugar trait so adapters get its helpers
// (`register_field`, `register_custom_table`, `register_entity_fields`,
// `get_field`, `set_field`, `on_after_write`, `bind_anti_eav_hooks`)
// without each having to `use` two traits.
use TMDO_Anti_EAV_Aware;
// ── Default no-op implementations for the interface ────────────────
/**
* Override in concrete adapter (default 'hivepress' for safety).
*/
public function plugin_slug(): string {
return 'hivepress';
}
/**
* Override in concrete adapter (default empty string disables detection).
*/
public function detection_class(): string {
return '';
}
/**
* Override in concrete adapter (default empty string).
*/
public function detection_const(): string {
return '';
}
/**
* Override in concrete adapter to set version floor.
*
* Default empty string = accept any installed version.
*/
public function minimum_addon_version(): string {
return '';
}
/**
* Default no-op for adapters with no zone fields (e.g. presentation-only addons).
*
* @param TMDO_Schema_Registry $registry Schema registry singleton.
*/
public function on_register_fields( TMDO_Schema_Registry $registry ): void {
unset( $registry );
}
/**
* Default no-op — most addons don't contribute entity fields.
*
* `TMDO_Anti_EAV_Aware::on_register_entity_fields()` is overridden in
* concrete adapters that own user/term/comment groups (e.g. core
* adapter for hp_user). The trait's own default is also no-op so this
* override exists only to satisfy the interface contract symmetry.
*/
public function on_register_entity_fields(): void {
// Intentional no-op.
}
/**
* Default no-op for adapters with no custom tables.
*
* @param TMDO_Custom_Table_Registry $registry Custom table registry singleton.
*/
public function on_register_custom_tables( TMDO_Custom_Table_Registry $registry ): void {
unset( $registry );
}
/**
* Default no-op — adapter has no query routing needs.
*
* Adapters with hot zone fields used in search/filter override and
* call `TMDO_HivePress_Query_Router::register_for( $this )` here.
*/
public function on_register_query_hooks(): void {
// Intentional no-op.
}
/**
* Default no-op — adapter has no cross-adapter event handling.
*/
public function on_register_event_hooks(): void {
// Intentional no-op.
}
/**
* Self-report 8-dimension anti-EAV suitability score.
*
* Default returns 1.0 across the board, plus aggregate 10.0. Adapters
* with known weaknesses (e.g. addon-only detection, missing hot
* tables) MUST override and lower the relevant dimensions to keep
* the aggregate honest.
*
* @return array{d1_meta_calls:float, d2_meta_sql:float, d3_table_coverage:float, d4_registry_meta:float, d5_hook_bus:float, d6_options:float, d7_coupling:float, d8_perf:float, aggregate:float}
*/
public function suitability_score(): array {
return $this->compose_score( array() );
}
/**
* Default doctor probe — returns ok with adapter name.
*
* Adapters with custom tables MUST override and verify table existence.
*
* @return array{ok:bool, message:string, details?:array<string,mixed>}
*/
public function doctor_check(): array {
return array(
'ok' => true,
'message' => sprintf( '%s: adapter loaded, no custom tables to probe', $this->plugin_slug() ),
);
}
/**
* Default no FSM migrations (adapter is read-only or zero hot fields).
*
* @return array<int, string>
*/
public function migrations(): array {
return array();
}
// ── Helper: zone-aware field registration shortcuts ─────────────────
/**
* Register a single hot zone field (varchar/int/decimal column on `wp_wpdo_hot_<post_type>`).
*
* @param TMDO_Schema_Registry $registry Provided by `wpdo_register_fields` hook.
* @param string $post_type WordPress post type (e.g. 'hp_listing').
* @param string $meta_key wp_postmeta meta_key.
* @param string $data_type SQL column type (e.g. "tinyint(1) NOT NULL DEFAULT 0").
* @param array<string,mixed> $extra Optional overrides: column, indexed, etc.
*/
protected function register_hot( TMDO_Schema_Registry $registry, string $post_type, string $meta_key, string $data_type, array $extra = array() ): void {
$config = array_merge(
array(
'post_type' => $post_type,
'meta_key' => $meta_key,
'zone' => 'hot',
'data_type' => $data_type,
'column' => sanitize_key( $meta_key ),
'indexed' => false,
),
$extra
);
$this->register_field( $registry, $config );
}
/**
* Register a single cold zone field (object cache + JSON blob fallback).
*
* @param TMDO_Schema_Registry $registry Provided by `wpdo_register_fields` hook.
* @param string $post_type WordPress post type.
* @param string $meta_key wp_postmeta meta_key.
* @param int $cache_ttl Cache TTL in seconds (default 1 hour).
* @param array<string,mixed> $extra Optional overrides.
*/
protected function register_cold( TMDO_Schema_Registry $registry, string $post_type, string $meta_key, int $cache_ttl = 3600, array $extra = array() ): void {
$config = array_merge(
array(
'post_type' => $post_type,
'meta_key' => $meta_key,
'zone' => 'cold',
'cache_group' => 'wpdo_cold_' . sanitize_key( $post_type ),
'cache_ttl' => $cache_ttl,
),
$extra
);
$this->register_field( $registry, $config );
}
/**
* Register a single warm zone field (TTL counter / temp value).
*
* @param TMDO_Schema_Registry $registry Provided by `wpdo_register_fields` hook.
* @param string $post_type WordPress post type.
* @param string $meta_key wp_postmeta meta_key.
* @param int|null $ttl TTL in seconds (null = no expiry).
* @param array<string,mixed> $extra Optional overrides.
*/
protected function register_warm( TMDO_Schema_Registry $registry, string $post_type, string $meta_key, ?int $ttl = null, array $extra = array() ): void {
$config = array_merge(
array(
'post_type' => $post_type,
'meta_key' => $meta_key,
'zone' => 'warm',
'ttl' => $ttl,
),
$extra
);
$this->register_field( $registry, $config );
}
/**
* Subscribe to writes on another adapter's keys (cross-adapter coordination).
*
* Wraps `TMDO_Anti_EAV_Aware::on_after_write()` with a description hook
* so the cross-adapter linkage is greppable for the suitability scorer's
* D5 (Hook Bus Integration) check.
*
* @param string $key_prefix Meta key prefix (e.g. 'hp_sold_out_').
* @param callable $callback Receives (entity_type, entity_id, key, value, ok, op, before).
*/
protected function on_after_addon_write( string $key_prefix, callable $callback ): void {
$this->on_after_write( $key_prefix, $callback );
}
// ── Helper: 8-dimension score composition ───────────────────────────
/**
* Compose an 8-dimension score, filling missing keys with 1.0 and
* computing the aggregate as `sum / 8 * 1.25` (so 1.0 across all
* → 10.0).
*
* @param array<string,float> $partial Override values keyed by `d1_meta_calls`...`d8_perf`.
* @return array{d1_meta_calls:float, d2_meta_sql:float, d3_table_coverage:float, d4_registry_meta:float, d5_hook_bus:float, d6_options:float, d7_coupling:float, d8_perf:float, aggregate:float}
*/
protected function compose_score( array $partial ): array {
$defaults = array(
'd1_meta_calls' => 1.0,
'd2_meta_sql' => 1.0,
'd3_table_coverage' => 1.0,
'd4_registry_meta' => 1.0,
'd5_hook_bus' => 1.0,
'd6_options' => 1.0,
'd7_coupling' => 1.0,
'd8_perf' => 1.0,
);
$score = array_merge( $defaults, $partial );
// Clamp every dimension to [0.0, 1.0].
foreach ( $defaults as $k => $_ ) {
$score[ $k ] = max( 0.0, min( 1.0, (float) ( $score[ $k ] ?? 1.0 ) ) );
}
$sum = array_sum( array_intersect_key( $score, $defaults ) );
$score['aggregate'] = round( $sum / 8.0 * 10.0, 2 );
return $score;
}
}
} // end if ( ! trait_exists )