Plugin Context
The PluginContext is the foundation of WPZylos's isolation strategy. It encapsulates a plugin's identity and provides prefixed access to WordPress resources.
What is Plugin Context?
PluginContext is an immutable object that holds:
- Plugin file path
- Slug (URL-safe identifier)
- Prefix (for hooks, options, transients)
- Text domain (for translations)
- Version
Every WPZylos plugin creates exactly one PluginContext at boot time.
Creating a Plugin Context
<?php
// In your main plugin file
use WPZylos\Framework\Core\PluginContext;
$context = new PluginContext(
__FILE__, // Plugin main file path
'my-awesome-plugin', // Slug
'map_', // Prefix (include trailing underscore)
'my-awesome-plugin', // Text domain
'1.0.0' // Version
);
Accessing Context Values
// File paths
$context->file(); // /path/to/my-plugin/my-plugin.php
$context->path(); // /path/to/my-plugin/
$context->path('src'); // /path/to/my-plugin/src
// URLs
$context->url(); // https://example.com/wp-content/plugins/my-plugin/
$context->url('assets'); // https://example.com/wp-content/plugins/my-plugin/assets
// Identity
$context->slug(); // my-awesome-plugin
$context->prefix(); // map_
$context->textDomain(); // my-awesome-plugin
$context->version(); // 1.0.0
Prefixed Resources
Custom Hooks
// Create prefixed hook names
$hookName = $context->hook('settings_saved');
// Returns: map_settings_saved
do_action($hookName, $settings);
// Other plugins using the same hook name won't conflict
// Plugin B's context would return: pb_settings_saved
Options
// Create prefixed option keys
$optionKey = $context->option('settings');
// Returns: map_settings
update_option($optionKey, $value);
$value = get_option($optionKey, []);
Transients
// Create prefixed transient keys
$transientKey = $context->transientKey('api_cache');
// Returns: map_api_cache
set_transient($transientKey, $data, 3600);
$data = get_transient($transientKey);
User Meta
// Create prefixed meta keys
$metaKey = $context->metaKey('preferences');
// Returns: map_preferences
update_user_meta($userId, $metaKey, $preferences);
The ContextInterface
PluginContext implements ContextInterface, allowing for testing with mock contexts:
<?php
namespace WPZylos\Framework\Core\Contracts;
interface ContextInterface
{
public function file(): string;
public function path(string $relative = ''): string;
public function url(string $relative = ''): string;
public function slug(): string;
public function prefix(): string;
public function textDomain(): string;
public function version(): string;
public function hook(string $name): string;
public function option(string $name): string;
public function transientKey(string $name): string;
public function metaKey(string $name): string;
}
Why We Did It This Way
No Hardcoded Text Domains
Problem: Hardcoded text domains in translation functions make plugins non-portable and cause issues with PHP-Scoper.
Solution: Text domain comes from context, used consistently throughout.
// Bad: Hardcoded text domain
__('Hello', 'my-plugin');
// Good: Dynamic text domain from context
__('Hello', $context->textDomain());
// Good: Or via I18n service
$i18n->translate('Hello');
No Global Constants for Identity
Problem: define('MY_PLUGIN_SLUG', 'my-plugin') pollutes global namespace and can conflict.
Solution: Identity lives in PluginContext, passed via dependency injection.
// Bad: Global constants (can conflict)
define('MY_PLUGIN_PREFIX', 'mp_');
$hook = MY_PLUGIN_PREFIX . 'init';
// Good: Context-based (isolated)
$hook = $context->hook('init');
Immutable Identity
Problem: Mutable plugin identity could lead to inconsistent state.
Solution: PluginContext is immutable. Once created, identity cannot change.
// PluginContext has no setters
$context = new PluginContext(...);
// This is guaranteed to always return the same value
$context->prefix(); // Always 'map_'
Trailing Underscore Convention
Problem: Inconsistent prefix usage leads to ugly keys like mp_settings vs mpsettings.
Solution: Prefix includes trailing underscore. Methods don't add extra underscores.
$context = new PluginContext(
__FILE__,
'my-plugin',
'mp_', // Trailing underscore included
'my-plugin',
'1.0.0'
);
$context->hook('init'); // mp_init (not mp__init)
$context->option('cache'); // mp_cache (not mp__cache)
Using Context in Services
Inject ContextInterface into services that need plugin identity:
<?php
namespace Vendor\MyPlugin\Services;
use WPZylos\Framework\Core\Contracts\ContextInterface;
class CacheService
{
public function __construct(private ContextInterface $context)
{
}
public function get(string $key): mixed
{
return get_transient($this->context->transientKey($key));
}
public function put(string $key, mixed $value, int $ttl = 3600): bool
{
return set_transient($this->context->transientKey($key), $value, $ttl);
}
}
Register in the container:
// In a service provider
$this->container->singleton(ContextInterface::class, fn() => $this->context);
$this->container->singleton(CacheService::class);
Testing with Mock Context
<?php
use PHPUnit\Framework\TestCase;
use WPZylos\Framework\Core\Contracts\ContextInterface;
class CacheServiceTest extends TestCase
{
public function testGetReturnsTransient(): void
{
$context = $this->createMock(ContextInterface::class);
$context->method('transientKey')
->with('data')
->willReturn('test_data');
// Mock get_transient in your test bootstrap
$service = new CacheService($context);
// Test behavior
}
}
Next Steps
- Service Container — How services are resolved
- Hook System — Using context for hooks