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
Note: All operators are case-insensitive. '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')
Tip: Auto-detection makes your code cleaner and more readable. Only specify operators explicitly when you need precise control.

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')
Important: String comparisons are case-sensitive: '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();
Warning: Comparison operators cast values to numbers. String '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();
Tip: LIKE patterns are case-sensitive. Use REGEXP with the 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
Important: Always use delimiters (/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
Email /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i
Warning: Complex regex can impact performance. Use LIKE patterns when possible for better performance.

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();
Note: 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 trueIS
Null null EXISTS nullEXISTS
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):

  1. =, != (equality)
  2. IS, IS NOT (boolean)
  3. EXISTS, NOT EXISTS (existence)
  4. >, >=, <, <= (numeric)
  5. IN, NOT IN (membership)
  6. LIKE, NOT LIKE (wildcard)
  7. 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!


Ready for advanced features? Continue to Dynamic Placeholders to learn about using dynamic values in your rules.