Dynamic Placeholders
Placeholders allow you to inject dynamic runtime values into your rules. Instead of hardcoding values, you can reference contextual data using a simple colon-separated syntax that gets resolved during rule execution.
What Are Placeholders?#Copied!
Placeholders are special tokens enclosed in curly braces that get replaced with actual values from the execution context:
1// Static value
2'value' => 'Fixed string'
3
4// Dynamic placeholder
5'value' => '{request.uri}' // Current URL
6'value' => '{request.method}' // HTTP method
7'value' => '{user.login}' // Current user's login
8'value' => '{cookie.session_id}' // Session cookie value
Placeholder Syntax#Copied!
The placeholder syntax uses colon-separated parts to navigate the context hierarchy:
{category:subcategory:key}
category- Top-level context category (request, user, post, cookie, etc.)subcategory- Nested category (optional, can have multiple levels)key- Specific value to retrieve
Examples#Copied!
1'{request.uri}' // $context['request']['uri']
2'{request.method}' // $context['request']['method']
3'{request:headers:host}' // $context['request']['headers']['host']
4'{user.id}' // $context['user']['id']
5'{post.title}' // $context['post']['title']
6'{cookie.session_id}' // $context['cookie']['session_id']
Built-in Placeholder Categories#Copied!
Request Placeholders#Copied!
Access HTTP request data from the PHP package context.
Available Request Placeholders
| Placeholder | Description | Example Value |
|---|---|---|
{request.method} |
HTTP method | GET, POST |
{request.uri} |
Full request URI | /wp-admin/edit.php |
{request.scheme} |
URL scheme | https |
{request.host} |
Host name | example.com |
{request.path} |
URL path | /wp-admin/edit.php |
{request.query} |
Query string | post_type=page |
{request.referer} |
HTTP referer | https://example.com/previous |
{request.user_agent} |
User agent string | Mozilla/5.0... |
{request.ip} |
Client IP address | 192.168.1.1 |
Request Headers
1'{request:headers:content-type}' // Content-Type header
2'{request:headers:authorization}' // Authorization header
3'{request:headers:accept}' // Accept header
4'{request:headers:user-agent}' // User-Agent header
{request:headers:Content-Type} and {request:headers:content-type} are equivalent.Examples
1Rules::register_action('log_request', function($args, Context $context) {
2 $message = $args['value'] ?? '';
3 error_log($message);
4});
5
6Rules::create('log_requests')
7 ->when()->request_url('/api/*')
8 ->then()
9 ->custom('log_request', [
10 'value' => 'API request: {request.method} {request.uri} from {request.ip}'
11 ])
12 ->register();
13
14// Logs: "API request: GET /api/users from 192.168.1.1"
Cookie Placeholders#Copied!
Access cookie values.
1'{cookie.session_id}' // $_COOKIE['session_id']
2'{cookie.user_preference}' // $_COOKIE['user_preference']
3'{cookie.theme}' // $_COOKIE['theme']
Examples
1Rules::register_action('personalize', function($args, Context $context) {
2 $theme = $args['theme'] ?? 'default';
3 apply_theme($theme);
4});
5
6Rules::create('apply_user_theme')
7 ->when()->cookie('theme')
8 ->then()
9 ->custom('personalize', [
10 'theme' => '{cookie.theme}' // Uses cookie value
11 ])
12 ->register();
Parameter Placeholders#Copied!
Access query and form parameters.
1'{param.action}' // $_GET['action'] or $_POST['action']
2'{param.id}' // $_GET['id'] or $_POST['id']
3'{param.page}' // $_GET['page'] or $_POST['page']
Examples
1Rules::register_action('process_action', function($args, Context $context) {
2 $action = $args['action'] ?? '';
3 $id = $args['id'] ?? 0;
4 error_log("Processing action: {$action} for ID: {$id}");
5});
6
7Rules::create('process_request')
8 ->when()
9 ->request_param('action')
10 ->request_param('id')
11 ->then()
12 ->custom('process_action', [
13 'action' => '{param.action}',
14 'id' => '{param.id}'
15 ])
16 ->register();
WordPress Placeholders#Copied!
Access WordPress-specific data (available only when WordPress package is loaded).
User Placeholders
| Placeholder | Description | Example Value |
|---|---|---|
{user.id} |
User ID | 123 |
{user.login} |
User login name | john_doe |
{user.email} |
User email | |
{user.display_name} |
Display name | John Doe |
{user.roles} |
User roles (array) | administrator |
Post Placeholders
| Placeholder | Description | Example Value |
|---|---|---|
{post.id} |
Post ID | 456 |
{post.title} |
Post title | My Blog Post |
{post.type} |
Post type | post, page |
{post.status} |
Post status | publish, draft |
{post.author} |
Author ID | 123 |
Query Variable Placeholders
Access WordPress query variables from $wp_query->query_vars:
| Placeholder | Description | Example Value |
|---|---|---|
{query.post_type} |
Current post type | 'post', 'page' |
{query.paged} |
Current page number | 1, 2, 3 |
{query.s} |
Search query | 'search term' |
{query.m} |
Month/year archive | '202312' |
{query.cat} |
Category ID | '5' |
{query.tag} |
Tag slug | 'news' |
{query.author} |
Author ID or name | '1' |
Examples
1use MilliRulesContext;
2
3Rules::register_action('log_user_action', function($args, Context $context) {
4 error_log($args['message'] ?? '');
5});
6
7Rules::create('log_search')
8 ->when()
9 ->request_url('/search')
10 ->is_search()
11 ->then()
12 ->custom('log_user_action', [
13 'message' => 'User {user.login} searched for "{query.s}" on {request.uri}'
14 ])
15 ->register();
16
17// Logs: "User john_doe searched for "wordpress plugins" on /somewhere"
Using Placeholders in Actions#Copied!
Placeholders are primarily used in action configurations to inject dynamic values.
Basic Usage#Copied!
1Rules::register_action('send_notification', function($args, Context $context) {
2 $to = $args['to'] ?? '';
3 $subject = $args['subject'] ?? '';
4 $message = $args['message'] ?? '';
5
6 // Placeholders already resolved by BaseAction
7 wp_mail($to, $subject, $message);
8});
9
10Rules::create('notify_on_login')
11 ->when()
12 ->is_user_logged_in()
13 ->request_url('/wp-admin/*')
14 ->then()
15 ->custom('send_notification', [
16 'to' => '',
17 'subject' => 'User Login Alert',
18 'message' => 'User {user.login} logged in from {request.ip}'
19 ])
20 ->register();
Multiple Placeholders#Copied!
1Rules::register_action('log_detailed', function($args, Context $context) {
2 error_log($args['message'] ?? '');
3});
4
5Rules::create('detailed_logging')
6 ->when()->request_url('/api/*')
7 ->then()
8 ->custom('log_detailed', [
9 'message' => '{request.method} request to {request.uri} from {request.ip} '
10 . 'at {request.timestamp} by user {user.login}'
11 ])
12 ->register();
Nested Placeholders#Copied!
1Rules::register_action('set_header', function($args, Context $context) {
2 $name = $args['name'] ?? '';
3 $value = $args['value'] ?? '';
4
5 if (!headers_sent()) {
6 header("{$name}: {$value}");
7 }
8});
9
10Rules::create('custom_header')
11 ->when()->request_url('/api/*')
12 ->then()
13 ->custom('set_header', [
14 'name' => 'X-Request-ID',
15 'value' => '{request:headers:x-request-id}' // Forward header value
16 ])
17 ->register();
Implementing Placeholder Resolution#Copied!
In BaseAction Subclasses#Copied!
Actions extending BaseAction automatically get placeholder resolution:
1namespace MyPluginActions;
2
3use MilliRulesActionsBaseAction;
4
5class CustomNotificationAction extends BaseAction {
6 public function execute(array $context): void {
7 // Resolve placeholders in config values
8 $message = $this->resolve_value($this->config['message'] ?? '');
9 $recipient = $this->resolve_value($this->config['to'] ?? '');
10
11 // Use resolved values
12 wp_mail($recipient, 'Notification', $message);
13 }
14
15 public function get_type(): string {
16 return 'custom_notification';
17 }
18}
In Callback Actions#Copied!
Callback actions need to manually resolve placeholders:
1use MilliRulesPlaceholderResolver;
2
3Rules::register_action('manual_resolution', function($args, Context $context) {
4 $resolver = new PlaceholderResolver($context);
5
6 // Resolve individual value
7 $message = $resolver->resolve($args['message'] ?? '');
8
9 // Use resolved value
10 error_log($message);
11});
12
13Rules::create('use_manual_resolution')
14 ->when()->request_url('/test')
15 ->then()
16 ->custom('manual_resolution', [
17 'message' => 'Testing from {request.ip}'
18 ])
19 ->register();
Creating Custom Placeholder Resolvers#Copied!
Register custom placeholder categories for your own data sources.
Registering Custom Resolvers#Copied!
1use MilliRulesRules;
2
3// Register custom placeholder category
4Rules::register_placeholder('custom', function($context, $parts) {
5 // $parts[0] is the first key after 'custom:'
6 // $parts[1] is the second key, etc.
7
8 switch ($parts[0] ?? '') {
9 case 'site_name':
10 return get_bloginfo('name');
11
12 case 'site_url':
13 return home_url();
14
15 case 'current_time':
16 return date('Y-m-d H:i:s');
17
18 case 'option':
19 return get_option($parts[1] ?? '');
20
21 default:
22 return '';
23 }
24});
Using Custom Placeholders#Copied!
1Rules::register_action('log_custom', function($args, Context $context) {
2 error_log($args['message'] ?? '');
3});
4
5Rules::create('use_custom_placeholders')
6 ->when()->request_url('/api/*')
7 ->then()
8 ->custom('log_custom', [
9 'message' => 'Request to {custom.site_name} at {custom.current_time}'
10 ])
11 ->register();
12
13// Access WordPress options
14Rules::create('use_option_placeholder')
15 ->when()->request_url('/test')
16 ->then()
17 ->custom('log_custom', [
18 'message' => 'Site tagline: {custom:option:blogdescription}'
19 ])
20 ->register();
Complex Custom Resolvers#Copied!
1Rules::register_placeholder('env', function($context, $parts) {
2 $key = $parts[0] ?? '';
3
4 // Environment variables
5 if ($key === 'var') {
6 return getenv($parts[1] ?? '');
7 }
8
9 // Server information
10 if ($key === 'server') {
11 return $_SERVER[strtoupper($parts[1] ?? '')] ?? '';
12 }
13
14 // Custom environment data
15 $env_data = [
16 'name' => WP_ENVIRONMENT_TYPE ?? 'production',
17 'debug' => WP_DEBUG ?? false,
18 'version' => get_bloginfo('version'),
19 ];
20
21 return $env_data[$key] ?? '';
22});
23
24// Usage:
25// {env.name} → 'production'
26// {env.debug} → true/false
27// {env:var:API_KEY} → getenv('API_KEY')
28// {env:server:http_host} → $_SERVER['HTTP_HOST']
Advanced Placeholder Patterns#Copied!
Conditional Placeholders#Copied!
Use placeholders with fallback values:
1Rules::register_action('log_with_fallback', function($args, Context $context) {
2 $resolver = new PlaceholderResolver($context);
3
4 // Resolve with fallback
5 $user = $resolver->resolve($args['user'] ?? '') ?: 'guest';
6 $message = "User: {$user}";
7
8 error_log($message);
9});
10
11Rules::create('log_with_defaults')
12 ->when()->request_url('*')
13 ->then()
14 ->custom('log_with_fallback', [
15 'user' => '{user.login}' // Falls back to 'guest' if empty
16 ])
17 ->register();
Placeholder Transformation#Copied!
Transform placeholder values:
1Rules::register_action('transform_placeholder', function($args, Context $context) {
2 $resolver = new PlaceholderResolver($context);
3 $value = $resolver->resolve($args['value'] ?? '');
4
5 // Transform resolved value
6 $transformed = strtoupper($value);
7 $transformed = sanitize_text_field($transformed);
8
9 error_log($transformed);
10});
Array Placeholders#Copied!
Access array values:
1// Access first role
2'{user:roles:0}' // First role
3
4// Access header values
5'{request:headers:accept}' // Accept header
Object Property Access#Copied!
Access public properties and magic properties on objects using dot notation:
1// Access public object properties
2'{hook:args:0:ID}' // WP_Post object's ID property
3'{hook:args:0:post_title}' // WP_Post object's post_title property
4'{hook:args:0:post_author}' // WP_Post object's post_author property
5
6// Access magic properties (via __get() method)
7'{hook:args:2:permalink}' // WP_Post object's permalink (magic property)
8
9// Mixed array and object access
10'{hook:args:2:ID}' // Third argument (index 2) → object's ID property
WordPress Hook Examples
WordPress hooks often pass objects as arguments. You can now access their properties directly:
1use MilliRulesContext;
2
3// Example: transition_post_status hook passes (new_status, old_status, $post)
4Rules::register_action('clear_post_cache', function($args, Context $context) {
5 $url = $args['url'] ?? '';
6 // Clear cache for the URL
7 wp_cache_delete($url);
8});
9
10Rules::create('clear_on_publish')
11 ->when()
12 ->hook_is('transition_post_status')
13 ->hook_arg(0, '==', 'publish') // New status is 'publish'
14 ->then()
15 ->custom('clear_post_cache', [
16 'url' => '{hook:args:2:permalink}' // Access WP_Post's permalink property
17 ])
18 ->register();
Nested Objects and Arrays
Combine array and object access for complex data structures:
1// WordPress comment object in an array
2'{comments:0:comment_author}' // First comment's author
3'{comments:0:comment_content}' // First comment's content
4
5// API response with nested objects
6'{api:response:data:items:0:id}' // First item's ID from API response
7
8// Custom data structures
9'{data:user:profile:settings}' // Access nested object properties
How It Works
When resolving placeholders, MilliRules automatically detects whether each segment is:
- Array access: Uses
isset()and$array[$key] - Object property access: Checks
property_exists()for public properties, or__get()for magic properties
This allows seamless access to mixed array/object structures without special syntax.
Placeholder Resolution Flow#Copied!
Understanding how placeholders are resolved:
1. Action configuration contains placeholder: "{request.uri}"
↓
2. BaseAction::resolve_value() detects placeholder
↓
3. PlaceholderResolver splits by colons: ['request', 'uri']
↓
4. Looks up category 'request' in registered resolvers
↓
5. PHP package resolver handles 'request' category
↓
6. Returns $context['request']['uri']
↓
7. Placeholder replaced with actual value: "/api/users"
↓
8. Action executes with resolved value
Best Practices#Copied!
1. Use Descriptive Placeholder Names#Copied!
1// ✅ Good - clear what data is being used
2'message' => 'User {user.login} accessed {request.uri}'
3
4// ❌ Bad - unclear placeholders
5'message' => 'User {u} accessed {r}'
2. Provide Fallback Values#Copied!
1Rules::register_action('safe_action', function($args, Context $context) {
2 $resolver = new PlaceholderResolver($context);
3
4 // Resolve with fallback
5 $user = $resolver->resolve($args['user'] ?? '') ?: 'Unknown User';
6 $ip = $resolver->resolve($args['ip'] ?? '') ?: '0.0.0.0';
7
8 error_log("User: {$user}, IP: {$ip}");
9});
3. Validate Resolved Values#Copied!
1Rules::register_action('validated_action', function($args, Context $context) {
2 $resolver = new PlaceholderResolver($context);
3 $email = $resolver->resolve($args['email'] ?? '');
4
5 // Validate resolved value
6 if (!is_email($email)) {
7 error_log('Invalid email from placeholder');
8 return;
9 }
10
11 // Use validated value
12 wp_mail($email, 'Subject', 'Message');
13});
4. Document Custom Placeholders#Copied!
1/**
2 * Custom Placeholder: {payment.gateway}
3 * Returns the active payment gateway name
4 *
5 * Custom Placeholder: {payment:status:order_id}
6 * Returns the payment status for a given order ID
7 *
8 * Example: {payment:status:123} → 'completed'
9 */
10Rules::register_placeholder('payment', function($context, $parts) {
11 // Implementation...
12});
Common Pitfalls#Copied!
1. Missing Context Data#Copied!
1// ❌ Wrong - WordPress placeholders in PHP-only context
2Rules::create('php_rule', 'php')
3 ->when()->request_url('/api/*')
4 ->then()
5 ->custom('action', [
6 'value' => '{user.login}' // Empty! WordPress not available
7 ])
8 ->register();
9
10// ✅ Correct - check context availability
11Rules::register_action('safe_wp_action', function($args, Context $context) {
12 if (!isset($context['wp'])) {
13 error_log('WordPress context not available');
14 return;
15 }
16
17 $resolver = new PlaceholderResolver($context);
18 $user = $resolver->resolve('{user.login}');
19 // ...
20});
2. Incorrect Placeholder Syntax#Copied!
1// ❌ Wrong - missing braces
2'value' => 'request:uri'
3
4// ❌ Wrong - incorrect separator
5'value' => '{request.uri}'
6
7// ✅ Correct - proper syntax
8'value' => '{request.uri}'
3. Case Sensitivity#Copied!
1// Context keys are case-sensitive
2// ✅ Correct
3'{request.uri}'
4
5// ❌ Wrong
6'{Request:URI}'
7'{REQUEST:URI}'
Next Steps#Copied!
- Understanding the Package System - Learn about package architecture
- Creating Custom Actions - Implement actions with placeholders
- Advanced Patterns - Advanced placeholder techniques
- Real-World Examples - See placeholders in action
Ready to extend MilliRules? Continue to Creating Custom Packages to learn how to add your own context data and placeholders.