Competency API
Moodle 3.1
The competency API lives in the subsystem competency, namespaced
\core_competency\
and located in the root folder competency.
Mental Models
These are not diagrams of the database tables exactly - but do show the relation ships between the major components of the competencies API.
Public API
The public API is defined by the API class which contains all the methods publicly available to any code taking action on competency related objects and data. No code should ever directly call the database, or persistent (models) methods, unless they are within an API method. All public API methods MUST perform the capability checks relevant to the actions that they are representing. The API methods can be seen as the controllers, they are a mandatory step to accessing the data, even within the competency API itself.
External API
External functions are all located in
\core_competency\external
, mostly as a proxy to the public API.
Evidence
A piece of evidence is used as a record of an action that may affect the competency rating of a student. For instance, when a teacher rates a student in a course, a piece of evidence will be created to record who, what, where, when the rating had been done, in this case it would contain as much as: "The teacher Bob rated the competency 'Demonstrates understanding of French' of the student Ann in the course 'Introduction to French' with the rating 'Met' on the 3rd of January at 1pm".
Custom evidence
Developers wanting to record evidence for actions that are not covered by the standard API must use
\core_competency\api::add_evidence()
. Note that this method does not perform any capability checks as it is expected to be called as the result of another action which has been authorised. Evidence must not be recorded directly, they always are the result of something else. For that reason there is not any external function to add a piece of evidence.
Frontend
The competency API comes without any frontend, though Moodle core ships with the admin tool
tool_lp
which is acting as the default frontend for competencies. It includes templates, javascript (AMD modules) and some additional external functions. It was built split from the backend to easily allow the support for other frontends.
URL resolver
As some of the core APIs require a knowledge of the resource URLs, we introduced the concept of a URL resolver. The URL resolver is used to determine the location of a resource from core, without forcing the default frontend to be used.
When referring to a resource from core, use the corresponding
\core_competency\url::my_ressource_name()
method. Note that it is acceptable for a plugin to directly link to resource frontends within itself without using the resolver.
Defining new URLs
Create a class and implement each routing method defined in \core_competency\url, though they should not be static, take \tool_lp\url_resolver as a base or an example.
Then indicate to core what class to use by defining the config variable
$CFG->core_competency_url_resolver
.
$CFG->core_competency_url_resolver = '\\your_namespace\\your_class';
Internal API
Persistents, or Models
The class
\core_competency\persistent
represents an object in the database. It is typically what is called a model in general. It contains the list of fields present in the database table, as well as their
PARAM_*
types. Basic validation of the param types is automatically performed prior to any database write operation. Additional validation can also be added by developers. In essence, the persistent knows everything about its data and when it is valid or not. Persistents are able to CRUD themselves in the database. They are also the right place to perform complex queries which join multiple persistents together. There is never any access control performed within the persistent! However, the competency API is oriented towards model-based Access Control List (ACL), therefore some of the persistents contain helper ACL methods (
boolean function can_do_the_thing();
). The main benefit of adding ACL methods to the persistent is to avoid repeating the capability names all over the place, and thus reducing code complexity and human errors.
Here is a non-exaustive list of the persistents:
competency | A single competency |
competency_framework | A framework of competencies |
course_competency | A relationship table (many-to-many) linking courses and competencies |
course_competency_settings | The settings related to competencies in a course |
course_module_competency | A relationship table (many-to-many) linking course modules and competencies |
evidence | A piece of evidence relating the proficiency of a user towards a competency |
plan | A group of competencies for a user to achieve, often referred to as learning plans |
plan_competency | A relationship table (many-to-many) linking plans and competencies |
related_competency | A relationship table (many-to-many) linking competencies to other competencies (within the same framework) |
template | A template for a plan |
template_competency | A relationship table (many-to-many) linking templates and competencies |
template_cohort | The cohorts attached to a template, from which we will create plans. |
user_competency | The record of the proficiency and rating of a user towards a competency |
user_competency_course | The record of the rating of a user towards a competency but limited to the scope of a course |
user_competency_plan | The record of the proficiency and rating of a user within a plan that was archived/completed, understand frozen. |
user_evidence | A piece of evidence of prior learning |
user_evidence_competency | Many-to-many relationship between evidence of prior learning and competencies |
Exporters
When writing external functions and templates we often needed to explicitly specify all the properties of the persistents. For instance, when returning a competency from an external function, when creating a competency from an external function, when preparing the context of a template, when getting the context for a template from an external function, etc... As persistents already define their properties we decided to create exporters (they are sometimes referred to as serialisers), and persistent-based exporters.
Exporters are classes which convert objects to stdClasses. They may add more properties to the objects as they export them (E.g. generating a timeago property from the date property of the object). Therefore exporters are aware of and define all the properties that can be read from an object, and all the properties that are saved in an object.
They mainly serve two purposes:
- They export an object consistently wherever they are used, i.e. all properties will always be present
- They generate the external function parameters and returns structure automatically
- The parameters will be the update structure, which contains the real fields
- The returns structure will be the read structure, which contains everything.
Exporters can also define related objects. When specified those must be set prior to exporting the data, mainly to improve performance. At this stage they may be a bit weak and difficult to work with as adding related objects to an existing exporter will require all usage of this exporter to be updated. We have raised an issue to try to improve their design: MDL-53052.
Exporters have come in very handy when we had to add new properties to persistents, or to export new properties from an existing object. They greatly simplify the maintainability of external functions and
templatable::export_for_template()
.
Formatting, filters, context, etc...
During export, the exporters will take care of formatting the text fields to be ready for output. This means that you do not need to worry about
format_string()
and
format_text()
. The filters, sanitization rules, etc... will automatically be applied. The exporter will use the related object
context
to determine the context of the strings. The function
format_text()
is applied to properties of type
PARAM_RAW
when another property with the same name ending with format is also present. For instance, a
PARAM_RAW
property named description will be formatted when another property named descriptionformat is present. The function
format_string()
is applied to properties of type
PARAM_TEXT
. It is important to note that after formatting the values can contain HTML and will therefore have to be treated as
PARAM_RAW
.