Storage System

OptStack Storage System

OptStack uses a Store Adapter pattern to persist data to WordPress. Each store type wraps a WordPress storage mechanism and provides a consistent API for reading and writing data.


Store Types

StoreWordPress TableUse Case
OptionsStorewp_optionsOptions pages, theme settings, global configurations
PostStorewp_postmetaPost meta boxes, product data, page settings
TermStorewp_termmetaCategory settings, tag metadata, custom taxonomy fields
UserStorewp_usermetaUser profile extensions, user preferences

Key Principles

  1. Single Serialized Array: All stack data is stored as a single serialized array under one key
  2. Caching: Stores cache loaded data to minimize database queries
  3. Transparent: You typically don't interact with stores directly - OptStack handles it
  4. Consistent API: All stores implement the same StoreInterface

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     OptStack Facade                          β”‚
β”‚  (OptStack::getField, OptStack::updateField, etc.)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         Stack                                β”‚
β”‚              (getData, saveData, getField, etc.)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     StoreInterface                           β”‚
β”‚        (get, set, delete, all, has, setMany, etc.)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚              β”‚              β”‚              β”‚
          β–Ό              β–Ό              β–Ό              β–Ό
   OptionsStore    PostStore     TermStore      UserStore
   (wp_options)   (wp_postmeta) (wp_termmeta)  (wp_usermeta)

StoreInterface

All stores implement the StoreInterface contract, providing a consistent API:

interface StoreInterface
{
    // Get a single value
    public function get(string $key, mixed $default = null): mixed;
    
    // Set a single value
    public function set(string $key, mixed $value): bool;
    
    // Delete a single key
    public function delete(string $key): bool;
    
    // Get all data
    public function all(): array;
    
    // Check if key exists
    public function has(string $key): bool;
}

Additional Methods (All Stores)

Beyond the interface, all stores provide these additional methods:

// Set multiple values at once
$store->setMany(array $values): bool
 
// Replace all data (overwrites everything)
$store->replace(array $data): bool
 
// Delete all data for this stack
$store->deleteAll(): bool
 
// Clear the internal cache
$store->clearCache(): void

How Data is Stored

Single Serialized Array Pattern

OptStack stores all stack data as a single serialized array under one key. This approach:

  • Reduces database queries (one query loads all data)
  • Keeps related data together
  • Simplifies data management

Example:

For a stack with this structure:

$stack->field('site_name', ['type' => 'text']);
$stack->group('colors', function ($group) {
    $group->field('primary', ['type' => 'color']);
    $group->field('secondary', ['type' => 'color']);
});

Data is stored as:

[
    'site_name' => 'My Site',
    'colors' => [
        'primary' => '#3b82f6',
        'secondary' => '#8b5cf6',
    ]
]

Which is serialized in the database as:

a:2:{s:9:"site_name";s:7:"My Site";s:6:"colors";a:2:{s:7:"primary";s:7:"#3b82f6";s:9:"secondary";s:7:"#8b5cf6";}}

Where to Find Stored Data

ContextWordPress FunctionDatabase TableKey Column
Optionsget_option()wp_optionsoption_name
Post Metaget_post_meta()wp_postmetameta_key
Term Metaget_term_meta()wp_termmetameta_key
User Metaget_user_meta()wp_usermetameta_key

Automatic Store Binding

OptStack automatically binds the appropriate store based on how you define your stack:

MethodStoreWhen Bound
->forOptions()OptionsStoreDuring optstack_init
->forPostType()PostStoreOn REST API, OptStack::getField(), or save_post
->forTaxonomy()TermStoreOn REST API, OptStack::getField(), or term hooks
->forUser()UserStoreOn REST API, OptStack::getField(), or user hooks

Direct Store Access

While you typically use the OptStack facade, you can access stores directly:

Get Store from Stack

$stack = OptStack::get('theme_options');
$store = $stack->getStore();
 
// Direct store operations
$value = $store->get('site_name', 'Default');
$store->set('site_name', 'New Name');
$allData = $store->all();

Create Store Manually

use OptStack\WordPress\Store\OptionsStore;
use OptStack\WordPress\Store\PostStore;
use OptStack\WordPress\Store\TermStore;
use OptStack\WordPress\Store\UserStore;
 
// Options
$optionsStore = new OptionsStore('my_options');
$value = $optionsStore->get('key', 'default');
 
// Post meta
$postStore = new PostStore($postId, 'my_meta_key');
$value = $postStore->get('field_name');
 
// Term meta
$termStore = new TermStore($termId, 'term_settings');
$value = $termStore->get('color');
 
// User meta
$userStore = new UserStore($userId, 'user_preferences');
$value = $userStore->get('theme');

Storage Patterns

Pattern 1: Bulk Updates

When updating multiple fields:

// Inefficient - multiple database writes
$store->set('field1', 'value1');
$store->set('field2', 'value2');
$store->set('field3', 'value3');
 
// Efficient - single database write
$store->setMany([
    'field1' => 'value1',
    'field2' => 'value2',
    'field3' => 'value3',
]);

Pattern 2: Replace All Data

When you want to completely replace stored data:

// This replaces ALL data (be careful!)
$store->replace([
    'new_field' => 'new_value',
]);
// Previous fields are gone!

Pattern 3: Conditional Data Access

$store = $stack->getStore();
 
if ($store->has('premium_features')) {
    $features = $store->get('premium_features');
    // Process features
}

Performance Considerations

Caching

All stores implement internal caching:

// First call - hits database
$value1 = $store->get('field1');
 
// Second call - uses cache (no database query)
$value2 = $store->get('field2');
 
// Clear cache if needed (forces database reload)
$store->clearCache();

Large Data Sets

For stacks with many fields or large values:

  1. Use autoload: false for options (see OptionsStore)
  2. Consider splitting into multiple stacks
  3. Use deferred groups to delay loading UI

Debugging Storage

View Stored Data

// Get all data from a stack
$stack = OptStack::get('theme_options');
$data = $stack->getData();
error_log(print_r($data, true));
 
// Or using WordPress directly
$data = get_option('theme_options');
var_dump($data);

Check Store Type

$stack = OptStack::get('product_data');
$store = $stack->getStore();
 
if ($store instanceof OptionsStore) {
    echo "Using OptionsStore: " . $store->getOptionName();
} elseif ($store instanceof PostStore) {
    echo "Using PostStore for post: " . $store->getPostId();
} elseif ($store instanceof TermStore) {
    echo "Using TermStore for term: " . $store->getTermId();
} elseif ($store instanceof UserStore) {
    echo "Using UserStore for user: " . $store->getUserId();
}

Database Query

Check stored data directly in the database:

-- Options
SELECT * FROM wp_options WHERE option_name = 'theme_options';
 
-- Post Meta
SELECT * FROM wp_postmeta WHERE post_id = 123 AND meta_key = 'product_data';
 
-- Term Meta
SELECT * FROM wp_termmeta WHERE term_id = 5 AND meta_key = 'category_settings';
 
-- User Meta
SELECT * FROM wp_usermeta WHERE user_id = 1 AND meta_key = 'user_profile';

Best Practices

1. Use Meaningful Stack IDs

Stack ID becomes the storage key:

// βœ… Good - descriptive and namespaced
OptStack::make('mytheme_general_settings');
OptStack::make('myplugin_product_data');
 
// ❌ Bad - generic, may conflict
OptStack::make('settings');
OptStack::make('data');

2. Don't Mix Contexts

One stack = one storage context:

// βœ… Good - separate stacks for different contexts
OptStack::make('theme_options')->forOptions();
OptStack::make('product_data')->forPostType('product');
 
// ❌ Bad - trying to share stack across contexts

3. Use OptStack Facade

Prefer the facade over direct store access:

// βœ… Good - uses OptStack API
$price = OptStack::getField('product_data', 'price', 0, $post_id);
 
// Avoid unless necessary
$store = new PostStore($post_id, 'product_data');
$price = $store->get('price', 0);

4. Handle Missing Data

Always provide defaults:

// βœ… Good - always has a fallback
$color = OptStack::getField('theme_options', 'primary_color', '#3b82f6');
 
// ❌ Risky - may return null
$color = OptStack::getField('theme_options', 'primary_color');

5. Batch Updates When Possible

// βœ… Good - single save operation
OptStack::saveData('theme_options', [
    'site_name' => 'My Site',
    'tagline' => 'Just another site',
    'primary_color' => '#3b82f6',
]);
 
// Less efficient - multiple save operations
OptStack::updateField('theme_options', 'site_name', 'My Site');
OptStack::updateField('theme_options', 'tagline', 'Just another site');
OptStack::updateField('theme_options', 'primary_color', '#3b82f6');