Files
2meet-data-optimizer/includes/class-tmdo-api.php
T
wpdev d36bb954d1 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
2026-07-31 05:06:36 +08:00

184 lines
6.5 KiB
PHP

<?php
/**
* TMDO_API — public facade for partner plugins.
*
* Provides the canonical, stable API contract for reading/writing entity
* fields managed by WP Data Optimizer. Partner plugins MUST use this API
* instead of direct `get_post_meta()`, `get_user_meta()`, etc., when the
* field is registered to WPDO.
*
* Part A.4 of the Anti-EAV Playbook (plan file).
*
* Usage:
* TMDO_API::get_field( $post_id, 'hp_price' ); // post entity
* TMDO_API::get_entity( 'user', $uid, 'points' ); // any entity
* TMDO_API::set_field( $post_id, 'hp_price', '100.00' );
* TMDO_API::query( [ 'post_type' => 'hp_listing', 'meta_query' => [...] ] );
*
* @package WP_Data_Optimizer
* @since 2.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Public read/write facade. All methods are static and side-effect free
* outside of the underlying meta API.
*
* Backwards compatibility: this class is the long-lived contract for
* partner plugins. Internal implementation may change between versions
* (e.g. switch from postmeta passthrough to Hook Bus dispatch); the
* signatures here are guaranteed stable across the 2.x line.
*/
final class TMDO_API {
/**
* Get a single meta field for a post.
*
* Internally:
* - When the field is registered to a Zone, reads from the zone table
* once cutover.
* - When the field is unregistered, falls back to native get_post_meta()
* transparently.
*
* @param int $post_id Post ID.
* @param string $meta_key Meta key.
* @param bool $single Whether to return a single scalar.
* @return mixed Meta value (or empty string when single=true and absent).
*/
public static function get_field( int $post_id, string $meta_key, bool $single = true ) {
// Defer to standard WordPress metadata API. The Hook Bus / Sync_Bridge
// transparently routes the read to the appropriate zone table when
// the module is in a read-custom state (cutover/cleanup/complete).
// This single line of indirection is the contract that lets us swap
// internal storage without breaking callers.
return get_post_meta( $post_id, $meta_key, $single );
}
/**
* Get a single meta field for any entity (post/user/term/comment).
*
* @param string $entity_type One of: post, user, term, comment.
* @param int $entity_id Entity ID.
* @param string $meta_key Meta key.
* @param bool $single Whether to return a single scalar.
* @return mixed
*/
public static function get_entity( string $entity_type, int $entity_id, string $meta_key, bool $single = true ) {
return match ( $entity_type ) {
'post' => get_post_meta( $entity_id, $meta_key, $single ),
'user' => get_user_meta( $entity_id, $meta_key, $single ),
'term' => get_term_meta( $entity_id, $meta_key, $single ),
'comment' => get_comment_meta( $entity_id, $meta_key, $single ),
default => null,
};
}
/**
* Set a single meta field for a post.
*
* @param int $post_id Post ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value New value.
* @return bool|int Result of update_post_meta.
*/
public static function set_field( int $post_id, string $meta_key, $meta_value ) {
return update_post_meta( $post_id, $meta_key, $meta_value );
}
/**
* Set a meta field for any entity.
*
* @param string $entity_type One of: post, user, term, comment.
* @param int $entity_id Entity ID.
* @param string $meta_key Meta key.
* @param mixed $meta_value New value.
* @return bool|int
*/
public static function set_entity( string $entity_type, int $entity_id, string $meta_key, $meta_value ) {
return match ( $entity_type ) {
'post' => update_post_meta( $entity_id, $meta_key, $meta_value ),
'user' => update_user_meta( $entity_id, $meta_key, $meta_value ),
'term' => update_term_meta( $entity_id, $meta_key, $meta_value ),
'comment' => update_comment_meta( $entity_id, $meta_key, $meta_value ),
default => false,
};
}
/**
* Run a meta_query — currently a thin wrapper around WP_Query.
*
* Future: when v2.0.0 entity adapters are fully wired, this method will
* compile cross-entity queries via TMDO_Query_Compiler.
*
* @param array $args WP_Query-compatible arguments.
* @return WP_Query
*/
public static function query( array $args ): WP_Query {
return new WP_Query( $args );
}
/**
* Check whether a field is registered to WPDO (zone or entity).
*
* Useful for partner plugins to choose between TMDO_API and native APIs.
*
* @param string $entity_type One of: post, user, term, comment.
* @param string $meta_key Meta key.
* @return bool True when the field is registered.
*/
public static function is_field_registered( string $entity_type, string $meta_key ): bool {
// Zone path (post entity only): TMDO_Schema_Registry.
if ( 'post' === $entity_type && class_exists( 'TMDO_Schema_Registry' ) ) {
$registry = TMDO_Schema_Registry::instance();
// We can't filter without a post_type — search across all hot/cold/warm.
foreach ( $registry->all() as $field ) {
if ( ( $field['meta_key'] ?? '' ) === $meta_key ) {
return true;
}
}
}
// Entity path: TMDO_Entity_Registry (PR-1 ported).
if ( class_exists( 'TMDO_Entity_Registry' ) ) {
return TMDO_Entity_Registry::is_managed( $entity_type, $meta_key );
}
return false;
}
/**
* Trace the storage backend currently serving a field.
*
* Returns one of: 'postmeta' (native fallback), 'zone_hot', 'zone_warm',
* 'zone_cold', 'zone_archive', 'entity_table', 'unregistered'.
*
* Useful for `wp wpdo doctor` and Conflict Detector reports.
*
* @param string $entity_type Entity type.
* @param string $meta_key Meta key.
* @param string $post_type Post type, for zone routing (post entity only).
* @return string Storage label.
*/
public static function trace_storage( string $entity_type, string $meta_key, string $post_type = '' ): string {
if ( 'post' === $entity_type && class_exists( 'TMDO_Schema_Registry' ) && '' !== $post_type ) {
$field = TMDO_Schema_Registry::instance()->get_field( $post_type, $meta_key );
if ( $field ) {
$module = ( $field['zone'] ?? 'hot' ) . '_' . sanitize_key( $post_type );
if ( class_exists( 'TMDO_Feature_Flags' ) && TMDO_Feature_Flags::is_read_custom( $module ) ) {
return 'zone_' . $field['zone'];
}
return 'postmeta';
}
}
if ( class_exists( 'TMDO_Entity_Registry' ) && TMDO_Entity_Registry::is_managed( $entity_type, $meta_key ) ) {
return 'entity_table';
}
return 'unregistered';
}
}