Built-in Actions Reference
Actions are the "then" part of your rules—they define what happens when conditions are met. Unlike conditions, MilliRules' action system is primarily designed around custom actions that you define for your specific needs.
Understanding the Action System#Copied!
MilliRules provides a flexible action framework that allows you to:
- Register custom callback actions for quick, inline functionality
- Create reusable action classes for complex operations
- Execute actions sequentially in the order they're defined
- Access full context within actions for data-driven decisions
Action Execution Flow#Copied!
When a rule's conditions match:
1. Rule conditions evaluate to true
↓
2. Rule engine triggers action execution
↓
3. For each action in the rule:
a. Instantiate action with config and context
b. Execute action's execute() method
c. Continue to next action
↓
4. Return execution statistics
Actions execute immediately and sequentially. There's no action queue or deferred execution.
Action Types#Copied!
1. Custom Callback Actions#Copied!
The simplest way to create actions is using callback functions.
Registering Callback Actions
1use MilliRulesRules;
2use MilliRulesContext;
3
4// Simple action
5Rules::register_action('log_message', function($args, Context $context) {
6 $message = $args['message'] ?? $args[0] ?? 'No message';
7 error_log('MilliRules: ' . $message);
8});
9
10// Action with context access
11Rules::register_action('log_user_action', function($args, Context $context) {
12 $action = $args['action'] ?? 'accessed';
13 $user = $context->get('user.login', 'guest');
14 $url = $context->get('request.uri', 'unknown');
15 error_log("User {$user} {$action} {$url}");
16});
Using Callback Actions in Rules
1Rules::create('log_admin_access')
2 ->when()
3 ->request_url('/wp-admin/*')
4 ->is_user_logged_in()
5 ->then()
6 ->custom('log_message', ['value' => 'Admin area accessed'])
7 ->custom('log_user_action')
8 ->register();
Callback Parameters:
$context(array): Full execution context with request, WP data, etc.$config(array): Action configuration passed from the rule
2. Class-Based Actions#Copied!
For complex operations, create action classes implementing ActionInterface.
Action Interface
1namespace MilliRulesInterfaces;
2
3interface ActionInterface {
4 public function execute(Context $context): void;
5 public function get_type(): string;
6}
Creating an Action Class
1namespace MyPluginActions;
2
3use MilliRulesInterfacesActionInterface;
4
5class SendEmailAction implements ActionInterface {
6 private $config;
7 private $context;
8
9 public function __construct(array $config, Context $context) {
10 $this->config = $config;
11 $this->context = $context;
12 }
13
14 public function execute(Context $context): void {
15 $to = $this->config['to'] ?? '';
16 $subject = $this->config['subject'] ?? 'Notification';
17 $message = $this->config['message'] ?? '';
18
19 // Access context for dynamic data
20 $user_email = $context['wp']['user']['email'] ?? $to;
21
22 wp_mail($to, $subject, $message);
23 }
24
25 public function get_type(): string {
26 return 'send_email';
27 }
28}
Registering the Namespace
1use MilliRulesRuleEngine;
2
3// Register action namespace so MilliRules can find your action classes
4RuleEngine::register_namespace('Actions', 'MyPluginActions');
Using Class-Based Actions
1Rules::create('user_registration_notification')
2 ->when()
3 ->request_url('/wp-admin/user-new.php')
4 ->request_param('action', 'createuser')
5 ->then()
6 ->custom('send_email', [
7 'to' => '',
8 'subject' => 'New User Registration',
9 'message' => 'A new user has registered.'
10 ])
11 ->register();
3. BaseAction Helper Class#Copied!
MilliRules provides a BaseAction abstract class that includes placeholder resolution.
1namespace MyPluginActions;
2
3use MilliRulesActionsBaseAction;
4
5class CustomAction extends BaseAction {
6 public function execute(Context $context): void {
7 // Resolve placeholders in config values
8 $message = $this->resolve_value($this->config['value'] ?? '');
9
10 // Use resolved value
11 error_log($message);
12 }
13
14 public function get_type(): string {
15 return 'custom_action';
16 }
17}
Using placeholder resolution:
1Rules::create('dynamic_logging')
2 ->when()
3 ->request_url('/api/*')
4 ->then()
5 ->custom('custom_action', [
6 'value' => 'API request to {request.uri} from {request.ip}'
7 ])
8 ->register();
9
10// Logs: "API request to /api/users from 192.168.1.1"
See Dynamic Placeholders for complete placeholder syntax.
Common Action Patterns#Copied!
1. Logging Actions#Copied!
1// Simple logging
2Rules::register_action('log', function($args, Context $context) {
3 error_log($args['value'] ?? '');
4});
5
6// Structured logging
7Rules::register_action('log_structured', function($args, Context $context) {
8 $data = [
9 'timestamp' => time(),
10 'user' => $context->get('user.login', 'guest') ?? 'guest',
11 'ip' => $context['request']['ip'] ?? 'unknown',
12 'message' => $args['value'] ?? '',
13 ];
14 error_log(json_encode($data));
15});
16
17// Usage
18Rules::create('log_actions')
19 ->when()->request_url('/important/*')
20 ->then()
21 ->custom('log', ['value' => 'Important URL accessed'])
22 ->custom('log_structured', ['value' => 'Security alert'])
23 ->register();
2. Redirect Actions#Copied!
1Rules::register_action('redirect', function($args, Context $context) {
2 $url = $args['url'] ?? home_url();
3 $status = $args['status'] ?? 302;
4
5 if (!headers_sent()) {
6 wp_redirect($url, $status);
7 exit;
8 }
9});
10
11// Usage
12Rules::create('redirect_logged_out_users')
13 ->when()
14 ->request_url('/members/*')
15 ->is_user_logged_in(false)
16 ->then()
17 ->custom('redirect', [
18 'url' => wp_login_url(),
19 'status' => 302
20 ])
21 ->register();
exit.3. Cache Control Actions#Copied!
1Rules::register_action('set_cache_headers', function($args, Context $context) {
2 $duration = $args['duration'] ?? 3600;
3
4 if (!headers_sent()) {
5 header("Cache-Control: public, max-age={$duration}");
6 header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $duration) . ' GMT');
7 }
8});
9
10// Usage
11Rules::create('cache_api_responses')
12 ->when()
13 ->request_url('/api/*')
14 ->request_method(['GET', 'HEAD'], 'IN')
15 ->then()
16 ->custom('set_cache_headers', ['duration' => 7200])
17 ->register();
4. Database Operations#Copied!
1Rules::register_action('log_to_database', function($args, Context $context) {
2 global $wpdb;
3
4 $table = $wpdb->prefix . 'access_log';
5 $wpdb->insert($table, [
6 'user_id' => $context->get('user.id', 0) ?? 0,
7 'url' => $context->get('request.uri', '') ?? '',
8 'timestamp' => current_time('mysql'),
9 ]);
10});
11
12// Usage
13Rules::create('track_premium_access')
14 ->when()
15 ->request_url('/premium/*')
16 ->is_user_logged_in()
17 ->then()
18 ->custom('log_to_database')
19 ->register();
5. WordPress Hook Triggers#Copied!
1// Trigger WordPress actions
2Rules::register_action('do_action', function($args, Context $context) {
3 $hook = $args['value'] ?? '';
4 $args = $args['args'] ?? [];
5
6 if ($hook) {
7 do_action($hook, ...$args);
8 }
9});
10
11// Trigger WordPress filters
12Rules::register_action('apply_filters', function($args, Context $context) {
13 $hook = $args['value'] ?? '';
14 $value = $args['filter_value'] ?? '';
15 $args = $args['args'] ?? [];
16
17 if ($hook) {
18 return apply_filters($hook, $value, ...$args);
19 }
20});
21
22// Usage
23Rules::create('trigger_custom_hooks')
24 ->when()->request_url('/checkout/*')
25 ->then()
26 ->custom('do_action', [
27 'value' => 'my_checkout_started',
28 'args' => ['checkout_page']
29 ])
30 ->register();
6. Content Modification#Copied!
1Rules::register_action('modify_content', function($args, Context $context) {
2 add_filter('the_content', function($content) use ($config) {
3 $prepend = $args['prepend'] ?? '';
4 $append = $args['append'] ?? '';
5
6 return $prepend . $content . $append;
7 }, $args['priority'] ?? 10);
8});
9
10// Usage
11Rules::create('add_disclaimer')
12 ->when()
13 ->is_singular('post')
14 ->post_type('product')
15 ->then()
16 ->custom('modify_content', [
17 'prepend' => '<div class="disclaimer">Product information may vary.</div>',
18 'priority' => 10
19 ])
20 ->register();
7. Conditional Execution#Copied!
1Rules::register_action('execute_if', function($args, Context $context) {
2 $condition = $args['condition'] ?? null;
3 $callback = $args['callback'] ?? null;
4
5 if (is_callable($condition) && is_callable($callback)) {
6 if ($condition($context)) {
7 $callback($context);
8 }
9 }
10});
11
12// Usage
13Rules::create('conditional_action')
14 ->when()->request_url('/api/*')
15 ->then()
16 ->custom('execute_if', [
17 'condition' => function(Context $context) {
18 return date('H') >= 9 && date('H') <= 17; // Business hours
19 },
20 'callback' => function(Context $context) {
21 error_log('API accessed during business hours');
22 }
23 ])
24 ->register();
Action Configuration#Copied!
Actions receive configuration through the $config array:
1Rules::register_action('flexible_action', function($args, Context $context) {
2 // Common configuration keys
3 $value = $args['value'] ?? ''; // Primary value
4 $enabled = $args['enabled'] ?? true; // Enable flag
5 $priority = $args['priority'] ?? 10; // Priority/order
6 $options = $args['options'] ?? []; // Additional options
7
8 // Custom configuration
9 $custom_param = $args['custom_param'] ?? 'default';
10});
11
12// Usage with full configuration
13Rules::create('configured_action')
14 ->when()->request_url('/test')
15 ->then()
16 ->custom('flexible_action', [
17 'value' => 'test value',
18 'enabled' => true,
19 'priority' => 20,
20 'options' => ['key' => 'value'],
21 'custom_param' => 'custom value'
22 ])
23 ->register();
'value'for the primary action value'enabled'for enable/disable flags'priority'for ordering within the action'options'for nested configuration
Accessing Context in Actions#Copied!
The context provides access to all available data:
1Rules::register_action('context_aware_action', function($args, Context $context) {
2 // Request data
3 $url = $context->get('request.uri', '') ?? '';
4 $method = $context->get('request.method', '') ?? '';
5 $ip = $context['request']['ip'] ?? '';
6
7 // Cookies
8 $session = $context['request']['cookies']['session_id'] ?? '';
9
10 // WordPress data (if available)
11 $user_id = $context->get('user.id', 0) ?? 0;
12 $user_login = $context->get('user.login', 'guest') ?? 'guest';
13 $post_id = $context->get('post.id', 0) ?? 0;
14
15 // Query flags
16 $is_singular = $context['wp']['query']['is_singular'] ?? false;
17 $is_admin = $context['wp']['query']['is_admin'] ?? false;
18
19 // Use context data
20 error_log("User {$user_login} accessed {$url} from {$ip}");
21});
Full context structure:
1[
2 'request' => [
3 'method' => 'GET',
4 'uri' => '/path',
5 'scheme' => 'https',
6 'host' => 'example.com',
7 'path' => '/path',
8 'query' => 'key=value',
9 'referer' => 'https://example.com',
10 'user_agent' => 'Mozilla/5.0...',
11 'headers' => [...],
12 'ip' => '192.168.1.1',
13 'cookies' => [...],
14 'params' => [...],
15 ],
16 'wp' => [ // WordPress only
17 'post' => [...],
18 'user' => [...],
19 'query' => [...],
20 'constants' => [...],
21 ],
22]
Error Handling in Actions#Copied!
MilliRules catches action exceptions and continues execution:
1Rules::register_action('safe_action', function($args, Context $context) {
2 try {
3 // Risky operation
4 $result = risky_operation();
5
6 if (!$result) {
7 throw new Exception('Operation failed');
8 }
9 } catch (Exception $e) {
10 error_log('Action error: ' . $e->getMessage());
11 // Execution continues to next action
12 }
13});
Multiple Actions in Sequence#Copied!
Actions execute sequentially in definition order:
1Rules::create('multi_step_process')
2 ->when()->request_url('/process')
3 ->then()
4 ->custom('log', ['value' => '1. Starting process'])
5 ->custom('validate_data')
6 ->custom('log', ['value' => '2. Data validated'])
7 ->custom('process_data')
8 ->custom('log', ['value' => '3. Data processed'])
9 ->custom('send_response')
10 ->custom('log', ['value' => '4. Response sent'])
11 ->register();
Execution flow:
- Log: "1. Starting process"
- Validate data
- Log: "2. Data validated"
- Process data
- Log: "3. Data processed"
- Send response
- Log: "4. Response sent"
Best Practices#Copied!
1. Keep Actions Focused#Copied!
1// ✅ Good - single responsibility
2Rules::register_action('log_access', function($args, Context $context) {
3 error_log('Access logged');
4});
5
6Rules::register_action('update_counter', function($args, Context $context) {
7 update_option('access_count', get_option('access_count', 0) + 1);
8});
9
10// ❌ Bad - multiple responsibilities
11Rules::register_action('do_everything', function($args, Context $context) {
12 error_log('Access logged');
13 update_option('access_count', get_option('access_count', 0) + 1);
14 send_email('', 'Access', 'Someone accessed');
15 update_database();
16 // Too much in one action!
17});
2. Use Descriptive Action Names#Copied!
1// ✅ Good
2Rules::register_action('send_admin_notification_email', ...);
3Rules::register_action('log_security_event', ...);
4Rules::register_action('update_user_last_login_timestamp', ...);
5
6// ❌ Bad
7Rules::register_action('send', ...);
8Rules::register_action('log', ...);
9Rules::register_action('update', ...);
3. Validate Configuration#Copied!
1Rules::register_action('safe_action', function($args, Context $context) {
2 // Validate required configuration
3 if (empty($args['required_value'])) {
4 error_log('Action error: missing required_value');
5 return;
6 }
7
8 // Validate data types
9 $count = absint($args['count'] ?? 0);
10 $enabled = (bool) ($args['enabled'] ?? true);
11
12 // Proceed with validated data
13 // ...
14});
4. Check Prerequisites#Copied!
1Rules::register_action('wordpress_dependent', function($args, Context $context) {
2 // Check if WordPress functions are available
3 if (!function_exists('wp_mail')) {
4 error_log('WordPress not available');
5 return;
6 }
7
8 wp_mail($args['to'], $args['subject'], $args['message']);
9});
5. Use Constants for Configuration#Copied!
1// Define action configuration constants
2define('DEFAULT_EMAIL_RECIPIENT', '');
3define('DEFAULT_LOG_LEVEL', 'info');
4
5Rules::register_action('send_notification', function($args, Context $context) {
6 $to = $args['to'] ?? DEFAULT_EMAIL_RECIPIENT;
7 $level = $args['level'] ?? DEFAULT_LOG_LEVEL;
8
9 // Use constants for consistent configuration
10});
Common Pitfalls#Copied!
1. Modifying Context#Copied!
1// ❌ Wrong - context modifications don't persist
2Rules::register_action('modify_context', function($args, Context $context) {
3 $context['custom_value'] = 'modified';
4 // This change is lost after the action completes
5});
6
7// ✅ Correct - use external state or return values
8Rules::register_action('store_value', function($args, Context $context) {
9 update_option('custom_value', 'modified');
10});
$context within an action do not persist to subsequent actions.2. Assuming Action Order Across Rules#Copied!
1// ❌ Wrong - different rules, no guaranteed order
2Rules::create('rule1')->order(10)->when()->then()->custom('action1')->register();
3Rules::create('rule2')->order(20)->when()->then()->custom('action2')->register();
4// action1 and action2 only execute if their respective rule conditions match
5
6// ✅ Correct - actions in same rule execute in order
7Rules::create('rule')->order(10)
8 ->when()->request_url('*')
9 ->then()
10 ->custom('action1') // Executes first
11 ->custom('action2') // Executes second
12 ->register();
3. Using Exit/Die in Actions#Copied!
1// ❌ Wrong - prevents subsequent actions
2Rules::register_action('early_exit', function($args, Context $context) {
3 echo 'Response';
4 exit; // Stops all subsequent actions!
5});
6
7// ✅ Correct - use flags or return early
8Rules::register_action('conditional_processing', function($args, Context $context) {
9 if (!some_condition()) {
10 return; // Skip this action, continue to next
11 }
12
13 // Process normally
14});
Next Steps#Copied!
- Operators and Pattern Matching - Master condition operators
- Dynamic Placeholders - Use dynamic values in actions
- Creating Custom Actions - Advanced action development
- Real-World Examples - See actions in complete examples
Ready to create custom actions? Continue to Creating Custom Actions for advanced techniques and patterns.