The Option module provides a Laravel-style interface for managing WordPress options through a clean facade pattern. It bridges WordPress's native option system with Laravel's modern architecture patterns.
use Pollora\Support\Facades\Option;
// Set an option
Option::set('site_theme_color', '#3498db');
// Get an option with default
$color = Option::get('site_theme_color', '#ffffff');
// Update existing option
Option::update('site_title', 'My WordPress Site');
// Check if option exists
if (Option::exists('custom_setting')) {
// Option exists
}
// Delete an option
Option::delete('old_setting');For better testability and dependency management, inject the service directly:
Advanced usage:
OptionServiceis an application-layer contract intended for dependency injection in services. For most use cases, prefer theOptionfacade above.
use Pollora\Option\Application\Service\OptionService;
class ThemeController
{
public function __construct(
private readonly OptionService $optionService
) {}
public function updateThemeSettings(array $settings): void
{
foreach ($settings as $key => $value) {
$this->optionService->set($key, $value);
}
}
}Retrieves an option value. Returns the default if the option doesn't exist.
// Get with string default
$siteName = Option::get('site_name', 'Default Site');
// Get with array default
$themeOptions = Option::get('theme_options', [
'color' => '#ffffff',
'layout' => 'sidebar'
]);
// Get with null default (explicit)
$customOption = Option::get('custom_option', null);Creates a new option or updates an existing one. Returns true on success.
// Set string value
Option::set('company_name', 'Acme Corp');
// Set array value (automatically serialized)
Option::set('contact_info', [
'email' => 'contact@example.com',
'phone' => '+1234567890'
]);
// Set boolean value
Option::set('maintenance_mode', true);
// Set object value
Option::set('api_config', new ApiConfiguration());Updates an existing option. Functionally identical to set() but semantically clearer when updating.
// Update existing setting
Option::update('posts_per_page', 15);
// Update complex configuration
$config = Option::get('email_settings', []);
$config['smtp_host'] = 'new-smtp.example.com';
Option::update('email_settings', $config);Removes an option from the database. Returns true if deleted or didn't exist.
// Delete single option
Option::delete('temporary_setting');
// Delete multiple options
$oldSettings = ['old_theme', 'deprecated_option', 'unused_config'];
foreach ($oldSettings as $setting) {
Option::delete($setting);
}Checks if an option exists, regardless of its value (including false, 0, empty string).
// Check existence before processing
if (Option::exists('premium_features')) {
$features = Option::get('premium_features');
$this->enablePremiumFeatures($features);
}
// Conditional setting
if (! Option::exists('initial_setup_complete')) {
$this->runInitialSetup();
Option::set('initial_setup_complete', true);
}Alias for delete(). Provides Laravel-style naming consistency.
// Laravel-style option removal
Option::forget('cached_data');The Option module validates keys and values, and WordPress serializes what it stores:
// Automatic serialization for complex types
Option::set('user_preferences', [
'theme' => 'dark',
'notifications' => true,
'language' => 'en'
]);
// Scalars are stored as text
Option::set('items_per_page', 15);
$perPage = Option::get('items_per_page'); // 15 right after the write, '15' once read from the database
// false and null are stored as an empty string
Option::set('is_enabled', false);
$isEnabled = Option::get('is_enabled'); // false right after the write, '' afterwardsArrays and objects come back as arrays and objects. A scalar does not keep its type: WordPress stores every scalar as text, so true reads back as '1', and false and null as ''. Cast the value you read ((bool) Option::get('is_enabled')) rather than compare it with === false.
The module reads with get_option($key, null), so null means the option is not in the database. A stored false or null is an empty string in the database, never null:
Option::exists('is_enabled'); // true once false has been stored
Option::get('is_enabled', 'fallback'); // the stored value (false, then ''), not 'fallback'
Option::get('never_stored', 'fallback'); // 'fallback'The default of get() is therefore used only for an option that does not exist. To tell an unset option from one set to false, use exists().
Domain types as public API: The
Domain\ModelsandDomain\Exceptionstypes below are stable public contracts that follow semantic versioning. Importing them directly is the intended usage pattern.
use Pollora\Option\Domain\Model\Option as OptionModel;
// Create option object for advanced manipulation
$option = new OptionModel('api_credentials', [
'key' => 'abc123',
'secret' => 'xyz789'
], autoload: false); // Don't autoload sensitive data
// Value objects are immutable
$updatedOption = $option->withValue([
'key' => 'new123',
'secret' => 'new789'
]);// Batch update with validation
$settings = [
'site_title' => 'New Site Title',
'site_description' => 'A great WordPress site',
'posts_per_page' => 10
];
foreach ($settings as $key => $value) {
try {
Option::update($key, $value);
} catch (InvalidOptionException $e) {
Log::warning("Failed to update option {$key}: {$e->getMessage()}");
}
}// Theme configuration pattern
class ThemeConfigManager
{
private const CONFIG_KEY = 'theme_config';
public function getConfig(): array
{
return Option::get(self::CONFIG_KEY, $this->getDefaultConfig());
}
public function updateConfig(array $updates): void
{
$config = $this->getConfig();
$merged = array_merge($config, $updates);
Option::update(self::CONFIG_KEY, $merged);
}
private function getDefaultConfig(): array
{
return [
'layout' => 'default',
'sidebar' => true,
'color_scheme' => 'light'
];
}
}The Option module is automatically registered via OptionServiceProvider. For custom bindings:
// In your theme's service provider
public function register(): void
{
$this->app->singleton(CustomOptionService::class, function ($app) {
return new CustomOptionService(
$app->make(OptionService::class),
$app->make('config')
);
});
}use Pollora\Events\OptionUpdated;
// Listen for option changes
Event::listen(OptionUpdated::class, function (OptionUpdated $event) {
if ($event->key === 'site_theme') {
Cache::forget('compiled_css');
}
});
// Custom option service with events
class EventfulOptionService
{
public function __construct(
private readonly OptionService $optionService,
private readonly EventDispatcher $events
) {}
public function set(string $key, mixed $value): bool
{
$result = $this->optionService->set($key, $value);
if ($result) {
$this->events->dispatch(new OptionUpdated($key, $value));
}
return $result;
}
}// Cached option service for performance
class CachedOptionService
{
private const CACHE_TTL = 3600; // 1 hour
public function __construct(
private readonly OptionService $optionService,
private readonly CacheManager $cache
) {}
public function get(string $key, mixed $default = null): mixed
{
$cacheKey = "option.{$key}";
return $this->cache->remember($cacheKey, self::CACHE_TTL, function () use ($key, $default) {
return $this->optionService->get($key, $default);
});
}
public function set(string $key, mixed $value): bool
{
$result = $this->optionService->set($key, $value);
if ($result) {
$this->cache->forget("option.{$key}");
}
return $result;
}
}use Pollora\Option\Domain\Exception\OptionNotFoundException;
use Pollora\Option\Domain\Exception\InvalidOptionException;
// Handle missing required options
try {
$apiKey = Option::get('required_api_key');
if ($apiKey === null) {
throw new OptionNotFoundException('API key is required');
}
} catch (OptionNotFoundException $e) {
Log::error('Configuration error: ' . $e->getMessage());
return response()->json(['error' => 'Service unavailable'], 503);
}
// Handle invalid option data
try {
Option::set('', 'invalid-empty-key');
} catch (InvalidOptionException $e) {
// Option key cannot be empty
}// Safe option retrieval with fallbacks
class ConfigService
{
public function getEmailConfig(): array
{
try {
return Option::get('email_config', $this->getDefaultEmailConfig());
} catch (Exception $e) {
Log::warning('Failed to load email config', ['error' => $e->getMessage()]);
return $this->getDefaultEmailConfig();
}
}
private function getDefaultEmailConfig(): array
{
return [
'driver' => 'smtp',
'host' => env('MAIL_HOST', 'localhost'),
'port' => env('MAIL_PORT', 587)
];
}
}Extension author API: The
OptionRepositoryInterfacecontract is a stable public interface for mocking in tests or providing custom storage backends.
use Pollora\Option\Application\Service\OptionService;
use Pollora\Option\Domain\Contract\OptionRepositoryInterface;
class OptionServiceTest extends TestCase
{
private OptionService $service;
private OptionRepositoryInterface $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = Mockery::mock(OptionRepositoryInterface::class);
$this->service = new OptionService($this->repository);
}
public function test_can_get_option_with_default(): void
{
$this->repository
->shouldReceive('get')
->with('test_key')
->once()
->andReturn(null);
$result = $this->service->get('test_key', 'default_value');
$this->assertEquals('default_value', $result);
}
}class OptionIntegrationTest extends TestCase
{
public function test_option_crud_operations(): void
{
// Create
$result = Option::set('test_option', 'test_value');
$this->assertTrue($result);
// Read
$value = Option::get('test_option');
$this->assertEquals('test_value', $value);
// Update
$result = Option::update('test_option', 'updated_value');
$this->assertTrue($result);
$value = Option::get('test_option');
$this->assertEquals('updated_value', $value);
// Delete
$result = Option::delete('test_option');
$this->assertTrue($result);
$this->assertFalse(Option::exists('test_option'));
}
}- Use specific keys - Prefer targeted gets over broad queries
- Batch operations - Group related option updates together
- Cache frequently accessed options - Use Laravel's cache for hot options
- Consider autoload - Set
autoload: falsefor large or rarely accessed options - Use meaningful defaults - Always provide sensible fallback values
The Option module maintains full compatibility with WordPress's native option system:
- All options are stored in
wp_optionstable - Serialization matches WordPress conventions
- Autoload behavior preserved
- Third-party plugins can access options normally
// Old WordPress way
$theme_options = get_option('theme_options', []);
$theme_options['color'] = '#ff0000';
update_option('theme_options', $theme_options);
if (get_option('feature_enabled') === 'yes') {
// Enable feature
}
delete_option('old_setting');// New Pollora way
$themeOptions = Option::get('theme_options', []);
$themeOptions['color'] = '#ff0000';
Option::update('theme_options', $themeOptions);
if (Option::get('feature_enabled') === 'yes') {
// Enable feature
}
Option::delete('old_setting');Keep the === 'yes' comparison: Option::get('feature_enabled', false) tests truthiness, and 'no' is a non-empty string, so an option stored as 'no' would enable the feature.
To store a real flag instead, convert the existing 'yes'/'no' values once, then test the cast value (a stored true reads back as '1', a stored false as ''):
// One-off conversion of the stored 'yes'/'no'
Option::set('feature_enabled', Option::get('feature_enabled') === 'yes');
if ((bool) Option::get('feature_enabled')) {
// Enable feature
}The Pollora Option module provides a more expressive and testable interface while maintaining complete WordPress compatibility.