Completed
Push — master ( e11051...f173c0 )
by mw
10s
created

ApiCacheableTemplateParse::doParse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 3
crap 2
1
<?php
2
3
namespace SUC;
4
5
use ParserOptions;
6
use ParserOutput;
7
use ApiBase;
8
use Title;
9
10
/**
11
 * Content parsing can be expensive especially if it contains a large set of
12
 * #ask queries/#if parser functions to form a summary card.
13
 *
14
 * This API module is to parse a text/template and if possible tries to retrieve
15
 * data from a persistent cache layer to avoid unnecessary content parsing.
16
 *
17
 * On the event of NewRevisionFromEditComplete a cached item will be evicted if
18
 * it matches the BackendCache::getHashFrom.
19
 *
20
 * On the event that a template that was used for generating content  was modified
21
 * then a re-parse of the content is requested the next time instead of a cache
22
 * retrieval.
23
 *
24
 * @license GNU GPL v2+
25
 * @since 1.0
26
 *
27
 * @author mwjames
28
 */
29
class ApiCacheableTemplateParse extends ApiBase {
30
31
	/**
32
	 * @var BackendCache
33
	 */
34
	private $backendCache;
35
36
	/**
37
	 * @since 1.0
38
	 *
39
	 * @param BackendCache $backendCache
40
	 */
41 3
	public function setBackendCache( BackendCache $backendCache ) {
42 3
		$this->backendCache = $backendCache;
43 3
	}
44
45
	/**
46
	 * ApiBase::execute
47
	 *
48
	 * @since 1.0
49
	 */
50 2
	public function execute() {
51
52 2
		$data = $this->getDataFrom(
53 2
			BackendCache::getInstance( $this->backendCache ),
54 2
			$this->extractRequestParams()
55 2
		);
56
57 2
 		$this->getResult()->addValue(
58 2
 			null,
59 2
 			$this->getModuleName(),
60
 			$data
61 2
 		);
62 2
	}
63
64
	/**
65
	 * ApiBase::getAllowedParams
66
	 *
67
	 * @since 1.0
68
	 */
69 2
	public function getAllowedParams() {
70
		return array(
71 2
			'text'         => array( ApiBase::PARAM_TYPE => 'string' ),
72 2
			'template'     => array( ApiBase::PARAM_TYPE => 'string' ),
73 2
			'title'        => array( ApiBase::PARAM_TYPE => 'string' ),
74 2
			'userlanguage' => array( ApiBase::PARAM_TYPE => 'string' )
75 2
		);
76
	}
77
78
	/**
79
	 * @codeCoverageIgnore
80
	 * @see ApiBase::getParamDescription
81
	 *
82
	 * @return array
83
	 */
84
	public function getParamDescription() {
85
		return array(
86
			'text'     => 'Contains the text/template to be parsed.',
87
			'template' => 'Contains the template to track possible changes.',
88
			'title'    => 'Subject to filter repeated requests.',
89
			'userlanguage' => 'The user language.'
90
		);
91
	}
92
93
	/**
94
	 * @codeCoverageIgnore
95
	 * @see ApiBase::getDescription
96
	 *
97
	 * @return array
98
	 */
99
	public function getDescription() {
100
		return array(
101
			'Module to parse raw text/templates and store returning results in a backend-cache.'
102
		);
103
	}
104
105 2
	private function getDataFrom( BackendCache $backendCache, array $params ) {
106
107 2
		$start = microtime( true );
108 2
		$untouchedTemplate = false;
109
110
		$data = array(
111 2
			'text' => '',
112
			'time' => false
113 2
		);
114
115 2
		if ( !isset( $params['text'] ) || !isset( $params['title'] ) ) {
116 1
			return $data;
117
		}
118
119 1
		$blobStore = $backendCache->getBlobStore();
120
121 1
		list( $templateKey, $templateTouched ) = $this->getTemplateFrom( $params );
122
123 1
		$title = $backendCache->getTargetFrom( $params['title'] );
124 1
		$hash = $backendCache->getHashFrom( $title );
125
126 1
		$container = $blobStore->read( $hash );
127
128
		// If the template was touched then re-parse the content to
129
		// avoid stalled data
130 1
		if ( $container->has( $templateKey ) ) {
131
			$untouchedTemplate = $container->get( $templateKey ) == $templateTouched;
132
		}
133
134
		// Split by lang and fragment into separate containers, while the cache
135
		// is stored on a per subject basis allowing all related containers to
136
		// be purged at once
137 1
		$key = 'L#' . $params['userlanguage'] . '#' . ( $title !== null ? $title->getFragment() : '' );
138
139 1
		if ( $untouchedTemplate && $hash !== '' && $container->has( $key ) ) {
140
			wfDebugLog( 'smw', 'SummaryCards API cache hit on ' . $hash );
141
			$data = $container->get( $key );
142
			$data['time']['cached'] = microtime( true ) - $start;
143
			return $data;
144
		}
145
146
		$data = array(
147 1
			'text' => $this->doParse( $title, $params['userlanguage'], $params['text'] ),
148
			'time' => array(
149 1
				'parse'  => microtime( true ) - $start
150 1
			)
151 1
		);
152
153
		// Only cache when a template is known
154 1
		if ( $hash !== '' && $templateKey !== '' ) {
155
			$container->set( $key, $data );
156
			$container->set( $templateKey, $templateTouched );
157
			$blobStore->save( $container );
158
		}
159
160 1
		return $data;
161
	}
162
163 1
	private function getTemplateFrom( $params ) {
164
165 1
		$templateKey = '';
166 1
		$templateTouched = 0;
167
168 1
		if ( isset( $params['template'] ) ) {
169
			$template = Title::makeTitleSafe( NS_TEMPLATE, $params['template'] );
170
			$templateTouched = $template->getTouched();
171
			$templateKey = 'T#' . $params['template'] . '#' . $params['userlanguage'];
172
		}
173
174 1
		return array( $templateKey, $templateTouched );
175
	}
176
177 1
	private function doParse( $title, $userLanguage, $text ) {
0 ignored issues
show
Coding Style introduced by
doParse uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
178
179 1
		$parserOutput = $GLOBALS['wgParser']->parse(
180 1
			$text,
181 1
			$title,
182 1
			$this->makeParserOptions( $title, $userLanguage )
183 1
		);
184
185 1
		return $parserOutput instanceof ParserOutput ? $parserOutput->getText() : '' ;
0 ignored issues
show
Bug introduced by
The class ParserOutput does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
186
	}
187
188 1
	private function makeParserOptions( $title, $userLanguage ) {
189
190 1
		$user = null;
191
192 1
		$parserOptions = new ParserOptions( $user );
193 1
		$parserOptions->setInterfaceMessage( true );
194 1
		$parserOptions->setUserLang( $userLanguage );
195
196 1
		return $parserOptions;
197
	}
198
199
}
200