Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Web services (2.0 onwards)

From MoodleDocs
Revision as of 10:47, 22 December 2010 by Gerwood Stewart (talk | contribs)

Adding web-services to a plugin (Moodle 2.0)

Overview

Web services are built and provided the the 'externallib.php' file in the modules (or areas) directory. In this file is kept the external class that extends the 'external_api' class and provides definitions for the incoming message format, outgoing message format and the worker functions for each service.

Extract from courses externallib.php:

defined('MOODLE_INTERNAL') || die;

require_once("$CFG->libdir/externallib.php");

class moodle_course_external extends external_api {
}

Each web-service requires three methods: 1. A parameters function (appended with _parameters). 2. A return parameters function (appended with _returns). 3. And the worker function itself.

For example the 'get_courses' service has the following three methods:

//Provides a description of the parameters
public static function get_courses_parameters () {} 

//Does the work of collecting the requested data, creating adding deleting records etc.
public static function get_courses ($options) {} 

//Provides a description of the structure for the data returned by get_courses (the worker function).
public static function get_courses_returns () {}

Making a web-service (get_course_activities

This tutorial creates a get_course_activities web-service and adds it to the courses web-services. This is to avoid creating an entirely new plugin to demonstrate the web-services code.

To create this new service we will need to create the static functions in the externallib.php file under /course and will need to add several database entries.

What should the call do? A little planning

With this web-service we want to be able to ask moodle what activities a course has. To do this we will need to provide the web-service call with the course id that we want the activities returned for.

1. First we need to create the parameters function. We could start with any of them however it will be easier to define the inputs and outputs before moving onto the worker.

//First draft
public static function get_course_activities_parameters () {
 //The _parameters method only has one objective, to return a description of the input data structure.
 return new external_function_parameters();
}

We aren't done with this method yet.

A quick explanation of external_function_parameters is in order. (At the moment this explanation is guess work). The external_function_parameters is a wrapper class for the external_single_structure class. You must return an external_function_parameters class in the _parameters function. If you don't Moodle will throw an 'Invalid parameters description' error.

2. Next add the expected input structure to the external_function_parameters object.

//Second draft
public static function get_course_activities_parameters () {
 return new external_function_parameters (
  array(
   'courseid'=> new external_value(PARAM_INT, 'id of the course')
  )
 );
}

Here we have added a single external_single_structure implicitly through the external_function_parameters object. We don't need anything more spectacular as we only want to provide one piece of data, the course id.

To do this we add a named array with one key, value pair. The key is set to the name of the field: 'courseid' and the value is set to an external_value object with a PARAM_INT type.

'courseid'=> new external_value(PARAM_INT,'id of the course')

This completes the _parameters function for the web-service. Next we can move on and define what we want to see in return.

3. Creating the _returns function (get_course_activities_returns)

Differently to the _parameters function the _returns function doesn't require a specific object as its primary return value. Instead you can choose from any of the objects that inherit the external_description class.

As a result we should start by taking a look at the kind of data and therefore the kind of data structure that we are going to expect returned to use from the service. We expect that a single course will have many activities and we aren't asking for activities from more than one course. Each activity will have a set of fields that contain the activity information such as id and type.

This should give us a starting point roughly like this:

//
public static function get_course_acitivities_returns () {
 return new external_multiple_structure (
  new external_single_structure ( //This is used to represent a single activity.
   array(
   ), 'activity') //The description of what this element represents.
 )
}

We have a basic outline of the top level data structures that we want returned. This was fairly simple as the above structure is what you're likely to need anytime you want to return a list of objects or 'items'.

4. Adding a description of the activity fields to be returned.

Now we need to define what each activity item should look like. In reality when you develop a web-service you may switch between defining the return structure in the _returns function and writing the worker function (i.e what the author needed to do to write this tutorial). We are going to concentrate just on finishing this function so that the description of the fields is in one place and we can follow a single train of thought.

This is the basic data structure we are going to use. There are many more fields within related to each activity that we could add, but for the moment this will do.

array(
 'id'=> new external_value(PARAM_INT,'The id of the activity'),
 'name' => new external_value(PARAM_TEXT,'The name of the activity'),
 'mod' => new external_value(PARAM_ALPHANUM,'The module that provides this activity.')
)

This is enough of a structure to return an interesting result. It's time to move on to the worker function itself.

5. The worker function.

This is where the data gathering is done. The previous two functions mean that we can leave Moodle to do the heavy lifting when it comes time to make and send requests. The worker function just needs to get the data we are after and return it in a php data structure that matches the template we've outlined in the _return function.

    public static function get_course_activities ($courseid) {
       global $CFG;
       require_once($CFG->dirroot . "/course/lib.php");

       $params = self::validate_parameters(self::get_course_activities_parameters(),
           array('courseid' => $courseid)
       );

       //retrieve courses
       if (!key_exists('courseid', $params)) { //Will probably need to chuck a narny.
           throw new moodle_exception(get_string('nocourseid', 'webservice', $exceptionparam));
       } else {
           $activities_raw = get_array_of_activities($params['courseid']);

           $activities = array();

           foreach($activities_raw as $key => $activity_raw) {
               $activity = array();
               $activity["id"] = $activity_raw->id;
               $activity["name"] = $activity_raw->name;
               $activity["mod"] = $activity_raw->mod;

               $activities[] = $activity;
           }

           return $activities;
       }
   }

This is everything we need to make the whole web service work, but perhaps we should break it down and look at it.

First some of the basics

       global $CFG;
       require_once($CFG->dirroot . "/course/lib.php");

Instead of re-inventing the wheel and working out how collect the data for ourselves we are essentially going to wrap the get_array_of_activities function provided by the course library. It takes care of making the right database calls and putting the information together. It will also mean that the information is returned consistently the same whether you are using the internal call or the external call (web service).

Next we will validate the incoming data. This is where the _parameters method comes in. To validate we call the validate_parameters function that is provided for free by extending the external_api class. We provide to it the _parameters function for the web service being created and the incoming data. In this case in a format to match the top level structure that we defined in _parameters function.

       $params = self::validate_parameters(self::get_course_activities_parameters(),
           array('courseid' => $courseid)
       );

From this call we are provided with the $params data structure. This will provide a structure that matches the structure set up in the _parameters function.

       //retrieve courses
       if (!key_exists('courseid', $params)) { //Will probably need to chuck a narny.
           throw new moodle_exception(get_string('nocourseid', 'webservice', $exceptionparam));
       } else {
         //Here we will place the work code that provides the activity data.
       }

If we don't have a courseid value we will instead return with a moodle_exception. If everything is fine proceed to to collecting the activity information. Inside the if statement we add this:

           $activities_raw = get_array_of_activities($params['courseid']);

           $activities = array();

           foreach($activities_raw as $key => $activity_raw) {
               $activity = array();
               $activity["id"] = $activity_raw->id;
               $activity["name"] = $activity_raw->name;
               $activity["mod"] = $activity_raw->mod;

               $activities[] = $activity;
           }

           return $activities;

In essence this code calls the course function get_array_of_activities and then processes the result. The structure to be built is an array of named arrays. To do this we create the $activities array and then loop through the object we received back. For each entry we create another array (convenience of php) and add named entries to match the fields in the _returns function. In this case we add the 'id', 'name' and 'mod' fields.

Once done we then append the $activity array to the list of others we are collecting and final return them.

At this point the web-service is ready to go except for the database entry that will tell moodle that we have a new web service available and where to find it.

Adding the database entry

For now we will do this the hard (and not good) way.

Moodle web-services are described in the external_functions (mdl_external_functions) table.

The table has six fields that we need to fill in:

  • name - This is the name of the service and the way it will be referred to by moodle. Consider it the public name
  • classname - This is the name of the class that provides this method. In this case moodle_course_external
  • methodname - The name of the method: In this case get_course_activities. Moodle will work out the _returns and _parameters
  • classpath - The name of the file that the class (referred to in classname) can be found in. The path is relative to the moodle root. In this case: 'course/externallib.php'. externallib.php is the default for these services and all of the core services are found here so stick with it.
  • component - all the core services belong to 'moodle'. This (I'm currently guessing) refers to the component that is providing the services. I.E. If you're not contributing to core avoid using 'moodle' here.
  • capabilities - These are the capabilities users will require to successfully use this service.

(NB: I'm sure there has to be a better way than to add the db entries directly)

As a result we get the following query for this service:

insert into mdl_external_functions (name,classname,methodname,classpath,component,capabilities) values
('moodle_course_get_course_activities','moodle_course_external','get_course_activities',
'course/externallib.php','moodle','moodle/course:view,moodle/course:viewhiddencourses')

And from here we are done. You can now find and configure the new web-service through the admin area.

Of course there is testing

It would appear that the web-service tests in Moodle aren't able to read and test new ones without code additions. In which case try this. Fill in the blanks and run this in a php environment.

<?php
   error_reporting(E_ALL);
   $s = new SoapClient("http://add-your-moodle-url/webservice/soap/server.php?wstoken=add-your-ws-token&wsdl=1");
   var_dump($s->moodle_course_get_course_activities(--add a course id--));
?>

Data Structure Descriptors

In order to keep a little of the technical stuff out of the tutorial and provide an overview of the data structures:

external_single_structure

This method translates to a named array or hash with a subset of elements that provide the requested data.

Because of this you can imagine this to be the equivalent of a named array, hash or object wrapper.

i.e. external_single_structure = hash


external_multiple_structure

external_multiple_structure = array

external_value

external_value object is used to specify a data field. It requires two values, a type constant (see below) and a description string.

Available type constants

  • PARAM_INT : specifies an integer data type.
  • PARAM_TEXT : (description required).
  • PARAM_RAW : (description required).