phalcon/cphalcon · error · Phalcon\Mvc\Router\Exceptions\WrongPathsKey

Wrong key in paths: {part}

Error message

Wrong key in paths: {part}

What it means

Thrown during router matching (combined-regex fast path, which only runs when no events manager is attached and the route bucket has no hostname constraints) when a matched route's paths array contains a key that is not a string. Route paths must be an associative map of parameter name => capture position (e.g. ['controller' => 1, 'action' => 2]); a plain list array has integer keys, so the router cannot resolve parameter names and rejects it at match time, not at add() time.

Source

Thrown at phalcon/Mvc/Router.zep:1429

                        throw new BeforeMatchNotCallable();
                    }

                    if !{combinedBeforeMatch}(handledUri, combinedRoute, this) {
                        continue;
                    }
                }

                let combinedPaths      = combinedRoute->getPaths(),
                    parts              = combinedPaths,
                    matches            = combinedMatchesLocal,
                    combinedConverters = combinedRoute->getConverters(),
                    this->matches      = combinedMatchesLocal,
                    this->matchedRoute = combinedRoute,
                    routeFound         = true;

                for combinedPart, combinedPosition in combinedPaths {
                    if unlikely typeof combinedPart !== "string" {
                        throw new WrongPathsKey(combinedPart);
                    }

                    if typeof combinedPosition !== "string" && typeof combinedPosition !== "integer" {
                        continue;
                    }

                    if fetch combinedMatchPosition, combinedMatchesLocal[combinedPosition] {
                        if typeof combinedConverters === "array" && fetch combinedConverter, combinedConverters[combinedPart] {
                            let parts[combinedPart] = {combinedConverter}(combinedMatchPosition);
                            continue;
                        }

                        let parts[combinedPart] = combinedMatchPosition;
                    } else {
                        if typeof combinedConverters === "array" && fetch combinedConverter, combinedConverters[combinedPart] {
                            let parts[combinedPart] = {combinedConverter}(combinedPosition);
                        } elseif typeof combinedPosition === "integer" {
                            unset parts[combinedPart];

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Change the route's second argument to an associative array: ['controller' => 'posts', 'action' => 'show', 'id' => 1] where numeric VALUES are regex capture positions
  2. If paths come from config or JSON, ensure keys are quoted strings ("controller": ...) so they survive decoding as strings
  3. Audit every $router->add/addGet/addPost/... call or config 'paths' entry feeding this route and replace list syntax with a name => position map
  4. As a stopgap to surface the bug earlier (outside production), iterate your routes at boot and assert all array keys of getPaths() are strings

Example fix

// before
$router->add(
    '/admin/:controller/:action/:params',
    ['controller', 'action', 'params']
);

// after
$router->add(
    '/admin/:controller/:action/:params',
    ['controller' => 1, 'action' => 2, 'params' => 3]
);
Defensive patterns

Strategy: validation

Validate before calling

// before registering routes, assert every paths array is a name => position map
function assertValidPaths(array $paths): void
{
    foreach ($paths as $key => $_) {
        if (!is_string($key)) {
            throw new InvalidArgumentException(
                'Route paths must use string keys, got key: ' . var_export($key, true)
            );
        }
    }
}

foreach ($routesToRegister as $i => $def) {
    assertValidPaths($def['paths'] ?? []);
    $router->add($def['pattern'], $def['paths']);
}

Type guard

function isValidPaths(mixed $paths): bool
{
    if (!is_array($paths)) {
        return false;
    }

    foreach (array_keys($paths) as $key) {
        if (!is_string($key)) {
            return false;
        }
    }

    return true;
}

Try / catch

try {
    $router->handle($uri);
} catch (\Phalcon\Mvc\Router\Exceptions\WrongPathsKey $e) {
    $logger->error('Bad route paths definition: ' . $e->getMessage());
    // fail the request explicitly; do not serve a half-matched route
    $response->setStatusCode(500)->send();
}

Prevention

When it happens

Trigger: Defining a route via $router->add('/api/users', ['controller', 'action']) (list instead of map) and then issuing a request that matches it; also paths built with array_values(), array_merge() into a list, or decoded from JSON that produced numeric keys. The throw happens inside Router::handle() only after the URI actually matches, so the defect can sit dormant until traffic hits the route.

Common situations: Copy-pasting a paths example but dropping the '=>' pairs; building paths dynamically from a loop that uses append ($paths[] = ...) instead of named keys; YAML/JSON route files where quotes were lost and keys became integers; refactoring from short string paths ('Posts::show') to arrays and using wrong syntax.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/1d9788273a5bf986. Report an issue: GitHub.