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
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* TMDO_Demo_Entity_Counter — production proof-of-life for entity adapters.
|
||||
*
|
||||
* Task D follow-up to PR-5: demonstrates that the entity adapter framework
|
||||
* actually works end-to-end (not just stubs). Implements a "counter" pattern
|
||||
* shared across post / user / term / comment entities — a common gamification
|
||||
* primitive (user points, listing views, comment helpful_count, term usage).
|
||||
*
|
||||
* Storage:
|
||||
* wp_wpdo_demo_entity_counters (entity_type, entity_id, counter_key, counter_value, updated_at)
|
||||
*
|
||||
* Lifecycle:
|
||||
* - Plugin or test invokes ::set( 'user', 42, 'points', 50 )
|
||||
* - This writes to BOTH wp_usermeta (native, preserved) AND wpdo_demo table
|
||||
* - Reads come from wpdo_demo when feature flag entity_demo_counter == 'cutover',
|
||||
* otherwise fall through to native usermeta (transparent fallback)
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// phpcs:disable Squiz.Commenting.FunctionComment.Missing,Squiz.Commenting.InlineComment.InvalidEndChar,Generic.Commenting.DocComment.MissingShort,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.PHP.YodaConditions,Generic.CodeAnalysis.EmptyStatement -- v2.0.0 partner integrations: pure registration helpers + intentional silent catches.
|
||||
|
||||
/**
|
||||
* Demo entity counter — proves 4-entity adapter framework works end-to-end.
|
||||
*/
|
||||
final class TMDO_Demo_Entity_Counter {
|
||||
|
||||
/** Feature flag module name. */
|
||||
public const MODULE = 'entity_demo_counter';
|
||||
|
||||
/** Custom table holding all counters. */
|
||||
public const TABLE = 'wpdo_demo_entity_counters';
|
||||
|
||||
/**
|
||||
* Idempotent install of the demo table.
|
||||
*
|
||||
* Called from TMDO_Installer::install_v2_tables() OR manually for demos.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function install_table(): void {
|
||||
global $wpdb;
|
||||
if ( ! function_exists( 'dbDelta' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
}
|
||||
|
||||
$charset = $wpdb->get_charset_collate();
|
||||
$table = $wpdb->prefix . self::TABLE;
|
||||
|
||||
$sql = "CREATE TABLE {$table} (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
entity_type varchar(20) NOT NULL DEFAULT '',
|
||||
entity_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||||
counter_key varchar(100) NOT NULL DEFAULT '',
|
||||
counter_value bigint(20) NOT NULL DEFAULT 0,
|
||||
updated_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY ui_entity_counter (entity_type, entity_id, counter_key),
|
||||
KEY idx_lookup (entity_type, counter_key, counter_value),
|
||||
KEY idx_entity (entity_type, entity_id)
|
||||
) {$charset};";
|
||||
dbDelta( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the demo table — idempotent.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function drop_table(): void {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::TABLE;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a counter for any entity. Writes to BOTH native meta AND demo table
|
||||
* when feature flag is in a write-active state. Otherwise writes native
|
||||
* only (idle path — full backward compatibility).
|
||||
*
|
||||
* @param string $entity_type One of: post, user, term, comment.
|
||||
* @param int $entity_id Entity ID.
|
||||
* @param string $counter_key Counter slug (e.g. 'points', 'view_count').
|
||||
* @param int $value New value.
|
||||
* @return bool
|
||||
*/
|
||||
public static function set( string $entity_type, int $entity_id, string $counter_key, int $value ): bool {
|
||||
if ( ! self::is_valid_entity( $entity_type ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always write the native meta first (durability anchor).
|
||||
TMDO_API::set_entity( $entity_type, $entity_id, $counter_key, $value );
|
||||
|
||||
// Conditional dual-write to demo table.
|
||||
if ( TMDO_Feature_Flags::is_write_active( self::MODULE ) ) {
|
||||
self::write_to_table( $entity_type, $entity_id, $counter_key, $value );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a counter value. Source depends on feature flag state:
|
||||
* - read_custom (cutover/cleanup/complete) → demo table
|
||||
* - otherwise → native meta (fallback)
|
||||
*
|
||||
* @param string $entity_type One of: post, user, term, comment.
|
||||
* @param int $entity_id Entity ID.
|
||||
* @param string $counter_key Counter slug.
|
||||
* @return int
|
||||
*/
|
||||
public static function get( string $entity_type, int $entity_id, string $counter_key ): int {
|
||||
if ( ! self::is_valid_entity( $entity_type ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( TMDO_Feature_Flags::is_read_custom( self::MODULE ) ) {
|
||||
$row = self::read_from_table( $entity_type, $entity_id, $counter_key );
|
||||
if ( null !== $row ) {
|
||||
return (int) $row;
|
||||
}
|
||||
// Fallback to native if zone row missing — graceful degradation.
|
||||
}
|
||||
|
||||
return (int) TMDO_API::get_entity( $entity_type, $entity_id, $counter_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-N entities by counter value — the killer query that postmeta CANNOT
|
||||
* do efficiently (requires full scan + filesort). Demonstrates the value of
|
||||
* the entity adapter pattern.
|
||||
*
|
||||
* @param string $entity_type One of: post, user, term, comment.
|
||||
* @param string $counter_key Counter slug.
|
||||
* @param int $limit Max rows.
|
||||
* @return array<int, array{entity_id:int, counter_value:int}>
|
||||
*/
|
||||
public static function top_n( string $entity_type, string $counter_key, int $limit = 10 ): array {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::TABLE;
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from constant.
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT entity_id, counter_value FROM `{$table}` WHERE entity_type = %s AND counter_key = %s ORDER BY counter_value DESC LIMIT %d",
|
||||
$entity_type,
|
||||
$counter_key,
|
||||
$limit
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
return array_map(
|
||||
static fn( array $r ) => array(
|
||||
'entity_id' => (int) $r['entity_id'],
|
||||
'counter_value' => (int) $r['counter_value'],
|
||||
),
|
||||
$rows ?: array()
|
||||
);
|
||||
}
|
||||
|
||||
// ── Internals ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @param string $entity_type Entity type.
|
||||
*/
|
||||
private static function is_valid_entity( string $entity_type ): bool {
|
||||
return in_array( $entity_type, array( 'post', 'user', 'term', 'comment' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single counter value via UPSERT (1 round-trip).
|
||||
*
|
||||
* @param string $entity_type One of: post, user, term, comment.
|
||||
* @param int $entity_id Entity ID.
|
||||
* @param string $counter_key Counter slug.
|
||||
* @param int $value New value.
|
||||
* @return void
|
||||
*/
|
||||
private static function write_to_table( string $entity_type, int $entity_id, string $counter_key, int $value ): void {
|
||||
global $wpdb;
|
||||
TMDO_DB::upsert(
|
||||
$wpdb->prefix . self::TABLE,
|
||||
array(
|
||||
'entity_type' => $entity_type,
|
||||
'entity_id' => $entity_id,
|
||||
'counter_key' => $counter_key,
|
||||
'counter_value' => $value,
|
||||
'updated_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( 'counter_value', 'updated_at' ),
|
||||
array( 'entity_type', 'entity_id', 'counter_key' ),
|
||||
array( '%s', '%d', '%s', '%d', '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single counter value from the demo table.
|
||||
*
|
||||
* @param string $entity_type Entity type.
|
||||
* @param int $entity_id Entity ID.
|
||||
* @param string $counter_key Counter slug.
|
||||
* @return int|null Null when row absent.
|
||||
*/
|
||||
private static function read_from_table( string $entity_type, int $entity_id, string $counter_key ): ?int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . self::TABLE;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table from constant.
|
||||
$value = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT counter_value FROM `{$table}` WHERE entity_type = %s AND entity_id = %d AND counter_key = %s LIMIT 1",
|
||||
$entity_type,
|
||||
$entity_id,
|
||||
$counter_key
|
||||
)
|
||||
);
|
||||
return null === $value ? null : (int) $value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user