YAUR: Yet Another URI Router - Technical Documentation
Comprehensive technical documentation for the URI Router (YAUR – Yet Another URI Router). This documentation will be suitable for developers who need to understand how the system works and how to write routing rules.
1. Overview
YAUR (Yet Another URI Router) is a lightweight, rule-based URI routing engine designed for the WackoWiki platform. It processes incoming HTTP requests and dispatches them to appropriate handlers by matching URI patterns against a sequence of rules defined in a configuration file.
1.1. Key Characteristics
- Rule-based processing: Sequential evaluation of regex patterns against incoming URIs
- Flexible variable system: Supports URL parameters, HTTP globals, and local variables
- Pattern matching and substitution: Extract parts of URIs and assign them to variables
- Caching support: Configuration is cached for performance optimization
- Control flow: Support for conditional routing with explicit success/failure markers
1.2. Design Philosophy
The router operates on a simple principle: iterate through rules sequentially until one matches, execute all associated actions, and if successful, determine the next step (continue, jump, or terminate).
2. Architecture
2.1. Request Processing Pipeline
HTTP Request
↓
1. Extract URI and HTTP metadata (_uri, _method, _rewrite, _tls)
↓
2. Initialize routing environment with variables
↓
3. Iterate through config rules sequentially
↓
4. For each rule:
a) Match regex against current _uri
b) If match: populate variables from match groups
c) Execute actions sequentially
d) If action succeeds: continue to next action
e) If action fails: discard all changes, try next rule
f) If _ok! executed: terminate routing successfully
g) If _next! executed: skip to next regex rule
↓
5. Backpatch successful changes into HTTP superglobals
↓
6. Return routing result with extracted parameters
2.2. Component Stack
- Configuration Parser (
read_config()): Processes router.conf file- Expands macros
- Validates regex patterns
- Compiles action specifications
- Routing Engine (
route()): Main routing loop- Pattern matching
- Variable substitution
- Action execution
- Backtracking on failure
- Variable System (
parse_var()): Manages variable namespace- Regex match groups ($0-$9)
- Sub-match groups ($a-$j)
- HTTP superglobals (G*/P*/S*)
- Local variables
3. Core Concepts
3.1. Rules
A rule is a complete routing definition consisting of a regex pattern and zero or more actions:
regex [action1] [action2] [actionN]
Characteristics:
- Rules are processed in order
- A regex pattern must match for actions to execute
- If any action fails, all changes from that line are discarded
- Continuation lines (starting with whitespace) share the same regex
3.2. Matches and Sub-matches
When a regex pattern matches:
- Main match groups ($0-$9): Captured by the main regex
- $0: Complete match
- $1-$9: Capture groups from (...)
- Sub-match groups ($a-$j): Captured by pattern-matching actions ()
- Used only when a pattern matching operator succeeds
3.3. Variables
Three variable scopes exist:
- Regex matches:
$0,$1, ...,$9 - Sub-matches:
$a,$b, ...,$j - Named variables:
-
Gvar: $_GET['var'] -
Pvar: $_POST['var'] -
Svar: $_SERVER['var'] -
var: Local variable in router namespace
-
4. Routing Configuration
4.1. File Structure
The router.conf file contains:
- Comments: Lines starting with
//or# are ignored - Macro definitions:
define {name} pattern - Rules:
regex [actions...] - Continuation lines: Lines starting with whitespace (common regex)
4.2. Macro System
Macros are text substitutions that expand during configuration parsing.
4.2.1. Defining Macros
define {name} replacement_pattern
Examples:
define {hashid} [0-9a-zA-Z]+
define {i} [0-9]+
define {h} [0-9a-fA-F]+
define {a} [0-9a-zA-Z]+
define {w} [\w]+
define {} [^/]*
define {*} .*?
define {**} .*
4.2.2. Using Macros
Basic substitution:
`^{i}/about$`
Expands to: ^([0–9]+)/about$
Named capture group:
`^{page_id=i}/$`
Expands to: ^(?P<page_id>[0–9]+)/$
For variable assignment (inline naming):
`^{i=hashid}/show$`
This creates a capture group that can be accessed as $1~hashid:N for validation.
4.2.3. Predefined Macros
The {method} macro is automatically populated by the router with all available handler methods (discovered from handler directory):
define {method} show|edit|delete|admin|install|...
4.3. Regex Pattern Syntax
YAUR uses PHP PCRE regex syntax:
- Delimiters: Backticks or other paired delimiters required
- Modifiers: Added after closing delimiter (e.g.,
/pattern/ifor case-insensitive) - Match groups:
()creates numbered captures,(?P<name>...)creates named captures - Special sequences: Standard PCRE syntax applies
4.3.1. Empty Regex in Continuation Lines
A continuation line (starting with whitespace) may have an empty regex, meaning:
- Use the same regex pattern as the previous line
- Execute actions only if the regex already matched
`^user/(\d+)$`
action1 action2
action3 action4 # uses same regex as line above
5. Syntax Reference
5.1. Configuration Line Format
regex action1 action2 action3
- Regex: PCRE pattern in delimiters (required for first line of rule)
- Actions: Whitespace-separated specifications
- Comments: Start with // or # (removed during parsing)
5.2. Action Format
VARIABLE[:FUNCTION] OPERATOR VALUE
Components:
-
VARIABLE: Target variable (see Variable System section) -
[:FUNCTION]: Optional transformation (tolower, toupper, int) -
OPERATOR: One of =, !=, ==, ~, !~, ?, -, ?=, <, >, <=, >=, ! -
VALUE: Value to assign or compare (supports variable expansion)
6. Variable System
6.1. Variable Naming
6.1.1. Special Prefixes (HTTP Superglobals)
| Prefix | Source | Example | Access |
|---|---|---|---|
| G | $_GET | Gpage | $_GET['page'] |
| P | $_POST | Paction | $_POST['action'] |
| S | $_SERVER | SREQUEST_METHOD | $_SERVER['REQUEST_METHOD'] |
6.1.2. Regex Match Groups
| Notation | Type | Source | Scope |
|---|---|---|---|
| $0 | Complete match | Main regex | Current action line |
| $1-$9 | Capture groups | Main regex | Current action line |
| $a-$j | Sub-matches | Pattern matching (~) | From that ~action onward |
6.1.3. Local Variables
Simple identifiers (no prefix, no $) are stored in the router's local namespace:
method=show # stores in local 'method' variable
6.2. Predefined Variables
The router automatically initializes:
| Variable | Type | Value | Example |
|---|---|---|---|
| _uri | string | Request URI (without query string) | /admin/page |
| _method | string | HTTP method | GET, POST, PUT |
| _rewrite | boolean | 1 if mod_rewrite active, 0 otherwise | 1 or 0 |
| _tls | boolean | 1 for HTTPS, 0 for HTTP | 1 or 0 |
| _ip | string | Client IP address | 192.168.1.1 |
| _line | integer | Current config line number | 42 |
| _ok | boolean | Set to terminate routing successfully | (internal) |
| _next | boolean | Set to skip to next regex | (internal) |
6.3. Variable Expansion
Variables can be expanded in action values using several formats:
6.3.1. Basic Expansion
Gpage=${page} # expands local variable 'page'
method=$1 # expands first capture group
6.3.2. Safe Expansion (Suppress Errors)
Use @ prefix to suppress undefined variable errors:
template=@${theme} # returns empty string if theme undefined
6.3.3. Special Escaping
| Sequence | Expands To | Purpose |
|---|---|---|
| $$ | $ | Literal dollar sign |
| $@ | @ | Literal @ sign |
| @$var | Empty or "" | Undefined variable (no error) |
6.3.4. Invalid Expansions
Using an undefined variable without @ causes the action to fail:
var=${undefined} # Action fails if 'undefined' not set
Gres=${nonexist} # Fails, assignment not executed
7. Action Operators
7.1. Assignment Operators
7.1.1. Simple Assignment (=)
Assigns value to variable, discarding any previous value.
method=$1 # Assign capture group
Gpage=about # Assign literal string
template:tolower=${Gtemplate} # Assign with function
Functions:
-
tolower: Convert to lowercase -
toupper: Convert to uppercase -
int: Convert to integer
7.1.2. Conditional Assignment (?=)
Assigns value only if variable is not already set.
method?=show # Only sets method if not already set
7.1.3. Force Assignment (!)
Sets variable to 1 (used as boolean flag).
unlock! # Sets unlock=1 session! # Sets session=1
7.1.4. Unset (-)
Removes variable from its scope (e.g., removes from $_GET).
Gpage- # Removes $_GET['page'] method- # Removes local method variable
7.2. Comparison Operators
All comparisons fail if the variable is undefined (no @ masking available).
7.2.1. Equality (=====)
Loose equality comparison.
method==show _method==GET Gid==0
7.2.2. Inequality (!=)
Loose inequality comparison.
_method!=POST method!=admin
7.2.3. Less Than (<), Greater Than (>)
Numeric or string comparison (depends on operand types).
year:int<2020 role:int>5 name>admin # String comparison
7.2.4. Less Than or Equal (<=), Greater Than or Equal (>=)
age:int<=18 priority:int>=3
Function Modifier:
-
int: Converts both operands to integers before comparison
7.2.5. Isset (?)
Checks if variable is set (no value needed).
Gpage? # Pass if $_GET['page'] exists method? # Pass if local method exists $1? # Pass if first capture group exists
7.3. Pattern Matching
7.3.1. Regex Match (~)
Matches variable value against a PCRE regex pattern.
var~/pattern/i # Match with flags var!~/^admin/ # Negative match (must NOT match)
Match Groups:
- Successful
~operation populates $a-$j with sub-match groups - $a = complete match (or first group if group exists)
- $b-$j = subsequent capture groups
Example:
method~/^(show|edit|delete)$/ _next! # If method matches one of those, $a contains matched value
7.3.2. Hashid Match (~hashid:N)
Validates a hashid (encoded numeric IDs) with checksum verification.
$1~hashid:2 # Validate $1 as hashid with 2 numeric IDs Gone=$a # $a gets first numeric ID after validation Gtwo=$b # $b gets second numeric ID
Hashid Validation:
- Decodes the hashid string (removes non-alphanumeric characters)
- Checks that exactly N numeric values are encoded
- Verifies checksum using SHA1 hash
- Sets sub-match variables ($a-$j) with decoded values
8. Patterns and Matching
8.1. Regex Matching Process
- Pattern validation: Regex is validated during config parsing
- Macro expansion: All {macro} references expanded to capture groups
- Pattern compilation: PHP PCRE regex compiled with delimiters and flags
- URI matching:
preg_match()executed against current _uri value - Group extraction: Matched groups stored in $0-$9 and accessible as variables
8.2. Match Group Assignment
When a regex matches, captured groups are automatically available:
`^admin/(\w+)/(\d+)$`
Gaction=$1 # First capture group
Gid=$2 # Second capture group
8.3. The Match Context
- Only the first matching rule line executes its actions
- Actions have access to that rule's match groups
- Match groups persist across multiple action lines (continuations)
- After any action fails, backtrack to previous state and try next rule
9. Control Flow
9.1. Successful Routing
A route terminates successfully when _ok! is executed as an action in a rule where all previous actions also succeeded.
`^page/(\d+)$`
Gpage_id=$1
method=show
_ok! # Route succeeds
9.2. Conditional Routing (_ok!)
The _ok! action terminates routing immediately with success. All previous actions in the line must have succeeded.
`^static/(.+)$`
route=static
age=30
_ok! # No further rules processed
9.3. Jumping to Next Rule (_next!)
The _next! action skips all remaining continuation lines for the current regex and jumps to the next regex rule.
`^{hashid}(/.*)?$`
$1~hashid:2 page=$a method=hashid _ok!
// If hashid validation fails, jump to next rule
_next! # Skip remaining lines for this regex
// Following lines not executed if _next! above is reached
9.4. Fallthrough and 404
If no rule matches or if all rules have _ok! action fail:
// Return 404 (routing failed) $vars['_ok'] = false;
9.5. Rule Sequence
Rule 1 regex
Rule 1 action A
Rule 1 action B
Rule 1 regex (continuation, same regex)
Rule 1 action C
Rule 2 regex
Rule 2 action A
Execution flow:
- Try Rule 1 regex against _uri
- If matches: execute actions A, B, C sequentially
- If any action fails: revert changes, try Rule 2
- If
_ok!executed: stop routing - If
_next!executed: skip remaining actions, try Rule 2
10. Practical Examples
10.1. Example 1: Static File Routes
`^robots\.txt$` _ok!
`^(theme/{}/css|theme/_common)/{}$` _ok! // css
`^js/(lang/|photoswipe/)?{}$` _ok! // js
How it works:
- Match static files (robots.txt, CSS, JavaScript)
- If matched, mark as handled (
_ok!) and stop routing - Router will serve these files without further processing
10.2. Example 2: Dynamic Page with Hashid
`^{hashid=a}(/.*)?$`
$1~hashid:2 page=$a method=hashid _ok!
_next!
// If hashid validation fails, continue with standard page routing
$1~hashid:2 Gpage_id=$a Gversion_id=$b page= method=show redirect=301 _ok!
Flow:
- Capture hashid into $1
- Validate as 2-part hashid
- If valid: extract IDs, set method to hashid handler, succeed
- If invalid:
_next!jumps to next rule - Next rule tries standard page routing
10.3. Example 3: Method-based Routing
`^(|{page=**}/){method}(/.*?)?$`ii
method:tolower=$3 # Convert method to lowercase
method==file session=2 # Special session handling for files
_ok!
Pattern breakdown:
-
^(|{page=**}/): Optional page prefix (empty or page path with /) -
{method}: Method name (expanded to show|edit|delete|...) -
(/.*?)?$: Optional trailing path -
iflags: Case-insensitive matching - Actions: Convert method to lowercase, set session level for file method
10.4. Example 4: Request URI Initialization
`^`
SPATH_INFO!= _uri=${SPATH_INFO} _next! // if PATH_INFO available - use it
_rewrite==0 _uri=@${Gpage} Gpage- // when rewrite mode is off - replace _uri by page _GET variable
`^/*{_uri=*}/*$` // trim _uri of beginning & trailing slashes
Purpose:
- Initialize _uri from multiple sources (PATH_INFO or GET parameter)
- Handle both mod_rewrite and non-rewrite modes
- Normalize URI by trimming slashes
10.5. Example 5: Variable Extraction and Transformation
`^admin/(\w+)/(\d+)$`
Gaction:tolower=$1 # Convert action to lowercase
Gid:int=$2 # Convert ID to integer
unlock! # Set unlock flag
route=admin
_ok!
Variable transformations:
-
tolower: Convert extracted action to lowercase -
int: Convert ID string to integer for database queries -
!: Set boolean flag for authorization
11. Advanced Techniques
11.1. Conditional Execution Chains
Execute actions conditionally based on request properties:
`^data/(\w+)$`
_method==GET route=show_data _ok! # GET requests
_method==POST route=save_data _ok! # POST requests
_method==DELETE route=delete_data _ok! # DELETE requests
// If method is none of above, routing fails (404)
11.2. Sub-Match Extraction
Use pattern matching to extract parts of variables:
`^(\w+):(\w+):(\w+)$`
$1~/(show|edit)/i next!
// If $1 matches (show or edit), $a contains matched portion
action=$a
resource=$2
id=$3
_ok!
11.3. Safe Variable Fallback
Use @ masking to provide defaults:
template=@${Gtemplate:default.html}
// If Gtemplate undefined, template becomes ":default.html" (contains literal text)
// Better approach:
template:tolower=@${Gtemplate}
// If undefined, template stays empty, allowing defaults elsewhere
11.4. Hashid Multiple Parts
Extract multiple encoded IDs from a single hashid:
`^docs/({hashid=id})/versions/(\d+)$`
$id~hashid:3 page=$a section=$b version_id=$c _ok!
// Hashid contains 3 IDs: page, section, version_id
Result after successful match:
-
$a= first numeric ID -
$b= second numeric ID -
$c= third numeric ID
11.5. Combined Conditions
Chain multiple comparisons to enforce complex rules:
`^admin/(.+)$`
_method!=GET next! // Must be GET
Glevel:int>5 next! // User level > 5
_tls==1 next! // Must use HTTPS
Gaction=$1
route=admin
_ok!
If any condition fails, _next! continues to next rule.
11.6. Progressive URI Rewriting
Modify _uri and re-match it:
`^index\.php$`
_uri= // Clear _uri to re-match from top
`^index\.(php|html)$`
_uri= // Both rewritten to empty
// First rule: detect index.php
// Second rule: matches both index.php and index.html, clears URI
// Since _uri is now empty, matches the initial `^` rule again
11.7. Macro Composition
Build complex patterns from simpler macros:
define {hashid} [0-9a-zA-Z]+
define {i} [0-9]+
define {page} {hashid}(x{i})?
`^docs/{page=id}$`
page_id=$1
version_id=$2 // May be empty if no version
method=show
_ok!
12. Integration with WackoWiki
12.1. Handler Discovery
The router automatically discovers available handlers:
php
foreach (Ut::file_glob(HANDLER_DIR, '*/[!_]*.php') as $method)
{
$methods[] = pathinfo($method, PATHINFO_FILENAME);
}
define {method} show|edit|delete|admin|install|...
Available handlers become routing options:
`^{page=**}/{method}$`
method:tolower=$2
_ok!
12.2. Static Asset Serving
Routes for CSS, JavaScript, and images skip the main handler:
`^theme/{}/css/{}$` _ok!
`^image/(wikiedit/)?{}$` _ok!
`^js/(lang/)?{}$` _ok!
Optimization: _ok! prevents further processing, serving assets directly.
12.3. API/Feed Support
Special handling for XML feeds:
`^xml/opensearch\.xml$` _ok!
`^xml/{}$` age=0 // feeds (no cache)
route=feed
_ok!
12.4. Installation Mode
Separate routing during setup:
`^setup/(image|css)/{}$` _ok! unlock=1
Sets unlock=1 to bypass normal authorization during installation.
13. Best Practices
13.1. 1. Order Matters
Place more specific rules before general ones:
`^admin/special$` _ok! // Specific case first `^admin/(.+)$` _ok! // General case second `^(.+)$` _ok! // Catchall last
13.2. 2. Use Named Macros for Clarity
// Good
`^docs/{id=hashid}/show$`
// Less clear
`^docs/{hashid}/show$`
13.3. 3. Validate Before Assigning
// Good - validate before use $1~hashid:2 page=$a _ok! // Risky - unvalidated use page=$1 _ok!
13.4. 4. Document Complex Rules
// Extract page ID and version from hashid format
// then route to version-specific handler
`^{page=hashid}(/.*)?$`
$1~hashid:2 page_id=$a version_id=$b _ok!
13.5. 5. Use Safe Expansion for Optional Values
// Good - won't fail if undefined
theme:tolower=@${Gtheme}
// Risky - fails if not set
theme=$Gtheme
13.6. 6. Separate Concerns
// First: normalize and validate
`^`
SPATH_INFO!= _uri=${SPATH_INFO} _next!
// Then: route based on URI
`^page/(\d+)$`
Gpage=$1 _ok!
14. Troubleshooting
14.1. Rule Not Matching
- Check regex syntax: Use online PCRE tester
- Verify _uri value: Add
dbg=_uriaction - Check macro expansion: Ensure {macro} is defined
- Verify flags: Ensure regex is case-sensitive when needed
14.2. Action Failing Silently
- Check undefined variables: Add @ prefix for safe expansion
- Verify operator: Ensure correct operator for comparison type
- Check function names: tolower, toupper, int are only built-in functions
- Verify variable names: G/P/S prefixes for superglobals
14.3. Changes Not Applied
- Remember: If any action in a line fails, ALL changes from that line are discarded
- Split complex rules into multiple lines
- Use
_next!to skip and try alternative rules
14.4. Configuration Caching Issues
- Router caches compiled config to
/src/_cache/router.conf - If changes not reflected, clear cache file or set incorrect permissions
- Cache includes CODE_VERSION to auto-invalidate on code changes
15. References
- PHP PCRE Regex Syntax: https://www.php.net/manual/en/[...]e.pattern.syntax.php
- WackoWiki URI Router: https://wackowiki.org/doc/Dev/Projects/UriRouter
- Source Code:
/src/class/urirouter.php - Configuration:
/src/config/router.conf