diff --git a/lib/moodlelib.php b/lib/moodlelib.php index f22a17df7c6..d1a26fa6ab1 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -7727,6 +7727,37 @@ function component_callback_exists($component, $function) { return false; } +/** + * Call the specified callback method on the provided class. + * + * If the callback returns null, then the default value is returned instead. + * If the class does not exist, then the default value is returned. + * + * @param string $classname The name of the class to call upon. + * @param string $methodname The name of the staticically defined method on the class. + * @param array $params The arguments to pass into the method. + * @param mixed $default The default value. + * @return mixed The return value. + */ +function component_class_callback($classname, $methodname, array $params, $default = null) { + if (!class_exists($classname)) { + return $default; + } + + if (!method_exists($classname, $methodname)) { + return $default; + } + + $fullfunction = $classname . '::' . $methodname; + $result = call_user_func_array($fullfunction, $params); + + if (null === $result) { + return $default; + } else { + return $result; + } +} + /** * Checks whether a plugin supports a specified feature. * diff --git a/lib/tests/fixtures/component_class_callback_example.php b/lib/tests/fixtures/component_class_callback_example.php new file mode 100644 index 00000000000..cf88df6e3cf --- /dev/null +++ b/lib/tests/fixtures/component_class_callback_example.php @@ -0,0 +1,61 @@ +. + +/** + * Fixtures for component_class_callback tests. + * + * @package core + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class fixture for component_class_callback. + * + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class test_component_class_callback_example { + /** + * Function which returns the input value. + * + * @param mixed $output + * @return mixed + */ + public static function method_returns_value($output) { + return $output; + } + + /** + * Function which returns all args. + * + * @return mixed + */ + public static function method_returns_all_params() { + return count(func_get_args()); + } +} + +/** + * Class fixture for component_class_callback which extends another class. + * + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class test_component_class_callback_child_example extends test_component_class_callback_example { +} diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index e59a5ee702e..8977b7815a9 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -3588,4 +3588,130 @@ class core_moodlelib_testcase extends advanced_testcase { $a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]); $this->assertEquals($a, unserialize_array(serialize($a))); } + + /** + * Test that the component_class_callback returns the correct default value when the class was not found. + * + * @dataProvider component_class_callback_default_provider + * @param $default + */ + public function test_component_class_callback_not_found($default) { + $this->assertSame($default, component_class_callback('thisIsNotTheClassYouWereLookingFor', 'anymethod', [], $default)); + } + + /** + * Test that the component_class_callback returns the correct default value when the class was not found. + * + * @dataProvider component_class_callback_default_provider + * @param $default + */ + public function test_component_class_callback_method_not_found($default) { + require_once(__DIR__ . '/fixtures/component_class_callback_example.php'); + + $this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'this_is_not_the_method_you_were_looking_for', ['abc'], $default)); + } + + /** + * Test that the component_class_callback returns the default when the method returned null. + * + * @dataProvider component_class_callback_default_provider + * @param $default + */ + public function test_component_class_callback_found_returns_null($default) { + require_once(__DIR__ . '/fixtures/component_class_callback_example.php'); + + $this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'method_returns_value', [null], $default)); + $this->assertSame($default, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_value', [null], $default)); + } + + /** + * Test that the component_class_callback returns the expected value and not the default when there was a value. + * + * @dataProvider component_class_callback_data_provider + * @param $default + */ + public function test_component_class_callback_found_returns_value($value) { + require_once(__DIR__ . '/fixtures/component_class_callback_example.php'); + + $this->assertSame($value, component_class_callback(test_component_class_callback_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for')); + $this->assertSame($value, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for')); + } + + /** + * Test that the component_class_callback handles multiple params correctly. + * + * @dataProvider component_class_callback_multiple_params_provider + * @param $default + */ + public function test_component_class_callback_found_accepts_multiple($params, $count) { + require_once(__DIR__ . '/fixtures/component_class_callback_example.php'); + + $this->assertSame($count, component_class_callback(test_component_class_callback_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for')); + $this->assertSame($count, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for')); + } + + /** + * Data provider with list of default values for user in component_class_callback tests. + * + * @return array + */ + public function component_class_callback_default_provider() { + return [ + 'null' => [null], + 'empty string' => [''], + 'string' => ['This is a string'], + 'int' => [12345], + 'stdClass' => [(object) ['this is my content']], + 'array' => [['a' => 'b',]], + ]; + } + + /** + * Data provider with list of default values for user in component_class_callback tests. + * + * @return array + */ + public function component_class_callback_data_provider() { + return [ + 'empty string' => [''], + 'string' => ['This is a string'], + 'int' => [12345], + 'stdClass' => [(object) ['this is my content']], + 'array' => [['a' => 'b',]], + ]; + } + + /** + * Data provider with list of default values for user in component_class_callback tests. + * + * @return array + */ + public function component_class_callback_multiple_params_provider() { + return [ + 'empty array' => [ + [], + 0, + ], + 'string value' => [ + ['one'], + 1, + ], + 'string values' => [ + ['one', 'two'], + 2, + ], + 'arrays' => [ + [[], []], + 2, + ], + 'nulls' => [ + [null, null, null, null], + 4, + ], + 'mixed' => [ + ['a', 1, null, (object) [], []], + 5, + ], + ]; + } }