Lines 100.00% 18 / 18
Methods 100.00% 6 / 6
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 count 100.00% 1 / 1 100.00% 1 / 1 1
 max 100.00% 1 / 1 100.00% 1 / 1 1
 min 100.00% 1 / 1 100.00% 1 / 1 1
 sum 100.00% 1 / 1 100.00% 1 / 1 1
 avg 100.00% 1 / 1 100.00% 1 / 1 1
 __construct 100.00% 13 / 13 100.00% 1 / 1 6
15final class Aggregate
16{
17    /**
18     * Count rows (or non-null values of a column).
19     *
20     * @param  string  $column
21     * @param  string|null  $alias
22     * @return self
23     */
24    public static function count(string $column = '*', ?string $alias = null): self
25    {
26        return new self('count', $column, $alias);
27    }
28
29    /**
30     * Maximum of a column's values.
31     *
32     * @param  string  $column
33     * @param  string|null  $alias
34     * @return self
35     */
36    public static function max(string $column, ?string $alias = null): self
37    {
38        return new self('max', $column, $alias);
39    }
40
41    /**
42     * Minimum of a column's values.
43     *
44     * @param  string  $column
45     * @param  string|null  $alias
46     * @return self
47     */
48    public static function min(string $column, ?string $alias = null): self
49    {
50        return new self('min', $column, $alias);
51    }
52
53    /**
54     * Sum of a column's values.
55     *
56     * @param  string  $column
57     * @param  string|null  $alias
58     * @return self
59     */
60    public static function sum(string $column, ?string $alias = null): self
61    {
62        return new self('sum', $column, $alias);
63    }
64
65    /**
66     * Average of a column's values.
67     *
68     * @param  string  $column
69     * @param  string|null  $alias
70     * @return self
71     */
72    public static function avg(string $column, ?string $alias = null): self
73    {
74        return new self('avg', $column, $alias);
75    }
76
77    /**
78     * Create an aggregate.
79     *
80     * @param  string  $function  The aggregate function name — any bare SQL identifier.
81     * @param  string|Expression  $column  The column to aggregate, or an Expression for complex arguments.
82     * @param  string|null  $alias  The result column name; derived from the call text when null.
83     * @throws \InvalidArgumentException
84     */
85    public function __construct(
86        public readonly string $function,
87        public readonly string|Expression $column,
88        public readonly ?string $alias = null,
89    ) {
90        if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $function) !== 1) {
91            throw new \InvalidArgumentException(
92                "Aggregate function must be a bare SQL identifier; got [{$function}]."
93            );
94        }
95
96        // Column: `*`, an identifier path (`age`, `users.age`, `users.*`),
97        // or `distinct <path>` — validated at DECLARATION. An Expression is
98        // raw SQL by contract and passes through unvalidated (the same
99        // trust model as a raw select).
100        if (!$column instanceof Expression
101            && preg_match('/^(distinct\s+)?(\*|[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*|\.\*)?)$/i', $column) !== 1
102        ) {
103            throw new \InvalidArgumentException(
104                "Aggregate column must be *, an identifier path, `distinct <path>`, or an Expression; got [{$column}]."
105            );
106        }
107
108        if ($alias !== null && preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $alias) !== 1) {
109            throw new \InvalidArgumentException(
110                "Aggregate alias must be a bare identifier; got [{$alias}]."
111            );
112        }
113    }
114}