Note:

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

Question data structures: Difference between revisions

From MoodleDocs
No edit summary
m (Text replacement - "</code>" to "</syntaxhighlight>")
 
(2 intermediate revisions by 2 users not shown)
Line 4: Line 4:


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
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
<code php>
<syntaxhighlight lang="php">
$questiondata = question_bank::load_question($questionid); // Defined in question/engine/bank.php
$questiondata = question_bank::load_question($questionid); // Defined in question/engine/bank.php
// or
// or
$questiondata = question_load_questions(array($questionid)); // Defined in lib/questionlib.php
$questiondata = question_load_questions(array($questionid)); // Defined in lib/questionlib.php
</code>
</syntaxhighlight>


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


<code php>
<syntaxhighlight lang="php">
$questiondata = $DB->get_record('question', array('id', $questionid));
$questiondata = $DB->get_record('question', array('id', $questionid));
$questiondata->options = $DB->get_record("qtype_{$qtype}_options",
$questiondata->options = $DB->get_record("qtype_{$qtype}_options",
Line 20: Line 20:
$questiondata->hints = $DB->get_record('question_hints',
$questiondata->hints = $DB->get_record('question_hints',
       array('questionid', $questionid), 'id ASC');
       array('questionid', $questionid), 'id ASC');
</code>
</syntaxhighlight>


Some oddities that are worth commenting on there:
Some oddities that are worth commenting on there:
* The columns should really be called <tt>question_answers.questionid</tt>, not <tt>question_answers.question</tt>, 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.
* The columns should really be called <tt>question_answers.questionid</tt>, not <tt>question_answers.question</tt>, 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 <tt>$questiondata->answers</tt> instead of <tt>$questiondata->options->answers</tt>. Again this is historic, which is why when I added <tt>$questiondata->hints</tt> I did it in an inconsistent way. It is not clear whether it is worth fixing this anomaly.
* It would probably be better if it was <tt>$questiondata->answers</tt> instead of <tt>$questiondata->options->answers</tt>. Again this is historic, which is why when I added <tt>$questiondata->hints</tt> I did it in an inconsistent way. It is not clear whether it is worth fixing this anomaly.
* Note that, although I have used <tt>$questiondata</tt> as the name for this data structure, in many places in the code, the name <tt>$question</tt> is used instead. Again, this is historic. The intention is to move towards consistently calling variables that hold this data-structure <tt>$questiondata</tt> (or <tt>$qd</tt>).
* Note that, although I have used <tt>$questiondata</tt> as the name for this data structure, in many places in the code, the name <tt>$question</tt> is used instead. Again, this is historic. The intention is to move towards consistently calling variables that hold this data-structure <tt>$questiondata</tt>.


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


<code php>
<syntaxhighlight lang="php">
$questiondata->options->subquestions = $DB->get_records('question_match_sub',
$questiondata->options->subquestions = $DB->get_records('question_match_sub',
                 array('question' => $question->id), 'id ASC');  
                 array('question' => $question->id), 'id ASC');  
</code>
</syntaxhighlight>


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


<code php>
<syntaxhighlight lang="php">
$questiondata->options->answers = $DB->get_records_sql("
$questiondata->options->answers = $DB->get_records_sql("
         SELECT qa.*, qna.tolerance  
         SELECT qa.*, qna.tolerance  
Line 43: Line 43:
         WHERE qa.question = ?
         WHERE qa.question = ?
       ORDER BY qa.id ASC", array($questionid))) {
       ORDER BY qa.id ASC", array($questionid))) {
</code>
</syntaxhighlight>


(Again, you can see the age of these tables in several violations of the current coding guidelines.)
(Again, you can see the age of these tables in several violations of the current coding guidelines.)
Line 51: Line 51:
This representation closely mirrors the structure of the question editing form for the question type. It is what you get back from calling <tt>$mform->get_data()</tt> on the question editing form for the relevant type. Typically this data gets passed to  
This representation closely mirrors the structure of the question editing form for the question type. It is what you get back from calling <tt>$mform->get_data()</tt> on the question editing form for the relevant type. Typically this data gets passed to  


<code php>
<syntaxhighlight lang="php">
question_bank::get_qtype($qtype)->save_question($fromform);
question_bank::get_qtype($qtype)->save_question($fromform);
</code>
</syntaxhighlight>


This data structure is also used by [[Question formats|question import formats]]. They parse whatever import file they are processing, and generate this <tt>$fromform</tt> data structure, which it then passed to <tt>save_question</tt>.
This data structure is also used by [[Question formats|question import formats]]. They parse whatever import file they are processing, and generate this <tt>$fromform</tt> data structure, which it then passed to <tt>save_question</tt>.
Line 68: Line 68:
If you want to see what this data structure is for a particular question type, edit question/question.php, and insert the debugging line
If you want to see what this data structure is for a particular question type, edit question/question.php, and insert the debugging line


<code php>
<syntaxhighlight lang="php">
$question = $qtypeobj->save_question($question, $fromform);
$question = $qtypeobj->save_question($question, $fromform);
</code>
</syntaxhighlight>


Just before the line
Just before the line


<code php>
<syntaxhighlight lang="php">
if (!empty($CFG->usetags) && isset($fromform->tags)) {
if (!empty($CFG->usetags) && isset($fromform->tags)) {
</code>
</syntaxhighlight>


==Representation 3: <tt>question_definition $question</tt>==
==Representation 3: <tt>question_definition $question</tt>==
Line 82: Line 82:
Finally, there is the data structure used by the question engine, while questions are being attempted. You can get this structure by calling
Finally, there is the data structure used by the question engine, while questions are being attempted. You can get this structure by calling


<code php>
<syntaxhighlight lang="php">
$question = question_bank::make_question($questiondata);
$question = question_bank::make_question($questiondata);
</code>
</syntaxhighlight>


This data structure is actually an instance of a proper class, <tt>question_definition</tt>, 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 <tt>question/type/questionbase.php</tt>.
This data structure is actually an instance of a proper class, <tt>question_definition</tt>, 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 <tt>question/type/questionbase.php</tt>.

Latest revision as of 20:28, 14 July 2021

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