An IMS QTI (Question & Test Interoperability) Software Development Kit for PHP 7.0 and higher supporting a wide range of features described by the IMS QTI specification family.
This implementation of QTI is under constant enhancement. The API of the master branch might change at any time.
- Targets QTI 2.0, 2.1 and partially 2.2
- Complete QTI Information Model
- Complete QTI Rule Engine Support
- Custom Operator Hooks through PSR-0/PSR-4
- Wilbert Kraan's / Steve Lay's Goldilocks Rendering
- CSS Parser for direct QTI Information Model mapping at rendering time
- Item and Test Sessions (with lightning fast binary persistence)
- Nice and Clean API for QTI Document manipulation/traversal
- PreConditions & Branching
- Selection and Ordering
- Response, Outcome and Template Processing
- aria-* attributes
- Unit test driven
- Clone the repository.
- Make sure you know how Composer works and it is installed on your system.
- php composer.phar install
- You are ready!
Run Unit Tests by invoking the following shell command:
cp phpunit.xml.dist phpunit.xml
./vendor/bin/phpunit test
We are always looking for people to feed the project with:
- Bug reports
- Unit tests
- New features
The following example demonstrates how to instantiate an item session for a given QTI XML item document. The item in use in this example is the "Composition of Water" item, from the QTI 2.1 Implementation Guide.
<?php
use qtism\common\enums\BaseType;
use qtism\common\enums\Cardinality;
use qtism\common\datatypes\QtiIdentifier;
use qtism\data\storage\xml\XmlDocument;
use qtism\runtime\common\State;
use qtism\runtime\common\ResponseVariable;
use qtism\runtime\common\MultipleContainer;
use qtism\runtime\tests\AssessmentItemSession;
// Instantiate a new QTI XML document, and load a QTI XML document.
$itemDoc = new XmlDocument('2.1');
$itemDoc->load('choice_multiple.xml');
/*
* A QTI XML document can be used to load various pieces of QTI content such as assessmentItem,
* assessmentTest, responseProcessing, ... components. Our target is an assessmentItem, which is the
* root component of our document.
*/
$item = $itemDoc->getDocumentComponent();
/*
* The item session represents the collected and computed data related to the interactions a
* candidate performs on a single assessmentItem. As per the QTI specification, "an item session
* is the accumulation of all attempts at a particular instance of an assessmentItem made by
* a candidate.
*/
$itemSession = new AssessmentItemSession($item);
// The candidate is entering the item session, and is beginning his first attempt.
$itemSession->beginItemSession();
$itemSession->beginAttempt();
/*
* We instantiate the responses provided by the candidate for this assessmentItem, for the current
* item session. For this assessmentItem, the data collected from the candidate is represented by
* a State, composed of a single ResponseVariable named 'RESPONSE'.
*/
$responses = new State(
array(
// The 'RESPONSE' ResponseVariable has a QTI multiple cardinality, and a QTI identifier baseType.
new ResponseVariable(
'RESPONSE',
Cardinality::MULTIPLE,
BaseType::IDENTIFIER,
/*
* The ResponseVariable value is a Container with multiple cardinality and an identifier
* baseType to meet the cardinality and baseType requirements of the ResponseVariable.
*/
new MultipleContainer(
BaseType::IDENTIFIER,
/*
* The values composing the Container are identifiers 'H' and 'O', which represent
* the correct response to this item.
*/
array(
new QtiIdentifier('H'),
new QtiIdentifier('O')
)
)
)
)
);
/*
* The candidate is finishing the current attempt, by providing a correct response.
* ResponseProcessing takes place to produce a new value for the 'SCORE' OutcomeVariable.
*/
$itemSession->endAttempt($responses);
// The item session variables and their values can be accessed by their identifier.
echo 'numAttempts: ' . $itemSession['numAttempts'] . "\n";
echo 'completionStatus: ' . $itemSession['completionStatus'] . "\n";
echo 'RESPONSE: ' . $itemSession['RESPONSE'] . "\n";
echo 'SCORE: ' . $itemSession['SCORE'] . "\n";
/*
* numAttempts: 1
* completionStatus: completed
* RESPONSE: ['H'; 'O']
* SCORE: 2
*/
// End the current item session.
$itemSession->endItemSession();
As per the QTI specification, item sessions allow a single attempt to be performed by default. Trying to begin an attempt that will make the item session exceeding the maximum number of attempts will lead to a PHP exception, as in the following example.
<?php
use qtism\data\storage\xml\XmlDocument;
use qtism\runtime\common\State;
use qtism\runtime\tests\AssessmentItemSession;
use qtism\runtime\tests\AssessmentItemSessionException;
$itemDoc = new XmlDocument('2.1');
$itemDoc->load('choice_multiple.xml');
$item = $itemDoc->getDocumentComponent();
$itemSession = new AssessmentItemSession($item);
$itemSession->beginItemSession();
// Begin 1st attempt.
$itemSession->beginAttempt();
// End attempt by providing an empty response...
$itemSession->endAttempt(new State());
// Begin 2nd attempt, but by default, maximum number of attempts is 1.
try {
$itemSession->beginAttempt();
} catch (AssessmentItemSessionException $e) {
echo $e->getMessage();
// A new attempt for item 'choiceMultiple' is not allowed. The maximum number of attempts (1) is reached.
}
If multiple attempts are permitted on a given assessmentItem, the itemSessionControl
's maxAttempts
attribute
can be modified to allow multiple or unlimited attempts that can be performed by a candidate.
<?php
use qtism\data\ItemSessionControl;
use qtism\data\storage\xml\XmlDocument;
use qtism\runtime\common\State;
use qtism\runtime\tests\AssessmentItemSession;
$itemDoc = new XmlDocument('2.1');
$itemDoc->load('choice_multiple.xml');
$item = $itemDoc->getDocumentComponent();
$itemSession = new AssessmentItemSession($item);
// Set the maximum number of attempts to 0 (means unlimited).
$itemSessionControl = new ItemSessionControl();
$itemSessionControl->setMaxAttempts(0);
$itemSession->setItemSessionControl($itemSessionControl);
// Performing multiple attempts will not lead to a PHP exception anymore, because the maximum number of attemps is unlimited!
$itemSession->beginItemSession();
// 1st attempt will be an incorrect response from the candidate (['H'; 'Cl']).
$responses = new State(
array(
new ResponseVariable(
'RESPONSE',
Cardinality::MULTIPLE,
BaseType::IDENTIFIER,
new MultipleContainer(
BaseType::IDENTIFIER,
array(
new QtiIdentifier('H'),
new QtiIdentifier('Cl')
)
)
)
)
);
$itemSession->endAttempt($responses);
echo 'numAttempts: ' . $itemSession['numAttempts'] . "\n";
echo 'completionStatus: ' . $itemSession['completionStatus'] . "\n";
echo 'RESPONSE: ' . $itemSession['RESPONSE'] . "\n";
echo 'SCORE: ' . $itemSession['SCORE'] . "\n";
/*
* numAttempts: 1
* completionStatus: completed
* RESPONSE: ['H'; 'N']
* SCORE: 0
*/
// 2nd attempt will send a correct response this time (['H'; 'O'])!
$itemSession->beginAttempt();
$responses['RESPONSE'][1]->setValue('O');
$itemSession->endAttempt();
echo 'numAttempts: ' . $itemSession['numAttempts'] . "\n";
echo 'completionStatus: ' . $itemSession['completionStatus'] . "\n";
echo 'RESPONSE: ' . $itemSession['RESPONSE'] . "\n";
echo 'SCORE: ' . $itemSession['SCORE'] . "\n";
/*
* numAttempts: 2
* completionStatus: completed
* RESPONSE: ['H'; 'O']
* SCORE: 2
*/
$itemSession->endItemSession();
You can get more information on the QTI-SDK GitHub Wiki!
The QTI Software Development Kit enables you to transform XML serialized QTI files
into their (X)HTML5 Goldilocks equivalent. The following shell command renders the path/to/qti.xml
QTI file into an HTML5
document using the (X)HTML5 Golidlocks rendering flavour with indentation formatting. The rendering output (stdout)
is redirected to the /home/jerome/qti.html
file.
./vendor/bin/qtisdk render -df --source path/to/qti.xml --flavour goldilocks > /home/jerome/qti.html
For additional help and information, just call the help screen to know about the features provided by the rendering binaries!
./vendor/bin/qtisdk render --help
As for other major PHP frameworks such as Doctrine QTI-SDK makes use of annotations. In such a context, the two following Zend Opcache configuration directives must be configured as below.