{"record":{"id":"6c988a63e478df23","repo":"phalcon/cphalcon","slug":"routes-must-be-an-array","errorCode":null,"errorMessage":"'routes' must be an array","messagePattern":"'routes' must be an array","errorType":"exception","errorClass":"Phalcon\\Mvc\\Router\\Exceptions\\ConfigKeyMustBeArray","httpStatus":null,"severity":"error","filePath":"phalcon/Mvc/Router.zep","lineNumber":1775,"sourceCode":"            throw new InvalidConfigSource();\n        }\n\n        if isset config[\"removeExtraSlashes\"] {\n            let removeExtra = config[\"removeExtraSlashes\"];\n            this->removeExtraSlashes((bool) removeExtra);\n        }\n\n        if isset config[\"defaults\"] {\n            let defaults = config[\"defaults\"];\n            if typeof defaults !== \"array\" {\n                throw new ConfigKeyMustBeArray(\"defaults\");\n            }\n            this->setDefaults(defaults);\n        }\n\n        if fetch routes, config[\"routes\"] {\n            if typeof routes !== \"array\" {\n                throw new ConfigKeyMustBeArray(\"routes\");\n            }\n            for routeData in routes {\n                this->addRouteFromConfig(routeData);\n            }\n        }\n\n        if fetch groups, config[\"groups\"] {\n            if typeof groups !== \"array\" {\n                throw new ConfigKeyMustBeArray(\"groups\");\n            }\n            for groupData in groups {\n                this->mountGroupFromConfig(groupData);\n            }\n        }\n\n        if fetch notFoundPaths, config[\"notFound\"] {\n            this->notFound(notFoundPaths);\n        }","sourceCodeStart":1757,"sourceCodeEnd":1793,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Mvc/Router.zep#L1757-L1793","documentation":"In loadFromConfig(), the 'routes' key must be an array of route definition arrays (each with at least 'pattern' and 'paths'). If config['routes'] exists but is anything other than an array, ConfigKeyMustBeArray('routes') is thrown before any route is processed.","triggerScenarios":"\"routes\": {...} decoded as a JSON object; a single route definition passed directly under 'routes' instead of wrapped in a list; routes serialized as a string.","commonSituations":"json_decode without associative flag; writing a single route object under 'routes' ({\"pattern\": ..., \"paths\": ...}) instead of a one-element list ([{...}]); merging configs where a later override replaced the list with a scalar.","solutions":["Wrap route entries in a list: \"routes\": [{\"pattern\": \"/api\", \"paths\": {...}}, ...]","Decode JSON with json_decode($json, true) so objects inside 'routes' become arrays too","Pre-validate the config shape (see validation snippet) and report the offending file/line"],"exampleFix":"// before\n{\n    \"routes\": {\n        \"pattern\": \"/api/users\",\n        \"paths\": {\"controller\": \"users\"}\n    }\n}\n\n// after\n{\n    \"routes\": [\n        {\n            \"pattern\": \"/api/users\",\n            \"paths\": {\"controller\": \"users\"}\n        }\n    ]\n}","handlingStrategy":"validation","validationCode":"// every entry under 'routes' must itself be an array containing pattern + paths\n$routes = $config['routes'] ?? [];\nif (!is_array($routes)) {\n    throw new InvalidArgumentException(\"'routes' must be an array of route definitions\");\n}\nforeach ($routes as $i => $entry) {\n    if (!is_array($entry) || !isset($entry['pattern'], $entry['paths'])) {\n        throw new InvalidArgumentException(\"routes[$i] must contain 'pattern' and 'paths'\");\n    }\n}\n\n$router->loadFromConfig($config);","typeGuard":"function routesAreValid(array $config): bool\n{\n    if (!isset($config['routes'])) {\n        return true;\n    }\n    if (!is_array($config['routes'])) {\n        return false;\n    }\n    foreach ($config['routes'] as $entry) {\n        if (!is_array($entry) || !isset($entry['pattern'], $entry['paths'])) {\n            return false;\n        }\n    }\n    return true;\n}","tryCatchPattern":"try {\n    $router->loadFromConfig($config);\n} catch (\\Phalcon\\Mvc\\Router\\Exceptions\\ConfigKeyMustBeArray $e) {\n    $logger->error('Router config rejected: ' . $e->getMessage());\n    throw $e;\n}","preventionTips":["Wrap every route entry in [ { ... } ] — one object per list element","Use associative decoding for JSON and sequence syntax (dashed lists) in YAML","Validate the routes file schema before deploy, not at request time"],"tags":["phalcon","router","config","routes","validation"],"backgroundTag":"config-validation-failed","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}