{"record":{"id":"7295a67a4189889c","repo":"phalcon/cphalcon","slug":"groups-must-be-an-array","errorCode":null,"errorMessage":"'groups' must be an array","messagePattern":"'groups' must be an array","errorType":"exception","errorClass":"Phalcon\\Mvc\\Router\\Exceptions\\ConfigKeyMustBeArray","httpStatus":null,"severity":"error","filePath":"phalcon/Mvc/Router.zep","lineNumber":1784,"sourceCode":"            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        }\n\n        return this;\n    }\n\n    /**\n     * Mounts a group of routes in the router\n     *\n     * @param GroupInterface group\n     *","sourceCodeStart":1766,"sourceCodeEnd":1802,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Mvc/Router.zep#L1766-L1802","documentation":"In loadFromConfig(), the optional 'groups' key must be an array of group definition arrays (each may carry 'prefix', 'hostname', 'routes', and per-route entries). If config['groups'] exists but is not an array, ConfigKeyMustBeArray('groups') is thrown before any group is mounted.","triggerScenarios":"\"groups\": {\"prefix\": \"/admin\", \"routes\": [...} (a single group object instead of a list of groups); groups decoded to stdClass by json_decode without true; groups defined as a comma-separated string.","commonSituations":"Same JSON object-vs-array decode issue as 'routes'; defining one group inline without the enclosing [ ]; config merges that overwrite the groups list with a non-array.","solutions":["Wrap each group in a list: \"groups\": [{\"prefix\": \"/admin\", \"routes\": [...]}, ...]","Use json_decode($json, true) so nested group objects arrive as arrays","Pre-validate the 'groups' key is a list before calling loadFromConfig"],"exampleFix":"// before\n{\n    \"groups\": {\n        \"prefix\": \"/admin\",\n        \"routes\": [{\"pattern\": \"/dashboard\", \"paths\": {...}}]\n    }\n}\n\n// after\n{\n    \"groups\": [\n        {\n            \"prefix\": \"/admin\",\n            \"routes\": [{\"pattern\": \"/dashboard\", \"paths\": {...}}]\n        }\n    ]\n}","handlingStrategy":"validation","validationCode":"// 'groups' must be a list of group definitions, each with an array 'routes' (optional)\n$groups = $config['groups'] ?? [];\nif (!is_array($groups)) {\n    throw new InvalidArgumentException(\"'groups' must be an array of group definitions\");\n}\nforeach ($groups as $i => $group) {\n    if (!is_array($group) || (isset($group['routes']) && !is_array($group['routes']))) {\n        throw new InvalidArgumentException(\"groups[$i] invalid: 'routes' must be an array\");\n    }\n}\n\n$router->loadFromConfig($config);","typeGuard":"function groupsAreValid(array $config): bool\n{\n    if (!isset($config['groups'])) {\n        return true;\n    }\n    if (!is_array($config['groups'])) {\n        return false;\n    }\n    foreach ($config['groups'] as $group) {\n        if (!is_array($group) || (isset($group['routes']) && !is_array($group['routes']))) {\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":["Model groups as a list of maps; each group's 'routes' is itself a list","Decode with associative=true everywhere route config is parsed","Lint YAML/JSON route files in CI to catch object-vs-list mixups"],"tags":["phalcon","router","config","groups","validation"],"backgroundTag":"config-validation-failed","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}