Skip to content

Webservice

Peterrahr edited this page Dec 9, 2024 · 16 revisions

Service - Connecting Javascript with PHP

The services are what specify what functions the JavaScript can call within PHP.
Link to Moodle's documentation of services

The 4 necessary files

When creating a service to allow JavaScript to call a function in PHP 4 files are necessary to create/edit.
These files are:

  • db/services.php (edit)
  • classes/external/name_of_class.php (create)
  • amd/src/repository.js (edit)
  • amd/src/JS_file_that_calls_php_function.js (create/edit)

db/services.php

The services.php file is the file that defines what functions that the PHP should allow to be called by the Javascript.
In this file there is a list of functions that is defined, here is an example of a function:

'mod_livequiz_save_question' => [
        'classname'   => 'mod_livequiz\external\save_question',
        'description' => 'Save a question.',
        'type'        => 'write',
        'ajax'        => true,
        'services'    => [MOODLE_OFFICIAL_MOBILE_SERVICE],
    ],

We only need to edit the classname and the description. Here it is important that the classname links to the correct class in the external folder.

classes/external/name_of_class.php

This is the class that will be called when calling from the JavaScript.
This class must have three methods: execute, execute_parameters, and execute_returns.

execute

This is the method that will be called from the JavaScript, this is where the logic should be placed. (One can make other methods within the class to call)
Here is an example of the execute method of the save_question class, that also calls a private static function of the class:

public static function execute(array $questiondata, int $lecturerid, int $quizid): array {
        self::validate_parameters(self::execute_parameters(), [
            'question' => $questiondata,
            'lecturerid' => $lecturerid,
            'quizid' => $quizid,
        ]);
        $services = livequiz_services::get_singleton_service_instance();
        $livequiz = $services->get_livequiz_instance($quizid);// Get livequiz object and add the new question to it.
        $questionid = $questiondata['id'];
        if ($questionid === 0) {// The question is new.
            $livequiz->add_question(self::new_question($questiondata));
        } else if ($questionid > 0) {// The question is already in the database.
            // Get the question from the livequiz and set new attributes.
            $question = $livequiz->get_question_by_id($questionid);
            self::set_question_attributes($question, $questiondata);
        }

        try {
            $livequiz = $services->submit_quiz($livequiz, $lecturerid);// Submit the livequiz to the database.
            $templatelivequiz = $livequiz->prepare_for_template();
            $templatequestions = $templatelivequiz->questions;
            return $templatequestions;
        } catch (dml_exception $dmle) {
            debugging('Error inserting livequiz into database: ' . $dmle->getMessage());
            throw $dmle;
        }
    }

The first thing that the execute method should do is call the execute_parameters method, which will verify that the arguments are of the correct type.

execute_parameters

This method will check that the arguments are of the correct type.
See the documentation for a more thorough walkthrough: execute_parameters Here an example from the save_question class:

public static function execute_parameters(): external_function_parameters {
        return new external_function_parameters([
            'question' => new external_single_structure([
                'id' => new external_value(PARAM_INT, 'ID'),
                'title' => new external_value(PARAM_TEXT, 'Title'),
                'description' => new external_value(PARAM_TEXT, 'Description of Question'),
                'explanation' => new external_value(PARAM_TEXT, 'Explanation of Question'),
                'type' => new external_value(PARAM_INT, "Type of the question as integer"),
                'answers' => new external_multiple_structure(
                    new external_single_structure([
                        'description' => new external_value(PARAM_TEXT, 'Description of Answer'),
                        'correct' => new external_value(PARAM_BOOL, 'Correctness of Answer'),
                        'explanation' => new external_value(PARAM_TEXT, 'Explanation of Answer'),
                     ]),
                    'Answers'
                ),
            ]),
            'lecturerid' => new external_value(PARAM_INT, 'Lecturer ID'),
            'quizid' => new external_value(PARAM_INT, 'Quiz ID'),
        ]);
    }

execute_returns

The execute_returns method defines the return type of the method.
The definition is done the same way as with execute_parameters see Link to execute_returns Here is an example from the save_question class, here a helper class have been created as the question structure is used in multiple services:

public static function execute_returns(): external_multiple_structure {
        return new external_multiple_structure(data_structure_helper::get_question_structure(), 'List of questions');
    }

amd/src/repository.js

The repository.js file is where the JavaScript defines the PHP functions that can be called.
Here is an example of a function definition:

export const saveQuestion = (question, lecturerid, quizid) => fetchMany([
    {
        methodname: 'mod_livequiz_save_question',
        args: {
            question,
            lecturerid,
            quizid
        },
    }
])[0];

It is important that the methodname is correct, such that it will consist of the module name in this case mod_livequiz_ and the name of the class in this case save_question.
Thus the only thing to edit is the method name and the corresponding arguments.

amd/src/JS_file_that_calls_php_function.js

This is the file that will call the function in PHP, it is important to import the function from repository.js.
Once the function is imported it can be called like any other asynchronous function in JavaScript.
Here is an example of the save_question service being called:

 saveQuestion(savedQuestion, lecturerId, quizId).then((questions) => {
        rerenderSavedQuestionsList(questions, updateEventListeners); // Re-render saved questions list.
        rerenderTakeQuizButton(takeQuizUrl, true); // Re-render take quiz button.
    })
    .catch((error) => window.console.log(error));
Clone this wiki locally