Note:

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

Creating a web service and a web service function: Difference between revisions

From MoodleDocs
m (removing categories)
 
(90 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{Moodle_2.0}}
#REDIRECT [[Adding_a_web_service_to_a_plugin]]
In this document you will learn how to create a web service function.
 
This document expect that you first read the administration manual [[Setting_up_a_web_service]].<br/>
To explain how to write a new web service function, we will write a web service function ''create_groups($groups)''. This function create a group into a Moodle course. - actually the function already exists into Moodle. You'll be able to browse CVS to have a look to the actual code.
 
This function will take a list of group object as parameters and it will return the same groups with their newly created id. If ever one group creation  fails, the function will throw an exception, and no creation will happen.
 
We'll finally add the web service function to a core web service.
 
== Preparation ==
We need to identify:
* '''the core functions''': for our case we will use ''groups_create_group()'' from [http://cvs.moodle.org/moodle/group/ /group/lib.php]. Of course not every core function exists and in some other cases you could have to write them. 
* '''the parameter types''': for our case we'll send a list of object. This object are groups, with id/name/courseid.
* '''the returned value types''': for our case we want to send back a list of group object with their id.
* '''the user capabilities''': for our case the user will need the ''moodle/course:managegroups'' capability.
 
== Declare the web service function ==
Web service functions are declared into ''db/services.php'' of each component. In our case ''create_groups()'' is a core external function, so we will add the function to [http://cvs.moodle.org/moodle/lib/db/ /lib/db/services.php].
 
<code php>
$functions = array(
    'moodle_group_create_groups' => array(        //web service function name
        'classname'  => 'moodle_group_external',  //class containing the external function
        'methodname'  => 'create_groups',          //external function name
        'classpath'  => 'group/externallib.php',  //file containing the class/external function
        'description' => 'Creates new groups.',    //human readable description of the web service function
        'type'        => 'write',                  //database rights of the web service function (read, write)
    ),
);
</code>
 
Here you should ask yourself what is the difference between a web service function and an external function. Web service function could be considered as a redirection to the external function. The web service function name is the name that is served by web service server to the client. By this way the client doesn't know about the external functions.
 
The web service function name is arbitrary, but it must be globally unique so we highly recommend using the component name as prefix (and "moodle" for core functions).
 
== Define the web service description ==
Some web service protocol need a full description of the web service function parameters and return value. For example we need to generate a full WSDL for SOAP if we expect a JAVA/.Net client to call our function. This WSDL need a complete and fully detailed description of the parameters.<br/>
Moreover we implemented a web service parameters validation process that also require a full description of these parameters.<br/>
In conclusion, you must write these descriptions. These descriptions are not applied on the web service function but the external function, remember that the web service function is just a kind of redirection.<br/><br/>
 
Descriptions are usually located into a class into an externallib.php in the component folder. You probably remember that we referenced this ''moodle_group_external'' class into [http://cvs.moodle.org/moodle/lib/db/ /lib/db/services.php]. Let's take a minuteto talk about the [http://cvs.moodle.org/moodle/group/ /group/externallib.php] that we need to create.
 
This file will contain all external functions. These functions are all related together. In our case the externallib.php file will contain all ''group'' related functions. For each external function, we will have:
* the external function, calling a core function.
* a function describing the parameters of the external function
* a function describing the return values of the external function
 
Now we'll have look to the description of the ''create_group($groups)'' parameters. All parameter descriptions are included into a function called ''externalfunctionname_parameters()''. In our case it will be ''create_groups_parameters()'' :
 
<code php>
    /**
    * Returns description of method parameters
    * @return external_function_parameters
    */
    public static function create_groups_parameters() {
        return new external_function_parameters(
            array(
                'groups' => new external_multiple_structure(
                    new external_single_structure(
                        array(
                            'courseid' => new external_value(PARAM_INT, 'id of course'),
                            'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                            'description' => new external_value(PARAM_RAW, 'group description text'),
                            'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                        )
                    )
                )
            )
        );
    }
</code>
 
Ok you are probably already here :) We have to understand this function very well. There is different approaches to understand this function, here is my personal one.<br/>
The function ''xxx_parameters()'' always return an ''external_function_parameters'' object. This object is initialized with an associative array. This array contains the parameter descriptions. If the external function have three parameters, this array will have three elements, the keys being the paramters name.
Each of its element is a parameter description. A parameter can be a list (external_multiple_structure), an object (external_single_structure) or a primary type (external_value).
 
Our create_groups() function expect one parameter named ''groups'', so we will first write:
 
<code php>
    /**
    * Returns description of method parameters
    * @return external_function_parameters
    */
    public static function create_groups_parameters() {
        return new external_function_parameters(
            array(
                'groups' => ...
               
            )
        );
    }
</code>
 
This ''groups'' parameter is a list of group. So we will write :
 
<code php> 
                'groups' => new external_multiple_structure(
                    ...
                )         
</code>
 
An external_multiple_structure object (list) can be initialized with external_single_structure (object), external_value (primary type) or another external_multiple_structure (list). You will respectively obtain a list of a unique object type, a list of a unique primary type, or a list of a list. These last case being probably bad design.<br/>
In our case we want a list of group, a group being a object. Have a look on how would describe a group :
 
<code php>           
                    new external_single_structure(
                        array(
                            'courseid' => ...,
                            'name' => ...,
                            'description' => ...,
                            'enrolmentkey' => ...,
                        )
                    )         
</code>
 
From this code you can guess that a external_single_structure is initialize with an associative array. This array containing the object attibuts.<br/>
Thus a list of group will be defined :
 
<code php> 
                'groups' => new external_multiple_structure(
                    new external_single_structure(
                        array(
                            'courseid' => ...,
                            'name' => ...,
                            'description' => ...,
                            'enrolmentkey' => ...,
                        )
                    )
                )         
</code>
 
Every of the group attribut are primary type. ''courseid'' is an integer. ''name'' is a string, ''description'' is a string, and ''enrolmentkey'' is also a string. To define a primary type we use the ''external_value'' object. This ''external_value'' is initialize with a non associative array containing three elements:
# the primary type
# a human readable description
# is the parameter optional (if the parameter is mandatory we don't need this third element)
 
For example for the ''courseid'' and ''name'' attribut we would define:
 
<code php>               
                            'courseid' => new external_value(PARAM_INT, 'id of course'),
                            'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),                       
</code>
 
 
So our list of object description will be :
 
<code php> 
                'groups' => new external_multiple_structure(
                    new external_single_structure(
                        array(
                            'courseid' => new external_value(PARAM_INT, 'id of course'),
                            'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                            'description' => new external_value(PARAM_RAW, 'group description text'),
                            'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                        )
                    )
                )         
</code>
 
Now you should be able to understand this parameter description function. You also should be able to deduce how to write the return value description function.
 
Return value function always return one primary or complex value, so we don't need to return external_function_parameters object. In our case we want to return the exact same list of groups. The only change being that we want the group to have an id attribut.
 
<code php>
public static function create_groups_returns() {
        return new external_multiple_structure(
            new external_single_structure(
                array(
                    'id' => new external_value(PARAM_INT, 'group record id'),
                    'courseid' => new external_value(PARAM_INT, 'id of course'),
                    'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                    'description' => new external_value(PARAM_RAW, 'group description text'),
                    'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                )
            )
        );
    }
</code>
 
Here ends the most difficult part of the web service infrastructure implementation. Congratulation!
 
=== Annex ===
An optional attribut would be defined as:
 
<code php>               
                            'yearofstudy' => new external_value(PARAM_INT, 'year of study',false, 0),                       
</code>
 
The third parameter (false) indicates that 'yearofstudy' is not mandatory. When an attribut is optional, you should <b>always</b> add the default value as fourth parameter. Here the default value of 'yearofstudy' is 0.
 
== Define the external function ==
We declared our web service function and we defined the external function parameters and return values. We will now implement the external function. Let's have a full look to the external function.
 
<code php>
    /**
    * Create groups
    * @param array $groups array of group description arrays (with keys groupname and courseid)
    * @return array of newly created groups
    */
    public static function create_groups($groups) { //Don't forget to set it as static
        global $CFG, $DB;
        require_once("$CFG->dirroot/group/lib.php");
 
        $params = self::validate_parameters(self::create_groups_parameters(), array('groups'=>$groups));
 
        $transaction = $DB->start_delegated_transaction();
 
        $groups = array();
 
        foreach ($params['groups'] as $group) {
            $group = (object)$group;
 
            if (trim($group->name) == '') {
                throw new invalid_parameter_exception('Invalid group name');
            }
            if ($DB->get_record('groups', array('courseid'=>$group->courseid, 'name'=>$group->name))) {
                throw new invalid_parameter_exception('Group with the same name already exists in the course');
            }
 
            // now security checks
            $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
            self::validate_context($context);
            require_capability('moodle/course:managegroups', $context);
 
            // finally create the group
            $group->id = groups_create_group($group, false);
            $groups[] = (array)$group;
        }
 
        $transaction->allow_commit();
 
        return $groups;
    }
</code>
 
First let's have a look at
<code php>
$params = self::validate_parameters(self::create_groups_parameters(), array('groups'=>$groups));
</code>
This ''validate_parameters'' function validates the external function parameters against the description. It will return an exception if some required parameters are missing, if parameters are not well-formed, and check the parameters validity. The array('groups'=>$groups) take the expected parameters by the description function.
 
A very important point: the parameters of your external function (''$groups'' into ''public static function create_groups('''$groups''') {'') should be in the exact same order as the ones into your description function ('''return new external_function_parameters( array( ''''groups'''' => ... )'''). I agree that our example function doesn't give us a good example :)
 
Then we have the following line
<code php>
$transaction = $DB->start_delegated_transaction();
</code>
Our function has for goal to create multiple group. The function is calling multiple time the '''groups_create_group()''' core function. If ever one function fail and return an exception, we do not want to commit any change. It's why we declare this line that suspend any commit till we write the following line
<code php>
$transaction->allow_commit();
</code>
 
You will also notice that our function throws exception. It is automatically handle by Moodle web service servers.
<code php>
throw new invalid_parameter_exception('Group with the same name already exists in the course');
</code>
 
And yet again another important point: the context checking and capability checking happens into the external function. In a long term, in Moodle 2.0, all context/capability checks will disappear from the core functions and will be located into the external functions
<code php>
/// now security checks
$context = get_context_instance(CONTEXT_COURSE, $group->courseid);
self::validate_context($context);
require_capability('moodle/course:managegroups', $context);
</code>
 
Last point, the return values will be parsed by the Moodle web service servers. Only the required values describe into the function description will be returned. An error will be returned if the external function doesn't return some required values.
 
== Add the web service function into a service ==
In some specific case we could want to set a service by default, so the Moodle administrator doesn't need to set a new one. We can do that by writing the following code into [http://cvs.moodle.org/moodle/lib/db/ /lib/db/services.php]:
 
<code php>
  $services = array(
      'groupservice' => array(                                                //the name of the web service
          'functions' => array ('moodle_add_groups', 'moodle_create_groups'), //web service functions of this service
          'requiredcapability' => 'some/capability:specified',                //if set, the web service user need this capability to access
                                                                              //any function of this service                 
          'restrictedusers' = >1,                                            //if enabled, the Moodle administrator must link some user to this service
                                                                              //into the administration
          'enabled'=>0,                                                      //if enabled, the service can be reachable on a default installation
      )
  );
</code>
 
In this code we created a service called ''groupservice''. This service contains two functions ''moodle_add_groups''/''moodle_create_groups''. <br/>
This possibility to preset a service exists to facilitate the administrator life. He can easily enable a bunch of function. <br/>
However it is not possible for an administrator to add/remove any function from preset service. For security purpose, it is not a recommended to preset a service.
 
== Test the web service function ==
Moodle is bundled with a test client that can be accessed into [http://cvs.moodle.org/moodle/webservice/ webservice/testclient.php]. You are more than advice to run it before to look at the code as the client can be used for multiple protocols, and multiple functions. You will have a better understanding.<br/>
In order to support our function we will need to add a form into [http://cvs.moodle.org/moodle/webservice/ webservice/testclient_forms.php].
<code php>
class moodle_group_create_groups_form extends moodleform {
    public function definition() {
        global $CFG;
 
        $mform = $this->_form;
 
        $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
 
        //note: these element are intentionally text aera without validation - we want users to test any rubbish as parameters
        $mform->addElement('text', 'wsusername', 'wsusername');
        $mform->addElement('text', 'wspassword', 'wspassword');
        $mform->addElement('text', 'courseid', 'courseid');
        $mform->addElement('text', 'name', 'name');
        $mform->addElement('text', 'description', 'description');
        $mform->addElement('text', 'enrolmentkey', 'enrolmentkey');
 
        $mform->addElement('hidden', 'function');
        $mform->setType('function', PARAM_SAFEDIR);
 
        $mform->addElement('hidden', 'protocol');
        $mform->setType('protocol', PARAM_SAFEDIR);
 
        $mform->addElement('static', 'warning', '', get_string('executewarnign', 'webservice'));
 
        $this->add_action_buttons(true, get_string('execute', 'webservice'));
    }
 
    public function get_params() {
        if (!$data = $this->get_data()) {
            return null;
        }
        // remove unused from form data
        unset($data->submitbutton);
        unset($data->protocol);
        unset($data->function);
        unset($data->wsusername);
        unset($data->wspassword);
 
        $params = array();
        $params['groups'] = array();
        $params['groups'][] = (array)$data;
 
        return $params;
    }
}
</code>
 
That's all for our create_groups() function. We've been able to implement it and to test it. <br/> In [[Creating_a_web_service_client]] we will talk about web service client.
 
==See also==
* [[Web services]]
* [[External services security]]
* [[External services description]]
* [[Creating_a_web_service_client]]
* [[Setting_up_a_web_service]]
 
 
[[Category:Web Services]]

Latest revision as of 09:08, 27 February 2012