Writing result printers

From semantic-mediawiki.org
Jump to: navigation, search
Table of Contents

This documentation is work in progress; some sections are not finished yet.

This page explains how to create new result printers which can be used to visualize the result of an SMW query. This is a developers topic, and is not relevant to regular users.

This guide applies for result printers compatible with SMW 1.7 and later, and MediaWiki 1.17 and later.

Do note that a lot of the current result printers do not adhere to the guidelines described here due to historical reasons. Therefore copying them and modifying only the bits you care about is not always a good idea, and often contains more code than is needed with recent versions of SMW.

Registering the format

To register new format you need to add your format name to $srfgFormats array and also modify $smwgResultFormats array is the following:

global $srfgFormats, $smwgResultFormats;
$srfgFormats[] = 'my-format';
$smwgResultFormats['my-format'] = 'MyFormatClass';

Creating the class

Each result printer is implemented as a class deriving from SMWResultPrinter. You will most likely need to implement these 3 methods:

getName

Get a human readable label for this printer. This can be done like this:

/**
 * (non-PHPdoc)
 * @see SMWResultPrinter::getName()
 */
public function getName() {
    return wfMsg( 'message-key-for-this-name' );
}

getResultText

This method gets the query result object and is supposed to return whatever output the format creates. For example, in the list format, it goes through all results and constructs an HTML list, which is then returned. Looping through the result object is somewhat complex, and requires some understanding of the SMWQueryResult class.

This is an example from the DSV query printer, where $res is the query result object.

$lines = array();
 
if ( $this->mShowHeaders ) {
	$headerItems = array();
 
	foreach ( $queryResult->getPrintRequests() as $printRequest ) {
		$headerItems[] = $printRequest->getLabel();
	}
 
	$lines[] = $this->getDSVLine( $headerItems );
}
 
// Loop over the result objects (pages).
while ( $row = $queryResult->getNext() ) {
	$rowItems = array();
 
	/**
	 * Loop over their fields (properties).
	 * @var SMWResultArray $field
	 */
	foreach ( $row as $field ) {
		$itemSegments = array();
 
		// Loop over all values for the property.
		while ( ( $object = $field->getNextDataValue() ) !== false ) {
			$itemSegments[] = Sanitizer::decodeCharReferences( $object->getWikiValue() );
		} 
 
		// Join all values into a single string, separating them with comma's.
		$rowItems[] = implode( ',', $itemSegments );
	}
 
	$lines[] = $this->getDSVLine( $rowItems );
}
 
return implode( "\n", $lines );

Putting in comments and using type hinting can make the code a lot clearer. Also make sure you split up the functionality into multiple methods when it makes sense. In general, do not create methods longer then 70 lines, unless they are very simple. Also don't put a lot of code in a loop, or worse yet, nested loop. In this example there is only a single line in the inner loop, if it where say 20, it'd be better to put this into a separate method (ie such as was done with the things that are in getDSVLine).

Parameter handling

Parameters passed to your result printer can be accessed via the $params field, which gets set by the base class before getResultText() is called. So if you want the value for parameter foobar, use $this->params['foobar']. It is not needed to check if these parameters are set, if they are of the right type, or adhere to any restrictions you might want to put on them. This will already have happened at this point in the code. Invalid and non-set values will have been changed to their default. Invalid or missing required parameters would have caused an abort earlier on, so getResultText() would not get called. When outputting any of these values, you will have to escape them using the core MediaWiki escaping functionality for security reasons.

JavaScript

If you have data that needs to be outputted for use in JavaScript, such as points to plot on a chart, do not create a string with JavaScript in which you insert PHP variables, and you then add as inline JS. Inline JS with logic should always be avoided. Instead just construct a data object (ie JSON) which you then interpret with JS in a separate file. This way the page will load faster and the code will be better in several ways.

In most MediaWiki code, a great way to get your data from PHP to such JS objects is simply creating one big PHP object (arrays and associative arrays) that holds it all and turn it into JSON using FormatJSON::encode. This function takes care of all escaping for you :) Inn query printers you need to use the SMWOutputs class, which makes sure your code works both in articles (when the result printers is used for inline ask queries), and on special pages, such as Special:ask and Special:BrowseData. The method needed for adding your JS is SMWOutputs::requireHeadItem, which takes an ID and your actual JS. You first need to wrap it into a script tag though. Instead of using FormatJSON::encode you can use Skin::makeVariablesScript which takes your PHP object, turns it into JS, and puts it into script tags, in a way that you'll be able to access the top level elements of your array using mw.config.get( 'array-key-name' ) in the JS. Example:

SMWOutputs::requireHeadItem(
	$chartId, // Unique id
	Skin::makeVariablesScript( $jsObject )
);

JavaScript files and other resources (such as CSS) get added in the form of ResourceLoader modules. They should also be added using SMWOutputs, with the method SMWOutputs::requireResource(), which takes the module name.

getParameters

A function to describe the allowed parameters of a query using any specific format. It should return an array of Parameter objects. These define in a declarative fashion which parameters your result printer accepts, which their type is, their default value, ect. See the Validator documentation for more info.

Example:

/**
 * @see SMWResultPrinter::getParamDefinitions
 *
 * @since 1.8
 *
 * @param $definitions array of IParamDefinition
 *
 * @return array of IParamDefinition|array
 */
public function getParamDefinitions( array $definitions ) {
	$params = parent::getParamDefinitions( $definitions );
 
	$params['searchlabel']->setDefault( wfMsgForContent( 'smw_dsv_link' ) );
 
	$params['limit']->setDefault( 100 );
 
	$params[] = array(
		'name' => 'separator',
		'message' => 'smw-paramdesc-dsv-separator',
		'default' => $this->separator,
		'aliases' => 'sep',
	);
 
	$params[] = array(
		'name' => 'filename',
		'message' => 'smw-paramdesc-dsv-filename',
		'default' => $this->fileName,
	);
 
	return $params;
}

You should always first get the params added by the parent class, using parent::getParameters(). Export formats can obtain the export specific parameters (such as searchlabel) via the exportFormatParameters method, also defined in the base class. (Non-export formats can just do $params = parent::getParameters();)

Aggregatable printers

Result printer that supports the distribution parameter, and related parameters. It allows the user to choose between regular behaviour or getting a distribution of values.

For example, this result set: "foo bar baz foo bar bar ohi" will be turned into

  • bar (3)
  • foo (2)
  • baz (1)
  • ohi (1)

getFormatOutput

Such formats inherit from the class SMWAggregatablePrinter, which in turn derives from SMWResultPrinter. In most cases they should not implement getResultText, which is handled by SMWAggregatablePrinter. Instead they should implement the getFormatOutput method. It has a similar function as getResultText, since it is also responsible for constructing the formats output and returning it. However, it does not get handled a query result object, but rather an array with already processed data depending on the values of the distribution-related parameters. This data is an associative array where each array key is a label, and their corresponding value is a number.

addResources

The SMWAggregatablePrinter also has a addResources method which gets called case there are actual results, and so provides a convenient way to add resources such as JavaScript and CSS. This method is a stub in the SMWAggregatablePrinter class which can be overridden, but this is of course not required.

Boilerplate

For a description on how to make best use of the the SRF boilerplate, have a look here.

JavaScript integration

See Writing javascript result printers

Writing tests

See Unit tests

It's important you write tests for your new code. As of version 1.8, SMW provides a base test class for result printers which you should use when writing such tests. For an example, see the tests for the Math result printer.

Tutorial

For an introduction on how to write tests for phpunit.

See also