4545use tool_monitoring \form \config as config_form ;
4646
4747/**
48- * Basic implementation of the {@see metric_config} interface.
48+ * Helper base class for simple metric configurations; fully implements the ** {@see metric_config}** interface.
4949 *
50- * A concrete subclass must simply define a constructor with
51- * {@link https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion promoted parameters} .
52- * Assuming all of those are public and there are no additional public properties on the config object, the JSON (de-)serialization
53- * simply maps those properties to keys in a JSON object .
50+ * During JSON serialization, the **public** properties of an instance are turned into the JSON object (saved in the DB).
51+ * See the {@see self::jsonSerialize `jsonSerialize`} method for details .
52+ * For JSON deserialization, the JSON object is expected to provide **keys that map to the constructor parameters** of the class.
53+ * See the {@see self::from_json `from_json`} method for details .
5454 *
55- * The definition of the Moodle form fields and their validation logic are inferred from those properties as well.
56- * For translatable field labels, a string with the ID `metric:<config-class-name>:<property-name>` must exist in the component
57- * defining the config class. If additionally a string with the ID `metric:<config-class-name>:<property-name>_help` exists,
58- * a help button is added to the form field with the corresponding text.
55+ * The same logic applies to the {@see self::to_form_data `to_form_data`} and {@see self::with_form_data `with_form_data`} methods.
56+ * The former returns the **public** properties of a config instance, the latter expects the form data to have properties that
57+ * **map to the constructor parameters** of the class.
5958 *
60- * Consider the following example config class defined in a plugin called `local_example`:
59+ * This means, a concrete subclass can be simply defined as a dataclass that has a constructor with public
60+ * {@link https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion promoted parameters}.
61+ * For example:
6162 *
6263 * ```
6364 * class my_metric_config extends simple_metric_config {
7273 *
7374 * Resulting/expected form data: `['foo' => 'bar', 'spam' => '3.14']`.
7475 *
75- * The Moodle form definition would be similar to this:
76+ * The definition of the Moodle form fields and their validation logic are inferred from those properties as well.
77+ * For translatable field labels, a string with the ID `metric:<config-class-name>:<property-name>` must exist in the component
78+ * defining the config class. If additionally a string with the ID `metric:<config-class-name>:<property-name>_help` exists,
79+ * a help button is added to the form field with the corresponding text.
80+ *
81+ * In a `local_example` plugin, the Moodle form definition for the example config above would be similar to this:
82+ *
7683 * ```
7784 * $mform->addElement('text', 'foo', get_string('metric:my_metric_config:foo', 'local_example'));
7885 * $mform->addHelpButton('foo', 'metric:my_metric_config:foo', 'local_example');
7986 * $mform->setType('foo', PARAM_TEXT);
80- * $mform->addRule('foo', null, 'required', null, 'client');
8187 *
8288 * $mform->addElement('text', 'spam', get_string('metric:my_metric_config:spam', 'local_example'));
8389 * $mform->addHelpButton('spam', 'metric:my_metric_config:spam', 'local_example');
8490 * $mform->setType('spam', PARAM_FLOAT);
8591 * $mform->addRule('spam', null, 'numeric', null, 'client');
86- * $mform->addRule('spam', null, 'required', null, 'client');
8792 * ```
8893 *
89- * For more advanced definition/validation options, override {@see extend_form_definition} and {@see extend_form_validation}.
94+ * The automatic field inference currently supports the following constructor parameter types:
95+ * - `bool` gives an `advcheckbox`/`PARAM_BOOL` field.
96+ * - `float` gives a `text`/`PARAM_FLOAT` field with client-side numeric validation.
97+ * - `int` gives a `text`/`PARAM_INT` field with client-side numeric validation.
98+ * - `string` gives a `text`/`PARAM_TEXT` field.
99+ *
100+ * Any other type annotation is treated as `string`, which results in a `text`/`PARAM_TEXT` field without any validation.
101+ *
102+ * For more advanced definition and validation options, the {@see self::extend_form_definition `extend_form_definition`} and
103+ * {@see self::extend_form_validation `extend_form_validation`} methods can still be overridden/extended.
90104 *
91105 * @package tool_monitoring
92106 * @copyright 2025 MootDACH DevCamp
98112 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
99113 */
100114abstract class simple_metric_config implements metric_config {
101- /** @var array<string, array<string, ReflectionParameter>> Cache for config parameters indexed by config class name. */
102- private static array $ configparameters = [];
115+ /** @var array<string, array<string, ReflectionParameter>> Cache for constructor parameters indexed by config class name. */
116+ private static array $ constructorparameters = [];
103117
104118 /**
105- * Reflects the class, analyzes the constructor of the config class , and returns all parameters indexed by name.
119+ * Reflects the calling class, analyzes its constructor, and returns all parameters indexed by name.
106120 *
107121 * Caches the result after the first call.
108122 *
109123 * @return array<string, ReflectionParameter> Constructor parameters indexed by name.
110124 * @throws simple_metric_config_constructor_missing
111125 */
112- protected static function get_config_parameters (): array {
113- if (isset (self ::$ configparameters [static ::class])) {
114- return self ::$ configparameters [static ::class];
126+ private static function get_constructor_parameters (): array {
127+ if (isset (self ::$ constructorparameters [static ::class])) {
128+ return self ::$ constructorparameters [static ::class];
115129 }
116130 $ class = new ReflectionClass (static ::class);
117131 if (is_null ($ constructor = $ class ->getConstructor ())) {
@@ -122,7 +136,7 @@ protected static function get_config_parameters(): array {
122136 column_key: null ,
123137 index_key: 'name ' ,
124138 );
125- self ::$ configparameters [static ::class] = $ parameters ;
139+ self ::$ constructorparameters [static ::class] = $ parameters ;
126140 return $ parameters ;
127141 }
128142
@@ -154,7 +168,7 @@ public static function from_json(string $json): static {
154168 throw new json_invalid ();
155169 }
156170 $ args = [];
157- foreach (self ::get_config_parameters () as $ name => $ param ) {
171+ foreach (self ::get_constructor_parameters () as $ name => $ param ) {
158172 if (array_key_exists ($ name , $ data )) {
159173 $ args [$ name ] = $ data [$ name ];
160174 } else if (!$ param ->isOptional ()) {
@@ -177,7 +191,7 @@ public static function from_json(string $json): static {
177191 #[\Override]
178192 public static function with_form_data (stdClass $ formdata ): static {
179193 $ args = [];
180- foreach (self ::get_config_parameters () as $ name => $ param ) {
194+ foreach (self ::get_constructor_parameters () as $ name => $ param ) {
181195 if (property_exists ($ formdata , $ name )) {
182196 $ args [$ name ] = $ formdata ->$ name ;
183197 } else if (!$ param ->isOptional ()) {
@@ -230,8 +244,11 @@ public static function extend_form_definition(config_form $configform, MoodleQui
230244 $ cls = substr ($ cls , $ pos + 1 );
231245 }
232246 $ stringmanager = get_string_manager ();
233- foreach (self ::get_config_parameters () as $ paramname => $ param ) {
247+ foreach (self ::get_constructor_parameters () as $ paramname => $ param ) {
234248 $ labelid = "metric: $ cls: $ paramname " ;
249+ if (PHPUNIT_TEST ) {
250+ $ labelid = "testing: $ labelid " ;
251+ }
235252 self ::add_field_to_form ($ mform , $ param , new lang_string ($ labelid , $ component ));
236253 // Optionally, add a help button if the defining component has a corresponding language string.
237254 if ($ stringmanager ->string_exists ("{$ labelid }_help " , $ component )) {
0 commit comments