MDL-84537 reportbuilder: pass options to aggregation type constructor.

Amendments to the column class allow reports/entities to specify these
options for column instances, which are then passed to the aggregation
type when it is applied.
This commit is contained in:
Paul Holden
2025-03-09 20:18:54 +00:00
parent 8cb5d000a1
commit bfabec2b65
11 changed files with 86 additions and 22 deletions
@@ -0,0 +1,18 @@
issueNumber: MDL-84537
notes:
core_reportbuilder:
- message: >-
Aggregation types can access passed options set via the base class
constructor in the `$this->options[]` class property. As such, their
`format_value` method is no longer static and is always called from an
instantiated class instance
type: changed
- message: >-
New `$options` argument added to the
`column::set_aggregation` method for system reports, to set aggregation
type-specific options
Report entities can call new `column::set_aggregation_options` to
achieve the same
type: changed
+3 -1
View File
@@ -134,11 +134,13 @@ abstract class datasource extends base {
" {$instance->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
$columnaggregation = $column->get('aggregation');
// We should clone the report column to ensure if it's added twice to a report, each operates independently.
$this->activecolumns['values'][] = clone $instance
->set_index($index)
->set_persistent($column)
->set_aggregation($column->get('aggregation'));
->set_aggregation($columnaggregation, $instance->get_aggregation_options($columnaggregation));
}
}
@@ -85,7 +85,7 @@ class avg extends base {
* @param int $columntype
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
public function format_value($value, array $values, array $callbacks, int $columntype) {
if (reset($values) === null) {
return null;
}
@@ -30,6 +30,18 @@ use core_reportbuilder\local\report\column;
*/
abstract class base {
/**
* Constructor
*
* @param array $options Aggregation type specific options
*/
public function __construct(
/** @var array Aggregation type specific options */
protected readonly array $options = [],
) {
}
/**
* Return the class name of the aggregation type
*
@@ -153,7 +165,7 @@ abstract class base {
* @param int $columntype The original type of the column, to ensure it is preserved for callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
public function format_value($value, array $values, array $callbacks, int $columntype) {
foreach ($callbacks as $callback) {
[$callable, $arguments] = $callback;
$value = ($callable)($value, (object) $values, $arguments, static::get_class_name());
@@ -79,7 +79,7 @@ class count extends base {
* @param int $columntype
* @return int
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): int {
public function format_value($value, array $values, array $callbacks, int $columntype): int {
return (int) reset($values);
}
}
@@ -93,7 +93,7 @@ class countdistinct extends base {
* @param int $columntype
* @return int
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): int {
public function format_value($value, array $values, array $callbacks, int $columntype): int {
return (int) reset($values);
}
}
@@ -93,7 +93,7 @@ class date extends base {
* @param int $columntype
* @return string
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): string {
public function format_value($value, array $values, array $callbacks, int $columntype): string {
return format::userdate($value, (object) [], get_string('strftimedaydate', 'core_langconfig'));
}
}
@@ -100,7 +100,7 @@ class groupconcat extends base {
* @param int $columntype
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
public function format_value($value, array $values, array $callbacks, int $columntype) {
$firstvalue = reset($values);
if ($firstvalue === null) {
return '';
@@ -82,7 +82,7 @@ class percent extends base {
* @param int $columntype
* @return string
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): string {
public function format_value($value, array $values, array $callbacks, int $columntype): string {
if (reset($values) === null) {
return '';
}
@@ -88,7 +88,7 @@ class sum extends base {
* @param int $columntype
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
public function format_value($value, array $values, array $callbacks, int $columntype) {
$firstvalue = reset($values);
if ($firstvalue === null) {
return null;
+45 -13
View File
@@ -76,7 +76,10 @@ final class column {
private $callbacks = [];
/** @var base|null $aggregation Aggregation type to apply to column */
private $aggregation = null;
private base|null $aggregation = null;
/** @var array[] $aggregationoptions Aggregation type options */
private array $aggregationoptions = [];
/** @var array $disabledaggregation Aggregation types explicitly disabled */
private $disabledaggregation = [];
@@ -358,7 +361,7 @@ final class column {
public function get_fields(): array {
$fieldsalias = $this->get_fields_sql_alias();
if (!empty($this->aggregation)) {
if ($this->aggregation !== null) {
$fieldsaliassql = array_column($fieldsalias, 'sql');
$field = reset($fieldsalias);
@@ -383,7 +386,7 @@ final class column {
* @throws coding_exception
*/
private function get_field_aggregation_sql(array $sqlfields): string {
if (empty($this->aggregation)) {
if ($this->aggregation === null) {
throw new coding_exception('Column aggregation is undefined');
}
@@ -447,7 +450,7 @@ final class column {
// To ensure cross-platform support for column aggregation, where the aggregation should also be grouped, we need
// to generate SQL from column fields and use it to generate aggregation SQL.
if (!empty($this->aggregation) && $this->aggregation::column_groupby()) {
if ($this->aggregation !== null && $this->aggregation::column_groupby()) {
if ($usealias) {
$this->set_groupby_sql($this->get_column_alias());
} else {
@@ -504,18 +507,25 @@ final class column {
* Set column aggregation type
*
* @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
* @param array|null $options Aggregation type options
* @return self
* @throws coding_exception For invalid aggregation type, or one that is incompatible with column type
*/
public function set_aggregation(?string $aggregation): self {
if (!empty($aggregation)) {
$aggregation = aggregation::get_full_classpath($aggregation);
if (!aggregation::valid($aggregation) || !$aggregation::compatible($this->get_type())) {
public function set_aggregation(?string $aggregation, ?array $options = null): self {
if ((string) $aggregation !== '') {
// Convert aggregation to full class instance for internal storage.
$aggregationclasspath = aggregation::get_full_classpath($aggregation);
if (!aggregation::valid($aggregationclasspath) || !$aggregationclasspath::compatible($this->get_type())) {
throw new coding_exception('Invalid column aggregation', $aggregation);
}
$options ??= $this->get_aggregation_options($aggregation);
$this->aggregation = new $aggregationclasspath($options);
} else {
$this->aggregation = null;
}
$this->aggregation = $aggregation;
return $this;
}
@@ -524,10 +534,32 @@ final class column {
*
* @return base|null
*/
public function get_aggregation(): ?string {
public function get_aggregation(): ?base {
return $this->aggregation;
}
/**
* Set options for the given aggregation type
*
* @param string $aggregation Type of aggregation, e.g. 'sum', 'count', etc
* @param array $options Aggregation type options
* @return self
*/
public function set_aggregation_options(string $aggregation, array $options): self {
$this->aggregationoptions[$aggregation] = $options;
return $this;
}
/**
* Get options for the given aggregation type
*
* @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
* @return array
*/
public function get_aggregation_options(?string $aggregation): array {
return $this->aggregationoptions[$aggregation] ?? [];
}
/**
* Set disabled aggregation methods for the column. Typically only those methods suitable for the current column type are
* available: {@see aggregation::get_column_aggregations}, however in some cases we may want to disable specific methods
@@ -585,7 +617,7 @@ final class column {
public function get_is_sortable(): bool {
// Defer sortable status to aggregation type if column is being aggregated.
if (!empty($this->aggregation)) {
if ($this->aggregation !== null) {
return $this->aggregation::sortable($this->issortable);
}
@@ -673,9 +705,9 @@ final class column {
$values = $this->get_values($row);
// If column is being aggregated then defer formatting to them, otherwise loop through all column callbacks.
if (!empty($this->aggregation)) {
if ($this->aggregation !== null) {
$value = self::get_default_value($values, $this->aggregation::get_column_type($this->get_type()));
$value = $this->aggregation::format_value($value, $values, $this->callbacks, $this->get_type());
$value = $this->aggregation->format_value($value, $values, $this->callbacks, $this->get_type());
} else {
$value = self::get_default_value($values, $this->get_type());
foreach ($this->callbacks as $callback) {