Guzzle URI Template

repository·2.0·Indexed 41 days ago

https://github.com/guzzle/uri-template

A PHP library that implements RFC 6570 to expand URI templates into concrete URI strings using provided variable values. It supports RFC 6570 levels 1 through 4, including simple, reserved, fragment, label, path segment, and form-style query expansion. The library provides the static UriTemplate::expand() method to handle scalar values, lists, maps, and nested query arrays with support for prefix and explode modifiers.

Tokens
4.2K
Snippets
15
Records
21
Agent score
51%

What's inside Guzzle URI Template

  1. How variable names are matched in UriTemplate::expand()

    2.0

    Variable names in the template must match the keys in the provided variables array exactly.

    • Dot Separators: Names can use dots (e.g., user.id), but cannot start/end with a dot or contain empty parts.
    • Numeric Names: If a variable name consists entirely of digits (e.g., {0}), PHP's integer coercion applies. ['0' => 'x'] and [0 => 'x'] are treated as the same key. However, non-canonical numeric strings like {01} must be provided as exact string keys ('01').
    • Percent-encoding: Percent-encoded triplets in variable names are treated as part of the name and are not decoded for lookup.
    UriTemplate::expand('/users/{user.id}', [
        'user.id' => 123,
    ]);
  2. Supported variable value types for expansion

    2.0

    When calling UriTemplate::expand(), the following value shapes are supported:

    • Scalars: Cast to strings before expansion.
    • Booleans: Expanded as 1 and 0 at every nesting level.
    • Floats: Expanded using PHP's float-to-string conversion with a normalized . decimal separator. Non-finite floats (INF, -INF, NAN) are not supported and throw InvalidArgumentException.
    • Stringable Objects: Objects implementing __toString().
    • Lists: Dense zero-indexed arrays (keys 0 through n-1 in ascending order).
    • Maps: Any array that is not a list (sparse, mixed-key, or reordered).
    • Nested Query Arrays: Supported only within exploded query/query-continuation expressions (e.g., {?filter*}).

    Omitted Values: null, empty arrays, and missing variables are treated as undefined and omitted from the expansion.

  3. Handle PHP Array Shapes (Lists vs. Maps)

    2.0

    Guzzle URI Template 2.0 uses the shape of PHP arrays to determine if they are expanded as lists or maps.

    Lists

    An array is treated as a list if its keys are exactly 0 through n-1 in ascending insertion order.

    • Sparse Arrays: If an array is sparse (e.g., [1 => 'a']), it is treated as a map. To force it to be a list, use array_values().
    $tag = [1 => 'red', 2 => 'green'];
    // To expand as a list:
    UriTemplate::expand('/tags{/tag*}', ['tag' => array_values($tag)]);

    Maps

    An array is treated as a map if it has non-sequential keys, reordered keys, or mixed-key types.

    UriTemplate::expand('/search{?filter*}', [
        'filter' => ['status' => 'open'],
    ]);
    $tag = [1 => 'red', 2 => 'green'];
    UriTemplate::expand('/tags{/tag*}', ['tag' => array_values($tag)]);
  4. Handle empty and null values in expansions

    2.0

    The behavior of empty or null values depends on the operator and the structure:

    • null members: Inside lists and maps, null members are treated as undefined and omitted. A map where all members are null is treated as an undefined variable and omitted entirely.
    • Empty nested arrays: These are omitted from exploded query expansions.
    • Empty-string values: For simple, reserved, fragment, label, and path segment operators, exploded map members with empty-string values render as the bare name (e.g., /path/name) rather than name=.
  5. Use explode modifiers for lists and maps

    2.0

    The explode modifier (*) expands lists and maps item by item.

    Lists

    Arrays with keys 0 through n-1 in ascending order expand as lists. If an array is sparse, use array_values() to reindex it before expansion to ensure it is treated as a list.

    Maps

    All other arrays (reordered, sparse, or mixed-key) expand as maps. Map order follows PHP array insertion order.

    Nested Arrays

    Nested arrays are supported only for exploded query-style map expansions (e.g., {?var*} or {&var*}). These use RFC 3986 query encoding with PHP bracket syntax (e.g., key[subkey]=value).

    // List expansion
    UriTemplate::expand('/tags{/tag*}', ['tag' => ['red', 'green', 'blue']]);
    // Result: /tags/red/green/blue
    
    // Map expansion
    UriTemplate::expand('/search{?filter*}', [
        'filter' => [
            'status' => 'open',
            'sort' => 'created',
        ],
    ]);
    // Result: /search?status=open&sort=created
    
    // Nested query expansion
    UriTemplate::expand('/search{?filter*}', [
        'filter' => [
            'author' => [
                'name' => 'Ada Lovelace',
            ],
        ],
    ]);
    // Result: /search?filter%5Bauthor%5D%5Bname%5D=Ada%20Lovelace
  6. Handle Literal Text in URI Templates

    2.0

    In version 2.0, literal text outside of expressions is validated and encoded according to RFC 6570.

    Rules:

    • Encoding: Use pct-encoded (percent-encoded) triplets for special characters in literal text (e.g., use %20 instead of a space).
    • Invalid Characters: Characters such as spaces, double quotes, control characters, <, >, \, ^, `, and | are invalid in literal text and will throw an InvalidArgumentException if not encoded.
    • UTF-8: Valid non-ASCII literal text must be valid UTF-8.

    Examples:

    Correct (Encoded):

    UriTemplate::expand('/search%20terms/{id}', ['id' => 1]);

    Incorrect (Will throw exception):

    // Space in literal text is invalid
    UriTemplate::expand('/search terms/{id}', ['id' => 1]);
  7. Use Valid RFC 6570 Variable Names

    2.0

    Variable names in templates must follow RFC 6570 syntax: ASCII letters, ASCII digits, _, pct-encoded triplets, and dot separators.

    Constraints:

    • No Hyphens: Hyphenated names (e.g., default-name) are invalid. Use underscores (default_name) or dot separators (default.name).
    • No Raw Unicode: Use pct-encoded triplets for non-ASCII characters in both the template and the variable map key.
    • No Whitespace: Whitespace inside variable specifiers is invalid.
    • Dot Separators: Dots are part of the variable name and do not traverse nested PHP arrays. To use a dot, the key in your variable map must be the full string (e.g., 'last.name' => 'Doe').

    Handling Hyphenated Query Parameters

    If you need a query parameter name that contains a hyphen, use an exploded map so the hyphenated name is treated as data rather than template syntax:

    UriTemplate::expand('/search{?query*}', [
        'query' => ['default-graph-uri' => 'https://example.com/'],
    ]);
  8. Apply Prefix Modifiers to Scalar Values

    2.0

    Prefix modifiers (e.g., {var:3}) are valid only for scalar or stringable values. They are not supported for lists or maps.

    Rules:

    • Value Type: Apply only to strings or stringable objects.
    • Length: The prefix length must be a positive integer between 1 and 9999 with no leading zeros.
    • Counting: Length is counted as Unicode code points (including pct-encoded triplets), not bytes.

    Example:

    // Correct: prefixing a string
    UriTemplate::expand('{name:3}', ['name' => 'value']);
    
    // Incorrect: prefixing a list (throws InvalidArgumentException)
    UriTemplate::expand('{list:1}', ['list' => ['red', 'green']]);
    UriTemplate::expand('{name:3}', ['name' => 'value']);
  9. Expand Lists and Maps using the Explode Modifier

    2.0

    To expand lists or maps item by item, use the explode modifier *.

    Lists:

    Use * to concatenate list members.

    UriTemplate::expand('{/list*}', ['list' => ['red', 'green']]);
    // Result: /red/green

    Maps:

    Use * to expand map members as key-value pairs.

    UriTemplate::expand('/search{?filter*}', [
        'filter' => [
            'status' => 'open',
            'sort' => 'created',
        ],
    ]);
    // Result: /search?status=open&sort=created
    UriTemplate::expand('{/list*}', ['list' => ['red', 'green']]);
  10. Upgrade from Guzzle URI Template 1.x to 2.0

    2.0

    Guzzle URI Template 2.0 is a major release that introduces stricter RFC 6570 validation.

    Key Changes:

    • PHP Version: Requires PHP ^7.4 || ^8.0. If you must support PHP 7.2 or 7.3, continue using 1.x.
    • Stricter Validation: The library now validates template syntax, literal text, variable names, modifiers, and values before expansion.
    • Error Handling: Instead of returning partially expanded URIs or producing PHP warnings, invalid input now throws an InvalidArgumentException.
    • Static API: The UriTemplate constructor is now private. You must use the static method UriTemplate::expand() instead of instantiating the class.
  11. Use Reserved and Fragment Expansion

    2.0

    Reserved expansion ({+var}) and fragment expansion ({#var}) preserve URI reserved delimiters from variable values according to RFC 6570.

    Key Behaviors:

    • Delimiters: Unlike simple expansion, these operators do not percent-encode reserved characters (like / or :).
    • Map Keys: In version 2.0, reserved and fragment expansion also preserve reserved characters in map keys instead of percent-encoding them.
    • Security Warning: Because these operators preserve the meaning of reserved characters, untrusted values can inject structure (like scheme or path) into the URI. Use simple expansion for untrusted input.

    Examples:

    Reserved Expansion (Path):

    UriTemplate::expand('/files/{+path}', ['path' => 'a/b']);
    // Result: /files/a/b

    Reserved Map Keys:

    UriTemplate::expand('{+x*}', ['x' => ['a/b' => 'v']]);
    // 2.0 Result: a/b=v (1.x would have encoded the key as a%2Fb=v)
    UriTemplate::expand('/files/{+path}', ['path' => 'a/b']);