Question data structures

Jump to: navigation, search

There are three different ways that questions get represented in the code. That is rather annoying, and at least one more than really necessary. It is mainly due to the age of the code, but cleaning things up would break a lot of backwards compatibility, and so probably will not happen. Hopefully this page can explain what is going on, and will help you understand the code. The reason there are three different representations in different places does have a certain logic.

Representation 1: $questiondata

This representation is designed to closely mirror the data in the database. This representation is used in most places in the question bank. You can get the question in this form by calling either

$questiondata = question_bank::load_question($questionid); // Defined in question/engine/bank.php
// or
$questiondata = question_load_questions(array($questionid)); // Defined in lib/questionlib.php

For a typical $qtype, the structure is something like what you would get by running

$questiondata = $DB->get_record('question', array('id', $questionid));
$questiondata->options = $DB->get_record("qtype_{$qtype}_options",
       array('questionid', $questionid));
$questiondata->options->answers = $DB->get_records('question_answers',
       array('question', $questionid), 'id ASC');
$questiondata->hints = $DB->get_record('question_hints',
       array('questionid', $questionid), 'id ASC');

Some oddities that are worth commenting on there:

  • The columns should really be called question_answers.questionid, not question_answers.question, at least according to the coding guidelines as they are now, but this table has existed since Moodle 1.1 when the coding guidelines were different. This anomaly will probably be fixed one day.
  • It would probably be better if it was $questiondata->answers instead of $questiondata->options->answers. Again this is historic, which is why when I added $questiondata->hints I did it in an inconsistent way. It is not clear whether it is worth fixing this anomaly.
  • Note that, although I have used $questiondata as the name for this data structure, in many places in the code, the name $question is used instead. Again, this is historic. The intention is to move towards consistently calling variables that hold this data-structure $questiondata.

Not all question types are exactly like this. For example it is optional whether any question type chooses to use question_answers or question_hints. It depends on the question type whether they are useful. Other question types may have other data structures. For example qtype_match does not use answers. Instead it uses

$questiondata->options->subquestions = $DB->get_records('question_match_sub',
                array('question' => $question->id), 'id ASC');

Another interesting example is qtype_numerical, which needs extra data for each answer, and so does:

$questiondata->options->answers = $DB->get_records_sql("
        SELECT qa.*, qna.tolerance 
          FROM {question_answers} qa
          JOIN {question_numerical} qna on qna.answer = qa.id
         WHERE qa.question = ?
      ORDER BY qa.id ASC", array($questionid))) {

(Again, you can see the age of these tables in several violations of the current coding guidelines.)

Representation 2: $fromform

This representation closely mirrors the structure of the question editing form for the question type. It is what you get back from calling $mform->get_data() on the question editing form for the relevant type. Typically this data gets passed to

question_bank::get_qtype($qtype)->save_question($fromform);

This data structure is also used by question import formats. They parse whatever import file they are processing, and generate this $fromform data structure, which it then passed to save_question.

The main differences compared with $questiondata are:

  • The structure is flattened, so $questiondata->options->someoption is the same as $fromform->someoption.
  • The answer and hints arrays are different, so $questiondata->options->answers[123]->fraction may be $fromform->fraction[1]
  • Data from the HTML editor is different, so, while you have $questiondata->hints[234]->hint and $questiondata->hints[234]->hintformat, you will have $fromform->hint[2]['text'], $fromform->hint[2]['format'] and $fromform->hint[2]['itemid'].

When importing, you may have $fromform->hint[2]['files'] instead of $fromform->hint[2]['itemid']. See question_type::import_or_save_files for how this is handled.

Once again, in the code, variables holding this data structure are often called $question. The plan is to gradually rename them all to $fromform.

If you want to see what this data structure is for a particular question type, edit question/question.php, and insert the debugging line

$question = $qtypeobj->save_question($question, $fromform);

Just before the line

if (!empty($CFG->usetags) && isset($fromform->tags)) {

Representation 3: question_definition $question

Finally, there is the data structure used by the question engine, while questions are being attempted. You can get this structure by calling

$question = question_bank::make_question($questiondata);

This data structure is actually an instance of a proper class, question_definition, or more likely one of its subclasses depending on the question type. Therefore, there is extensive documentation of all the fields on the base class, which is defined in question/type/questionbase.php.

Although this is a proper class, all the data is still stored in public fields that closely mirror the structure of the question table. The extra options (e.g. $questiondata->options->someoption) are flattened out (e.g. $question->someoption). $question->answers is an array of question_answer objects (or a subclass, for example in qtype_numerical) and $question->hints is an array of question_hint objects.


Summary of the data flows

This diagram should help summarise the various representations of questions, and where they are used.

Question data structures.png

See also