Architecture Overview
WPZylos is designed to solve one critical problem: enabling multiple plugins built with the same framework to run simultaneously without conflicts.
Core Architecture Principles
- Isolated Containers — Each plugin has its own PSR-11 container
- Context-Based Prefixing — Hooks, options, and transients are automatically scoped
- No Global State — Framework does not use global variables or singletons
- WordPress-First — Builds on WordPress APIs, doesn't replace them
- PHP-Scoper Ready — Designed for namespace isolation in distribution
High-Level Architecture
graph TB
subgraph Plugin A
PA[plugin-a.php] --> CA[PluginContext A]
CA --> CONTA[Container A]
CONTA --> SA[Services A]
end
subgraph Plugin B
PB[plugin-b.php] --> CB[PluginContext B]
CB --> CONTB[Container B]
CONTB --> SB[Services B]
end
subgraph WordPress
WP[WordPress Core]
HOOKS[Hook System]
DB[(Database)]
end
SA --> |prefixed hooks| HOOKS
SB --> |prefixed hooks| HOOKS
SA --> |prefixed options| DB
SB --> |prefixed options| DB
HOOKS --> WP
DB --> WP
Package Layers
graph TD
subgraph Application Layer
SCAFFOLD[wpzylos-scaffold]
WPCLI[wpzylos-wp-cli]
CLIDEV[wpzylos-cli-devtool]
end
subgraph Feature Layer
HTTP[wpzylos-http]
ROUTING[wpzylos-routing]
VIEWS[wpzylos-views]
VALIDATION[wpzylos-validation]
DATABASE[wpzylos-database]
MIGRATIONS[wpzylos-migrations]
I18N[wpzylos-i18n]
SECURITY[wpzylos-security]
end
subgraph Foundation Layer
CORE[wpzylos-core]
CONTAINER[wpzylos-container]
HOOKS[wpzylos-hooks]
CONFIG[wpzylos-config]
CLICORE[wpzylos-cli-core]
end
SCAFFOLD --> CORE
WPCLI --> CLICORE
CLIDEV --> CLICORE
HTTP --> CORE
ROUTING --> HTTP
VIEWS --> CORE
VALIDATION --> CORE
DATABASE --> CORE
MIGRATIONS --> DATABASE
I18N --> CORE
SECURITY --> CORE
CORE --> CONTAINER
CORE --> HOOKS
CORE --> CONFIG
CLICORE --> CORE
Boot Flow
sequenceDiagram
participant WP as WordPress
participant Plugin as plugin.php
participant Context as PluginContext
participant Container as Container
participant App as Application
participant Providers as ServiceProviders
WP->>Plugin: Load plugin file
Plugin->>Plugin: require autoload.php
Plugin->>Context: Create PluginContext
Plugin->>Container: Create Container
Plugin->>App: new Application(context, container)
WP->>WP: plugins_loaded
App->>Providers: Register all providers
Providers->>Container: Bind services
App->>Providers: Boot all providers
Providers->>WP: Register hooks
Why We Did It This Way
Isolated Containers (Not Singletons)
Problem: Traditional frameworks use singletons for the container. When two plugins share the same framework, they share the same container, causing service conflicts.
Solution: Each plugin instantiates its own Container. Services are scoped to their plugin.
// Plugin A
$containerA = new Container();
$containerA->bind(Logger::class, fn() => new Logger('plugin-a'));
// Plugin B (completely independent)
$containerB = new Container();
$containerB->bind(Logger::class, fn() => new Logger('plugin-b'));
Context-Based Prefixing
Problem: Two plugins using add_action('my_custom_hook', ...) would fire each other's hooks.
Solution: The PluginContext provides prefixed hook names for custom hooks while allowing direct access to WordPress core hooks.
// Custom hooks are prefixed
$context->hook('settings_saved'); // Returns: mp_settings_saved
// WordPress hooks remain unprefixed
$hooks->wpAction('init', fn() => ...); // Hooks into 'init'
Lightweight Activation
Problem: Heavy boot logic in activation hooks causes timeouts and database errors.
Solution: Activation hooks only perform essential database operations. Full boot happens on subsequent requests.
register_activation_hook(__FILE__, function () {
// Good: Only essential operations
update_option('mp_version', '1.0.0');
// Bad: Not here: service providers, routes, views
});
WordPress-First Philosophy
Problem: Some frameworks try to abstract WordPress away, causing integration headaches.
Solution: WPZylos enhances WordPress APIs rather than replacing them. All WordPress functions remain available.
// WPZylos wraps WordPress, doesn't replace it
$security->verifyNonce($nonce, 'action');
// Internally calls: wp_verify_nonce($nonce, 'mp_action')
// Direct WordPress access is always available
wp_nonce_field('my_action', 'my_nonce');
Component Responsibilities
| Component | Responsibility |
|---|---|
PluginContext | Plugin identity (slug, prefix, text domain) |
Container | Service registration and resolution |
Application | Boot orchestration and lifecycle |
ServiceProvider | Service registration in container |
HookManager | WordPress hook integration |
ConfigRepository | Configuration access |
File Structure
my-plugin/
+-- my-plugin.php # Entry point
+-- composer.json
+-- bootstrap/
| +-- app.php # Application factory
+-- config/
| +-- app.php # Application config
| +-- services.php # Provider config
+-- src/
| +-- Providers/ # Service providers
| +-- Http/
| | +-- Controllers/ # Request handlers
| | +-- Middleware/ # Request middleware
| +-- Models/ # Data models
| +-- Services/ # Business logic
+-- resources/
| +-- views/ # PHP templates
+-- routes/
| +-- admin.php # Admin routes
| +-- ajax.php # AJAX routes
+-- tests/ # PHPUnit tests
Next Steps
- Plugin Context — Deep dive into identity isolation
- Service Container — Dependency injection
- Hook System — WordPress and custom hooks