-
Notifications
You must be signed in to change notification settings - Fork 0
How It Works
This page explains the internal workings of ShadowORM, including how data flows through the system for reads, writes, and queries.
ShadowORM intercepts WordPress core functions to redirect metadata operations from wp_postmeta to optimized shadow tables:
WordPress Core ShadowORM
│ │
get_post_meta() ──────────────► ReadInterceptor
update_post_meta() ───────────► WriteInterceptor
WP_Query (meta_query) ────────► QueryInterceptor
When get_post_meta() is called, ShadowORM intercepts the request.
-
Hook Triggered:
get_post_metadatafilter fires - Type Check: Verify post type is supported (post, page, product)
- Cache Check: Look for entity in RuntimeCache
- Not Found Marker: Check if post was previously marked as not in shadow table
- Database Query: If not cached, fetch entire row from shadow table
- Cache Update: Store entity in RuntimeCache
- Return Value: Extract requested meta key and return
// User code
$price = get_post_meta($product_id, '_price', true);
// ReadInterceptor::intercept()
// 1. Check if post type is supported
if (!in_array($post->post_type, ['post', 'page', 'product'])) {
return $value; // Fall back to WordPress
}
// 2. Check RAM cache
$entity = $cache->get($postId);
// 3. If not cached, load from shadow table
if ($entity === null) {
$entity = $repository->find($postId);
$cache->set($postId, $entity);
}
// 4. Return the specific meta value
return $entity->getMeta('_price');| Scenario | Traditional wp_postmeta | ShadowORM |
|---|---|---|
| Product with 50 meta keys | 50 queries | 1 query |
| Repeat read of same post | 1 query each | 0 queries (cached) |
When post data is saved, ShadowORM syncs to the shadow table.
| Hook | Priority | Purpose |
|---|---|---|
save_post |
20 | Sync post after save |
updated_post_meta |
10 | Sync after meta update |
added_post_meta |
10 | Sync after meta add |
deleted_post_meta |
10 | Sync after meta delete |
deleted_post |
10 | Remove from shadow table |
- Check Post Type: Only sync supported types
- Skip Drafts: Ignore auto-drafts and revisions
-
Fetch Raw Meta: Read directly from
wp_postmeta(not shadow table) - Normalize Data: Unserialize arrays, remove internal keys
-
Create Entity: Build new
ShadowEntity - Save to Repository: Insert or update shadow table
- Update Cache: Refresh RuntimeCache
During sync, ShadowORM reads from wp_postmeta directly (bypassing its own interceptor) to avoid circular dependencies:
// SyncService::syncPost()
$rawMeta = $wpdb->get_results(
"SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = %d",
$postId
);When WP_Query uses meta_query, ShadowORM transforms the SQL.
- Post type is supported
- Query contains
meta_queryparameter - Not in admin area (unless AJAX)
- Shadow table exists for the post type
-
Detect Query:
posts_clausesfilter fires -
Extract Meta Query: Get
meta_queryfrom WP_Query - Build Conditions: Transform to JSON or JOIN syntax
- Modify Clauses: Update WHERE, JOIN clauses
- Return Modified SQL: WordPress executes optimized query
Original WordPress query:
SELECT * FROM wp_posts
INNER JOIN wp_postmeta ON post_id = ID
WHERE meta_key = '_price' AND meta_value > 100ShadowORM transformed (MySQL 8.0):
SELECT * FROM wp_posts
INNER JOIN wp_app_shadow_product ON post_id = ID
WHERE JSON_EXTRACT(meta_data, '$._price') > 100For MySQL 5.7/MariaDB, uses lookup table:
SELECT DISTINCT s.* FROM wp_app_shadow_product AS s
INNER JOIN wp_app_shadow_product_lookup AS l0 ON s.post_id = l0.post_id
WHERE l0.meta_key = '_price' AND l0.meta_value > 100The RuntimeCache provides per-request caching of ShadowEntity objects.
- In-Memory Storage: Array-based cache, no external dependencies
- Not Found Tracking: Marks posts that don't exist in shadow table
- Request Scoped: Cache is cleared at end of request
- No TTL: Lives for duration of PHP request
| State | Description |
|---|---|
null |
Not yet checked |
ShadowEntity |
Loaded and cached |
not_found |
Confirmed not in shadow table |
When syncing, metadata is normalized:
-
Unserialize:
maybe_unserialize()for PHP serialized data -
Skip Internal: Remove
_edit_lock,_edit_last - Flatten Arrays: Single values extracted from arrays
- Preserve Types: Arrays remain as JSON arrays
Input (wp_postmeta):
meta_key: _price, meta_value: "99.99"
meta_key: _sku, meta_value: "ABC123"
meta_key: _product_attributes, meta_value: a:2:{...}
Output (shadow table JSON):
{
"_price": "99.99",
"_sku": "ABC123",
"_product_attributes": {
"color": ["red", "blue"],
"size": ["S", "M", "L"]
}
}High-performance ORM layer for WordPress/WooCommerce
- Home - Overview and features
- Installation - Setup instructions
- Getting Started - Quick start guide
- Configuration - Settings reference
- WP-CLI Commands - Command reference
- Architecture - Technical design
- How It Works - Internal workings
- API Reference - Developer API
- Troubleshooting - Problem solving
- FAQ - Common questions
Links