octobercms/october · error · SystemException

The metric code can only contain Latin letters, numbers and

Error message

The metric code can only contain Latin letters, numbers and underscore. The first character must be a letter

What it means

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.

Source

Thrown at modules/dashboard/classes/ReportMetric.php:66

    private $displayFormatter;

    /**
     * __construct a new metric instance.
     * @param string $code Specifies the metric referral code.
     * @param string $databaseColumnName Specifies the column name in the data source table.
     * @param string $displayName Specifies the metric name used in reports.
     * @param string $aggregateFunction Specifies the aggregate function for the metric.
     * @param ?array $intlFormatOptions Client-side formatting options, compatible with the Intl.NumberFormat() constructor options argument.
     * Skip the argument to use the default formatting options.
     */
    public function __construct(string $code, string $databaseColumnName, string $displayName, string $aggregateFunction, ?array $intlFormatOptions = null)
    {
        if (!strlen($code)) {
            throw new SystemException('The metric code cannot be empty.');
        }

        if (!preg_match('/^[a-z][a-z0-9_]+$/i', $code)) {
            throw new SystemException('The metric code can only contain Latin letters, numbers and underscore. The first character must be a letter');
        }

        if (!strlen($databaseColumnName)) {
            throw new SystemException('The database column name cannot be empty.');
        }

        if (!strlen($displayName)) {
            throw new SystemException('The display name cannot be empty.');
        }

        if (!strlen($aggregateFunction)) {
            throw new SystemException('The aggregate function cannot be empty.');
        }

        $knownAggregateFunctions = [
            self::AGGREGATE_SUM,
            self::AGGREGATE_AVG,
            self::AGGREGATE_MIN,

View on GitHub (pinned to b608633a7e)

Solutions

  1. 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')
  2. Build codes from stable keys rather than free-form labels
  3. Cover metric definitions with a small unit test that runs the regex before release

Example fix

// before
$metric = new ReportMetric('1st-quarter-sales', 'oc_amount', 'Q1 Sales', 'sum');

// after
$metric = new ReportMetric('q1_sales', 'oc_amount', 'Q1 Sales', 'sum');

// helper for dynamic codes
$code = preg_replace('/[^a-zA-Z0-9_]+/', '_', $label);      // spaces/hyphens -> underscore
$code = preg_replace('/^[^a-zA-Z]+/', '', $code) ?: 'metric'; // drop leading digits
$code = substr($code, 0, 1) . max(substr($code, 1), 'x');     // guarantee >= 2 chars
Defensive patterns

Strategy: type-guard

Validate before calling

$code = preg_replace('/[^a-zA-Z0-9_]+/', '_', (string) $rawCode);
$code = preg_replace('/^[^a-zA-Z]+/', '', $code);
if (strlen($code) < 2) {
    $code = str_pad($code, 2, 'x');
}
if (!ReportMetric::isValidCode($code)) { // see typeGuard
    throw new InvalidArgumentException("Invalid metric code '{$rawCode}'");
}

Type guard

function isValidMetricCode(string $code): bool
{
    return (bool) preg_match('/^[a-z][a-z0-9_]+$/i', $code);
}

Prevention

When it happens

Trigger: 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.

Common situations: Machine-generated codes starting with digits; codes derived from translated or human-readable labels containing spaces/hyphens; very short auto-generated codes ('x', 'a').

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/6d93e940b8a8aa54. Report an issue: GitHub.