Operators and Pattern Matching
Operators are the backbone of condition evaluation in MilliRules. They determine how actual values are compared against expected values. This comprehensive guide covers all 13 operators with examples and best practices.
Operator Overview#Copied!
MilliRules supports 13 operators organized into five categories:
| Category | Operators | Description |
|---|---|---|
| Equality | =, != |
Exact matching and inequality |
| Comparison | >, >=, <, <= |
Numeric comparisons |
| Pattern | LIKE, NOT LIKE, REGEXP |
Wildcard and regex matching |
| Membership | IN, NOT IN |
Array membership testing |
| Existence | EXISTS, NOT EXISTS |
Value existence checking |
| Boolean | IS, IS NOT |
Boolean value comparison |
'LIKE', 'like', and 'Like' are treated identically. MilliRules normalizes operators to uppercase internally.Auto-Detection#Copied!
MilliRules intelligently detects the appropriate operator based on the value type:
flowchart TD
Value["Value Type"] --> String{"String?"}
Value --> Array{"Array?"}
Value --> Bool{"Boolean?"}
Value --> Null{"Null?"}
String -->|"has * or ?"| LIKE["LIKE"]
String -->|"starts with /"| REGEXP["REGEXP"]
String -->|"plain"| EQUALS["="]
Array --> IN["IN"]
Bool --> IS["IS"]
Null --> EXISTS["EXISTS"]
1// String value → '=' operator
2->request_method('GET')
3
4// Array value → 'IN' operator
5->request_method(['GET', 'HEAD'])
6
7// Boolean value → 'IS' operator
8->constant('WP_DEBUG', true)
9
10// Null value → 'EXISTS' operator
11->cookie('session_id')
12
13// String with wildcards (* or ?) → 'LIKE' operator
14->request_url('/admin/*')
15
16// String starting with '/' → 'REGEXP' operator
17->request_url('/^\/api\//i')
Equality Operators#Copied!
= (Equals)#Copied!
Exact match comparison. This is the default operator.
Syntax
1->condition($value) // Auto-detected
2->condition($value, '=') // Explicit
Examples
String comparison:
1Rules::create('exact_match')
2 ->when()
3 ->request_method('POST') // Exact match
4 ->request_url('/api/users') // Exact URL
5 ->cookie('theme', 'dark') // Exact value
6 ->then()->custom('action')
7 ->register();
Numeric comparison:
1Rules::create('exact_number')
2 ->when()
3 ->request_param('page', '1', '=') // Page equals 1
4 ->constant('PHP_VERSION', '8.0', '=') // Version equals 8.0
5 ->then()->custom('action')
6 ->register();
Boolean comparison (prefer IS operator):
1// Works but not recommended
2->constant('WP_DEBUG', true, '=')
3
4// Better - use IS operator
5->constant('WP_DEBUG', true, 'IS')
'POST' does not equal 'post'. Use exact casing or normalize values before comparison.!= (Not Equals)#Copied!
Inequality comparison. Matches when values are not equal.
Syntax
1->condition($value, '!=')
Examples
Exclude values:
1Rules::create('not_post_method')
2 ->when()
3 ->request_method('POST', '!=') // Not POST
4 ->request_url('/wp-login.php', '!=') // Not login page
5 ->then()->custom('action')
6 ->register();
Exclude status:
1Rules::create('not_debug_mode')
2 ->when()
3 ->constant('WP_DEBUG', true, '!=') // Debug not enabled
4 ->then()->custom('production_action')
5 ->register();
Comparison Operators#Copied!
Comparison operators perform numeric comparisons. Non-numeric strings are cast to numbers (often 0).
> (Greater Than)#Copied!
Matches when actual value is greater than expected value.
Syntax
1->condition($value, '>')
Examples
1Rules::create('pagination')
2 ->when()
3 ->request_param('page', '1', '>') // Page > 1
4 ->request_param('limit', '10', '>') // Limit > 10
5 ->then()->custom('paginated_action')
6 ->register();
>= (Greater Than or Equal)#Copied!
Matches when actual value is greater than or equal to expected value.
Syntax
1->condition($value, '>=')
Examples
1Rules::create('php_version_check')
2 ->when()
3 ->constant('PHP_VERSION', '7.4', '>=') // PHP 7.4+
4 ->then()->custom('use_modern_features')
5 ->register();
< (Less Than)#Copied!
Matches when actual value is less than expected value.
Syntax
1->condition($value, '<')
Examples
1Rules::create('early_pagination')
2 ->when()
3 ->request_param('page', '5', '<') // First 4 pages
4 ->then()->custom('show_getting_started')
5 ->register();
<= (Less Than or Equal)#Copied!
Matches when actual value is less than or equal to expected value.
Syntax
1->condition($value, '<=')
Examples
1Rules::create('legacy_php')
2 ->when()
3 ->constant('PHP_VERSION', '7.3', '<=') // PHP 7.3 or older
4 ->then()->custom('use_legacy_code')
5 ->register();
'abc' becomes 0 which may cause unexpected results. Ensure you're comparing numeric values.Pattern Matching Operators#Copied!
Pattern matching operators allow flexible string matching using wildcards or regular expressions.
LIKE (Wildcard Pattern)#Copied!
SQL-style wildcard matching using * (matches any characters) and ? (matches single character).
Syntax
1->condition($pattern, 'LIKE') // Explicit
2->condition($pattern) // Auto-detected if pattern contains * or ?
Wildcards
| Wildcard | Description | Example | Matches | Doesn't Match |
|---|---|---|---|---|
* |
Any characters (0 or more) | /admin/* |
/admin/, /admin/posts, /admin/a/b/c |
/administrator/ |
? |
Single character | /page-? |
/page-1, /page-a |
/page-10, /page- |
Examples
Prefix matching:
1Rules::create('admin_urls')
2 ->when()
3 ->request_url('/wp-admin/*') // Matches /wp-admin/anything
4 ->then()->custom('admin_action')
5 ->register();
Suffix matching:
1Rules::create('api_endpoints')
2 ->when()
3 ->request_url('*/api') // Matches anything/api
4 ->then()->custom('api_action')
5 ->register();
Contains matching:
1Rules::create('search_pages')
2 ->when()
3 ->request_url('*search*') // Contains "search" anywhere
4 ->then()->custom('search_action')
5 ->register();
Single character wildcard:
1Rules::create('version_urls')
2 ->when()
3 ->request_url('/v?/*') // Matches /v1/, /v2/, /va/, etc.
4 ->then()->custom('version_action')
5 ->register();
Header patterns:
1Rules::create('bearer_tokens')
2 ->when()
3 ->request_header('Authorization', 'Bearer *', 'LIKE')
4 ->then()->custom('validate_token')
5 ->register();
Complex patterns:
1Rules::create('complex_pattern')
2 ->when()
3 ->request_url('/api/v?/users/*') // /api/v1/users/123, /api/v2/users/abc
4 ->then()->custom('api_action')
5 ->register();
i flag for case-insensitive matching.NOT LIKE (Inverse Wildcard)#Copied!
Inverse of LIKE. Matches when pattern does NOT match.
Syntax
1->condition($pattern, 'NOT LIKE')
Examples
Exclude admin areas:
1Rules::create('non_admin')
2 ->when()
3 ->request_url('/wp-admin/*', 'NOT LIKE')
4 ->request_url('/wp-login.php', '!=')
5 ->then()->custom('public_action')
6 ->register();
Exclude API endpoints:
1Rules::create('non_api')
2 ->when()
3 ->request_url('/api/*', 'NOT LIKE')
4 ->then()->custom('web_action')
5 ->register();
REGEXP (Regular Expression)#Copied!
Full regular expression matching using PHP's preg_match().
Syntax
1->condition($regex_pattern, 'REGEXP') // Explicit
2->condition('/pattern/') // Auto-detected (starts with /)
Pattern Format
Regex patterns must be valid PHP regex with delimiters:
1'/pattern/' // Basic pattern
2'/pattern/i' // Case-insensitive
3'/pattern/u' // UTF-8
4'/^\/api\//' // Must escape forward slashes
/pattern/) and escape forward slashes in the pattern itself (\/).Examples
API version matching:
1Rules::create('api_versions')
2 ->when()
3 // Matches /api/v1/, /api/v2/, /api/v123/
4 ->request_url('/^\/api\/v[0-9]+\//i', 'REGEXP')
5 ->then()->custom('api_action')
6 ->register();
Email validation:
1Rules::create('email_param')
2 ->when()
3 ->request_param('email', '/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i', 'REGEXP')
4 ->then()->custom('process_email')
5 ->register();
Complex URL patterns:
1Rules::create('product_urls')
2 ->when()
3 // Matches /product/abc-123, /product/xyz-456
4 ->request_url('/^\/product\/[a-z]+-[0-9]+$/i', 'REGEXP')
5 ->then()->custom('show_product')
6 ->register();
Date patterns:
1Rules::create('date_urls')
2 ->when()
3 // Matches /2024/01/15, /2023/12/31
4 ->request_url('/^\/[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/', 'REGEXP')
5 ->then()->custom('date_archive')
6 ->register();
Case-insensitive matching:
1Rules::create('case_insensitive')
2 ->when()
3 ->request_url('/\/admin/i', 'REGEXP') // Matches /admin, /ADMIN, /Admin
4 ->then()->custom('action')
5 ->register();
Common Regex Patterns
| Pattern | Regex | Example |
|---|---|---|
| Numeric ID | /^\/post\/[0-9]+$/ |
/post/123 |
| Alphanumeric slug | /^\/page\/[a-z0-9-]+$/i |
/page/my-slug |
| UUID | /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i |
550e8400-e29b-41d4-a716-446655440000 |
| API versioning | /^\/api\/v[0-9]+\// |
/api/v2/ |
| Date (YYYY-MM-DD) | /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ |
2024-01-15 |
/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i |
|
Membership Operators#Copied!
Membership operators check if a value exists in an array of possibilities.
IN (In Array)#Copied!
Matches when actual value is in the array of expected values.
Syntax
1->condition([$val1, $val2, ...], 'IN') // Explicit
2->condition([$val1, $val2, ...]) // Auto-detected
Examples
Multiple HTTP methods:
1Rules::create('safe_methods')
2 ->when()
3 ->request_method(['GET', 'HEAD', 'OPTIONS'], 'IN')
4 ->then()->custom('cacheable_action')
5 ->register();
6
7// Auto-detected IN operator
8Rules::create('safe_methods_auto')
9 ->when()
10 ->request_method(['GET', 'HEAD', 'OPTIONS'])
11 ->then()->custom('cacheable_action')
12 ->register();
Multiple URLs:
1Rules::create('protected_pages')
2 ->when()
3 ->request_url([
4 '/dashboard',
5 '/profile',
6 '/settings'
7 ], 'IN')
8 ->then()->custom('require_auth')
9 ->register();
Environment types:
1Rules::create('non_production')
2 ->when()
3 ->constant('WP_ENVIRONMENT_TYPE', ['local', 'development', 'staging'], 'IN')
4 ->then()->custom('enable_debug')
5 ->register();
Post types:
1Rules::create('content_types')
2 ->when()
3 ->post_type(['post', 'page', 'article'], 'IN')
4 ->then()->custom('show_reading_time')
5 ->register();
NOT IN (Not In Array)#Copied!
Matches when actual value is not in the array of expected values.
Syntax
1->condition([$val1, $val2, ...], 'NOT IN')
Examples
Exclude methods:
1Rules::create('non_modifying')
2 ->when()
3 ->request_method(['POST', 'PUT', 'DELETE', 'PATCH'], 'NOT IN')
4 ->then()->custom('read_only_action')
5 ->register();
Exclude URLs:
1Rules::create('non_admin_urls')
2 ->when()
3 ->request_url([
4 '/wp-admin/*',
5 '/wp-login.php'
6 ], 'NOT IN')
7 ->then()->custom('public_action')
8 ->register();
Existence Operators#Copied!
Existence operators check whether a value exists, regardless of its actual value.
EXISTS#Copied!
Matches when a value exists and is not empty.
Syntax
1->condition(null, 'EXISTS') // Explicit
2->condition() // Auto-detected (no value parameter)
Empty Values
These values are considered "non-existent":
null''(empty string)[](empty array)'0'is NOT empty (it exists)
Examples
Cookie existence:
1Rules::create('has_session')
2 ->when()
3 ->cookie('session_id', null, 'EXISTS') // Cookie exists
4 ->then()->custom('load_session')
5 ->register();
6
7// Shorthand
8Rules::create('has_session_short')
9 ->when()
10 ->cookie('session_id') // EXISTS auto-detected
11 ->then()->custom('load_session')
12 ->register();
Parameter existence:
1Rules::create('has_action_param')
2 ->when()
3 ->request_param('action') // Parameter exists with any value
4 ->then()->custom('route_action')
5 ->register();
Header existence:
1Rules::create('has_auth_header')
2 ->when()
3 ->request_header('Authorization') // Header exists
4 ->then()->custom('validate_auth')
5 ->register();
NOT EXISTS#Copied!
Matches when a value does not exist or is empty.
Syntax
1->condition(null, 'NOT EXISTS')
Examples
New visitor detection:
1Rules::create('first_visit')
2 ->when()
3 ->cookie('visited_before', null, 'NOT EXISTS')
4 ->then()->custom('show_welcome')
5 ->register();
Missing parameters:
1Rules::create('no_page_param')
2 ->when()
3 ->request_param('page', null, 'NOT EXISTS')
4 ->then()->custom('show_first_page')
5 ->register();
Opt-out detection:
1Rules::create('analytics_allowed')
2 ->when()
3 ->cookie('analytics_opt_out', null, 'NOT EXISTS')
4 ->then()->custom('track_analytics')
5 ->register();
Boolean Operators#Copied!
Boolean operators provide strict boolean value comparison.
IS (Is True/False)#Copied!
Matches when value strictly equals true or false.
Syntax
1->condition(true, 'IS') // Explicit
2->condition(true) // Auto-detected
Examples
Debug mode:
1Rules::create('debug_enabled')
2 ->when()
3 ->constant('WP_DEBUG', true, 'IS')
4 ->then()->custom('show_debug_bar')
5 ->register();
6
7// Auto-detected
8Rules::create('debug_enabled_auto')
9 ->when()
10 ->constant('WP_DEBUG', true) // IS auto-detected
11 ->then()->custom('show_debug_bar')
12 ->register();
Feature flags:
1Rules::create('feature_enabled')
2 ->when()
3 ->constant('FEATURE_ENABLED', true)
4 ->then()->custom('use_new_feature')
5 ->register();
Boolean states:
1Rules::create('user_logged_in')
2 ->when()
3 ->is_user_logged_in() // Returns boolean, uses IS
4 ->then()->custom('show_dashboard')
5 ->register();
IS NOT (Is Not True/False)#Copied!
Matches when value does not strictly equal true or false.
Syntax
1->condition(true, 'IS NOT')
2->condition(false, 'IS NOT')
Examples
Debug disabled:
1Rules::create('production_mode')
2 ->when()
3 ->constant('WP_DEBUG', true, 'IS NOT') // Not true (false or undefined)
4 ->then()->custom('production_features')
5 ->register();
Feature disabled:
1Rules::create('legacy_mode')
2 ->when()
3 ->constant('NEW_FEATURE', true, 'IS NOT')
4 ->then()->custom('use_legacy_code')
5 ->register();
IS and IS NOT perform strict boolean comparison. Use = and != for truthy/falsy comparisons.Operator Precedence#Copied!
When using multiple conditions with different operators, all conditions are evaluated independently. There's no operator precedence since conditions are combined using match types (all/any/none).
1Rules::create('multiple_operators')
2 ->when() // match_all by default
3 ->request_url('/api/*', 'LIKE') // Pattern
4 ->request_method(['GET', 'HEAD'], 'IN') // Membership
5 ->cookie('session_id', null, 'EXISTS') // Existence
6 ->then()->custom('action')
7 ->register();
8
9// Evaluates as: (URL LIKE /api/*) AND (method IN [GET, HEAD]) AND (cookie EXISTS)
Operator Auto-Detection Rules#Copied!
MilliRules uses these rules for operator auto-detection:
| Value Type | Pattern | Auto-Detected Operator | Example |
|---|---|---|---|
| Array | Any array | IN |
['GET', 'POST'] → IN |
| Boolean | true or false |
IS |
true → IS |
| Null | null |
EXISTS |
null → EXISTS |
| String with wildcard | Contains * or ? |
LIKE |
'/admin/*' → LIKE |
| String (regex) | Starts with / |
REGEXP |
'/^abc/' → REGEXP |
| Other | Any other value | = |
'POST' → = |
1// Auto-detection examples
2->request_method('GET') // = (string, no wildcard)
3->request_method(['GET', 'HEAD']) // IN (array)
4->constant('WP_DEBUG', true) // IS (boolean)
5->cookie('session_id') // EXISTS (no value parameter)
6->request_url('/admin/*') // LIKE (has wildcard)
7->request_url('/^\/api\//i') // REGEXP (starts with /)
Best Practices#Copied!
1. Let Auto-Detection Work#Copied!
1// ✅ Good - clean and readable
2->request_url('/api/*')
3->request_method(['GET', 'HEAD'])
4->constant('WP_DEBUG', true)
5
6// ❌ Unnecessary - auto-detection works fine
7->request_url('/api/*', 'LIKE')
8->request_method(['GET', 'HEAD'], 'IN')
9->constant('WP_DEBUG', true, 'IS')
2. Use LIKE for Simple Patterns#Copied!
1// ✅ Good - simple and fast
2->request_url('/api/*')
3->request_url('/product-*')
4
5// ❌ Overkill - regex is slower
6->request_url('/^\/api\//i', 'REGEXP')
7->request_url('/^\/product-/i', 'REGEXP')
3. Validate Regex Patterns#Copied!
1// ✅ Good - valid regex with delimiters
2->request_url('/^\/api\/v[0-9]+\//i', 'REGEXP')
3
4// ❌ Wrong - missing delimiters
5->request_url('^/api/v[0-9]+/', 'REGEXP')
6
7// ❌ Wrong - forward slashes not escaped
8->request_url('/^/api/v[0-9]+/', 'REGEXP')
4. Use Appropriate Operators#Copied!
1// ✅ Good - right operator for the job
2->request_method(['GET', 'HEAD'], 'IN') // Multiple values
3->request_url('/admin/*', 'LIKE') // Pattern match
4->constant('WP_DEBUG', true, 'IS') // Boolean
5->cookie('session_id', null, 'EXISTS') // Existence
6
7// ❌ Wrong - inefficient or incorrect
8->request_method('GET', '=') // Use IN for multiple
9->request_method('POST', '=')
10->request_url('/admin/edit.php', 'LIKE') // Use = for exact match
5. Consider Performance#Copied!
Operator performance (fastest to slowest):
=,!=(equality)IS,IS NOT(boolean)EXISTS,NOT EXISTS(existence)>,>=,<,<=(numeric)IN,NOT IN(membership)LIKE,NOT LIKE(wildcard)REGEXP(regex - slowest)
1// ✅ Good - fast checks first
2->when()
3 ->request_method('POST') // Fast equality
4 ->request_url('/api/users') // Fast equality
5 ->request_header('Authorization') // Fast existence
6 ->custom('complex_validation') // Slow custom check last
7
8// ❌ Bad - slow check first
9->when()
10 ->request_url('/complex-.*-pattern/i', 'REGEXP') // Slow regex first!
11 ->request_method('POST')
Common Pitfalls#Copied!
1. Case Sensitivity#Copied!
1// ❌ Wrong - case mismatch
2->request_method('post') // Won't match 'POST'
3
4// ✅ Correct - exact case
5->request_method('POST')
6
7// ✅ Alternative - case-insensitive regex
8->request_method('/^post$/i', 'REGEXP')
2. Wildcard Escaping#Copied!
1// ❌ Wrong - literal asterisk not treated as wildcard
2->request_url('\*')
3
4// ✅ Correct - asterisk is wildcard
5->request_url('*')
6
7// ✅ If you need literal asterisk, use regex
8->request_url('/\*/','REGEXP')
3. Regex Delimiters#Copied!
1// ❌ Wrong - no delimiters
2->request_url('^/api/', 'REGEXP')
3
4// ✅ Correct - with delimiters
5->request_url('/^\/api\//i', 'REGEXP')
4. Empty Arrays#Copied!
1// ❌ Wrong - empty array always fails
2->request_method([], 'IN')
3
4// ✅ Correct - non-empty array
5->request_method(['GET', 'POST'], 'IN')
Next Steps#Copied!
- Dynamic Placeholders - Use dynamic values in conditions
- Built-in Conditions - See operators in action
- Custom Conditions - Implement custom operators
- Real-World Examples - Complete working examples
Ready for advanced features? Continue to Dynamic Placeholders to learn about using dynamic values in your rules.