{"record":{"id":"6d93e940b8a8aa54","repo":"octobercms/october","slug":"the-metric-code-can-only-contain-latin-letters-nu","errorCode":null,"errorMessage":"The metric code can only contain Latin letters, numbers and underscore. The first character must be a letter","messagePattern":"The metric code can only contain Latin letters, numbers and underscore\\. The first character must be a letter","errorType":"exception","errorClass":"SystemException","httpStatus":null,"severity":"error","filePath":"modules/dashboard/classes/ReportMetric.php","lineNumber":66,"sourceCode":"    private $displayFormatter;\n\n    /**\n     * __construct a new metric instance.\n     * @param string $code Specifies the metric referral code.\n     * @param string $databaseColumnName Specifies the column name in the data source table.\n     * @param string $displayName Specifies the metric name used in reports.\n     * @param string $aggregateFunction Specifies the aggregate function for the metric.\n     * @param ?array $intlFormatOptions Client-side formatting options, compatible with the Intl.NumberFormat() constructor options argument.\n     * Skip the argument to use the default formatting options.\n     */\n    public function __construct(string $code, string $databaseColumnName, string $displayName, string $aggregateFunction, ?array $intlFormatOptions = null)\n    {\n        if (!strlen($code)) {\n            throw new SystemException('The metric code cannot be empty.');\n        }\n\n        if (!preg_match('/^[a-z][a-z0-9_]+$/i', $code)) {\n            throw new SystemException('The metric code can only contain Latin letters, numbers and underscore. The first character must be a letter');\n        }\n\n        if (!strlen($databaseColumnName)) {\n            throw new SystemException('The database column name cannot be empty.');\n        }\n\n        if (!strlen($displayName)) {\n            throw new SystemException('The display name cannot be empty.');\n        }\n\n        if (!strlen($aggregateFunction)) {\n            throw new SystemException('The aggregate function cannot be empty.');\n        }\n\n        $knownAggregateFunctions = [\n            self::AGGREGATE_SUM,\n            self::AGGREGATE_AVG,\n            self::AGGREGATE_MIN,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/dashboard/classes/ReportMetric.php#L48-L84","documentation":"ReportMetric codes must match ^[a-z][a-z0-9_]+$/i: they start with a letter, continue with letters (either case), digits, or underscores — and, because of the '+', must be at least TWO characters long. Violations throw immediately in the constructor.","triggerScenarios":"new ReportMetric('1total', ...) (leading digit), new ReportMetric('total-amount', ...) (hyphen), new ReportMetric('total amount', ...) (space), or even new ReportMetric('x', ...) — a single-character code fails because the pattern requires one letter PLUS at least one more character.","commonSituations":"Machine-generated codes starting with digits; codes derived from translated or human-readable labels containing spaces/hyphens; very short auto-generated codes ('x', 'a').","solutions":["Normalize the code: strip non-alphanumerics, convert to underscores, prefix a letter if it starts with a digit, and ensure length >= 2 (e.g. 'total_amount', 'm1')","Build codes from stable keys rather than free-form labels","Cover metric definitions with a small unit test that runs the regex before release"],"exampleFix":"// before\n$metric = new ReportMetric('1st-quarter-sales', 'oc_amount', 'Q1 Sales', 'sum');\n\n// after\n$metric = new ReportMetric('q1_sales', 'oc_amount', 'Q1 Sales', 'sum');\n\n// helper for dynamic codes\n$code = preg_replace('/[^a-zA-Z0-9_]+/', '_', $label);      // spaces/hyphens -> underscore\n$code = preg_replace('/^[^a-zA-Z]+/', '', $code) ?: 'metric'; // drop leading digits\n$code = substr($code, 0, 1) . max(substr($code, 1), 'x');     // guarantee >= 2 chars","handlingStrategy":"type-guard","validationCode":"$code = preg_replace('/[^a-zA-Z0-9_]+/', '_', (string) $rawCode);\n$code = preg_replace('/^[^a-zA-Z]+/', '', $code);\nif (strlen($code) < 2) {\n    $code = str_pad($code, 2, 'x');\n}\nif (!ReportMetric::isValidCode($code)) { // see typeGuard\n    throw new InvalidArgumentException(\"Invalid metric code '{$rawCode}'\");\n}","typeGuard":"function isValidMetricCode(string $code): bool\n{\n    return (bool) preg_match('/^[a-z][a-z0-9_]+$/i', $code);\n}","tryCatchPattern":null,"preventionTips":["Derive codes from stable machine keys, never free-form labels","Watch the two-character minimum — single-letter codes like 'x' are rejected","Filter hyphens/spaces to underscores and strip leading digits when generating codes dynamically"],"tags":["php","dashboard","report","metric","identifier","regex"],"backgroundTag":"invalid-identifier-format","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}