QueryBuilder   B
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 32
Bugs 8 Features 14
Metric Value
wmc 39
c 32
b 8
f 14
lcom 1
cbo 7
dl 0
loc 421
rs 8.2857

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getSelects() 0 3 1
A select() 0 8 2
A selectDistinct() 0 9 2
A selectReduced() 0 9 2
A describe() 0 12 2
A setQueryForm() 0 7 2
A addExpressions() 0 18 3
A where() 0 4 1
A also() 0 4 1
A optional() 0 4 1
A filter() 0 4 1
A filterExists() 0 4 1
A filterNotExists() 0 4 1
A union() 0 4 1
A subquery() 0 4 1
A newSubquery() 0 3 1
A newSubgraph() 0 3 1
A groupBy() 0 6 2
A having() 0 4 1
A orderBy() 0 4 1
A limit() 0 4 1
A offset() 0 4 1
A getSPARQL() 0 16 4
A formatSelects() 0 3 2
A __toString() 0 3 1
A format() 0 4 1
1
<?php
2
3
namespace Asparagus;
4
5
use InvalidArgumentException;
6
use RangeException;
7
use RuntimeException;
8
9
/**
10
 * Abstraction layer to build SPARQL queries
11
 *
12
 * Nested filters not supported
13
 * Supports SPARQL v1.0 (v1.1 to come)
14
 *
15
 * @since 0.1
16
 *
17
 * @license GNU GPL v2+
18
 * @author Bene* < [email protected] >
19
 */
20
class QueryBuilder {
21
22
	/**
23
	 * @var ExpressionValidator
24
	 */
25
	private $expressionValidator;
26
27
	/**
28
	 * @var UsageValidator
29
	 */
30
	private $usageValidator;
31
32
	/**
33
	 * @var QueryPrefixBuilder
34
	 */
35
	private $prefixBuilder;
36
37
	/**
38
	 * @var string[] list of expressions to select
39
	 */
40
	private $selects = array();
41
42
	/**
43
	 * @var string uniqueness constraint, one of DISTINCT, REDUCED or empty
44
	 */
45
	private $uniqueness = '';
46
47
	/**
48
	 * @var string form of the query, one of SELECT or DESCRIBE
49
	 */
50
	private $queryForm = null;
51
52
	/**
53
	 * @var GraphBuilder
54
	 */
55
	private $graphBuilder;
56
57
	/**
58
	 * @var QueryModifierBuilder
59
	 */
60
	private $modifierBuilder;
61
62
	/**
63
	 * @var string[] $prefixes
64
	 * @throws InvalidArgumentException
65
	 */
66
	public function __construct( array $prefixes = array() ) {
67
		$this->expressionValidator = new ExpressionValidator();
68
		$this->usageValidator = new UsageValidator();
69
		$this->prefixBuilder = new QueryPrefixBuilder( $prefixes, $this->usageValidator );
70
		$this->graphBuilder = new GraphBuilder( $this->usageValidator );
71
		$this->modifierBuilder = new QueryModifierBuilder( $this->usageValidator );
72
	}
73
74
	/**
75
	 * @since 0.3
76
	 *
77
	 * @return string[] list of expressions to select
78
	 */
79
	public function getSelects() {
80
		return $this->selects;
81
	}
82
83
	/**
84
	 * Specifies the expressions to select.
85
	 *
86
	 * @param string|string[] $expressions
87
	 * @return self
88
	 * @throws InvalidArgumentException
89
	 * @throws RuntimeException
90
	 */
91
	public function select( $expressions /* expressions ... */ ) {
92
		$expressions = is_array( $expressions ) ? $expressions : func_get_args();
93
94
		$this->setQueryForm( 'SELECT' );
95
		$this->addExpressions( $expressions );
96
97
		return $this;
98
	}
99
100
	/**
101
	 * Specifies the expressions to select. Duplicate results are eliminated.
102
	 *
103
	 * @since 0.3
104
	 *
105
	 * @param string|string[] $expressions
106
	 * @return self
107
	 * @throws InvalidArgumentException
108
	 * @throws RuntimeException
109
	 */
110
	public function selectDistinct( $expressions /* expressions ... */ ) {
111
		$expressions = is_array( $expressions ) ? $expressions : func_get_args();
112
113
		$this->setQueryForm( 'SELECT' );
114
		$this->uniqueness = 'DISTINCT ';
115
		$this->addExpressions( $expressions );
116
117
		return $this;
118
	}
119
120
	/**
121
	 * Specifies the expressions to select. Duplicate results may be eliminated.
122
	 *
123
	 * @since 0.3
124
	 *
125
	 * @param string|string[] $expressions
126
	 * @return self
127
	 * @throws InvalidArgumentException
128
	 * @throws RuntimeException
129
	 */
130
	public function selectReduced( $expressions /* expressions ... */ ) {
131
		$expressions = is_array( $expressions ) ? $expressions : func_get_args();
132
133
		$this->setQueryForm( 'SELECT' );
134
		$this->uniqueness = 'REDUCED ';
135
		$this->addExpressions( $expressions );
136
137
		return $this;
138
	}
139
140
	/**
141
	 * Specifies the expressions to describe.
142
	 *
143
	 * @since 0.4
144
	 *
145
	 * @param string|string[] $expressions
146
	 * @return self
147
	 * @throws InvalidArgumentException
148
	 * @throws RuntimeException
149
	 */
150
	public function describe( $expressions /* expressions ... */ ) {
151
		$expressions = is_array( $expressions ) ? $expressions : func_get_args();
152
153
		$this->setQueryForm( 'DESCRIBE' );
154
		$this->addExpressions(
155
			$expressions,
156
			ExpressionValidator::VALIDATE_VARIABLE | ExpressionValidator::VALIDATE_FUNCTION_AS
157
				| ExpressionValidator::VALIDATE_IRI | ExpressionValidator::VALIDATE_PREFIXED_IRI
158
		);
159
160
		return $this;
161
	}
162
163
	private function setQueryForm( $queryForm ) {
164
		if ( $this->queryForm !== null ) {
165
			throw new RuntimeException( 'Query type is already set to ' . $this->queryForm );
166
		}
167
168
		$this->queryForm = $queryForm;
169
	}
170
171
	private function addExpressions( array $expressions, $options = null ) {
172
		foreach ( $expressions as $expression ) {
173
			$this->expressionValidator->validate(
174
				$expression,
175
				$options ?: ExpressionValidator::VALIDATE_VARIABLE | ExpressionValidator::VALIDATE_FUNCTION_AS
176
			);
177
178
			// @todo temp hack to add AS definitions to defined variables
179
			$regexHelper = new RegexHelper();
180
			$matches = $regexHelper->getMatches( 'AS \{variable}', $expression );
181
			$this->usageValidator->trackDefinedVariables( $matches );
182
183
			// @todo detect functions and wrap with brackets automatically
184
			$this->usageValidator->trackUsedVariables( $expression );
185
			$this->usageValidator->trackUsedPrefixes( $expression );
186
			$this->selects[] = $expression;
187
		}
188
	}
189
190
	/**
191
	 * Adds the given triple as a condition.
192
	 *
193
	 * @param string $subject
194
	 * @param string $predicate
195
	 * @param string $object
196
	 * @return self
197
	 * @throws InvalidArgumentException
198
	 */
199
	public function where( $subject, $predicate, $object ) {
200
		$this->graphBuilder->where( $subject, $predicate, $object );
201
		return $this;
202
	}
203
204
	/**
205
	 * Adds the given triple/double/single value as an additional condition
206
	 * to the previously added condition.
207
	 *
208
	 * @param string $subject
209
	 * @param string|null $predicate
210
	 * @param string|null $object
211
	 * @return self
212
	 * @throws InvalidArgumentException
213
	 */
214
	public function also( $subject, $predicate = null, $object = null ) {
215
		$this->graphBuilder->also( $subject, $predicate, $object );
216
		return $this;
217
	}
218
219
	/**
220
	 * Adds the given graph or triple as an optional condition.
221
	 *
222
	 * @since 0.3
223
	 *
224
	 * @param string|GraphBuilder $subject
225
	 * @param string|null $predicate
226
	 * @param string|null $object
227
	 * @return self
228
	 * @throws InvalidArgumentException
229
	 */
230
	public function optional( $subject, $predicate = null, $object = null ) {
231
		$this->graphBuilder->optional( $subject, $predicate, $object );
232
		return $this;
233
	}
234
235
	/**
236
	 * Adds the given expression as a filter to this query.
237
	 *
238
	 * @since 0.3
239
	 *
240
	 * @param string $expression
241
	 * @return self
242
	 * @throws InvalidArgumentException
243
	 */
244
	public function filter( $expression ) {
245
		$this->graphBuilder->filter( $expression );
246
		return $this;
247
	}
248
249
	/**
250
	 * Adds a filter that the given graph or triple exists.
251
	 *
252
	 * @since 0.3
253
	 *
254
	 * @param string|GraphBuilder $subject
255
	 * @param string|null $predicate
256
	 * @param string|null $object
257
	 * @return self
258
	 * @throws InvalidArgumentException
259
	 */
260
	public function filterExists( $subject, $predicate = null, $object = null ) {
261
		$this->graphBuilder->filterExists( $subject, $predicate, $object );
262
		return $this;
263
	}
264
265
	/**
266
	 * Adds a filter that the given graph or triple does not exist.
267
	 *
268
	 * @since 0.3
269
	 *
270
	 * @param string|GraphBuilder $subject
271
	 * @param string|null $predicate
272
	 * @param string|null $object
273
	 * @return self
274
	 * @throws InvalidArgumentException
275
	 */
276
	public function filterNotExists( $subject, $predicate = null, $object = null ) {
277
		$this->graphBuilder->filterNotExists( $subject, $predicate, $object );
278
		return $this;
279
	}
280
281
	/**
282
	 * Adds the given graphs as alternative conditions.
283
	 *
284
	 * @since 0.3
285
	 *
286
	 * @param GraphBuilder|GraphBuilder[] $graphs
287
	 * @return self
288
	 * @throws InvalidArgumentException
289
	 */
290
	public function union( $graphs /* graphs ... */ ) {
0 ignored issues
show
Unused Code introduced by
The parameter $graphs is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
291
		call_user_func_array( array( $this->graphBuilder, 'union' ), func_get_args() );
292
		return $this;
293
	}
294
295
	/**
296
	 * Adds the given subquery.
297
	 *
298
	 * @param QueryBuilder $query
299
	 * @return self
300
	 * @throws InvalidArgumentException
301
	 */
302
	public function subquery( QueryBuilder $query ) {
303
		$this->graphBuilder->subquery( $query );
304
		return $this;
305
	}
306
307
	/**
308
	 * Creates a new subquery builder.
309
	 *
310
	 * @return QueryBuilder
311
	 */
312
	public function newSubquery() {
313
		return new QueryBuilder( $this->prefixBuilder->getPrefixes() );
314
	}
315
316
	/**
317
	 * Creates a new subgraph builder.
318
	 *
319
	 * @since 0.3
320
	 *
321
	 * @return GraphBuilder
322
	 */
323
	public function newSubgraph() {
324
		return new GraphBuilder( $this->usageValidator );
325
	}
326
327
	/**
328
	 * Sets the GROUP BY modifier.
329
	 *
330
	 * @param string|string[] $expressions
331
	 * @return self
332
	 * @throws InvalidArgumentException
333
	 */
334
	public function groupBy( $expressions /* expressions ... */ )  {
335
		$expressions = is_array( $expressions ) ? $expressions : func_get_args();
336
337
		$this->modifierBuilder->groupBy( $expressions );
338
		return $this;
339
	}
340
341
	/**
342
	 * Sets the HAVING modifier.
343
	 *
344
	 * @param string $expression
345
	 * @return self
346
	 * @throws InvalidArgumentException
347
	 */
348
	public function having( $expression ) {
349
		$this->modifierBuilder->having( $expression );
350
		return $this;
351
	}
352
353
	/**
354
	 * Sets the ORDER BY modifier.
355
	 *
356
	 * @param string $expression
357
	 * @param string $direction one of ASC or DESC
358
	 * @return self
359
	 * @throws InvalidArgumentException
360
	 */
361
	public function orderBy( $expression, $direction = 'ASC' ) {
362
		$this->modifierBuilder->orderBy( $expression, $direction );
363
		return $this;
364
	}
365
366
	/**
367
	 * Sets the LIMIT modifier.
368
	 *
369
	 * @param int $limit
370
	 * @return self
371
	 * @throws InvalidArgumentException
372
	 */
373
	public function limit( $limit ) {
374
		$this->modifierBuilder->limit( $limit );
375
		return $this;
376
	}
377
378
	/**
379
	 * Sets the OFFSET modifier.
380
	 *
381
	 * @param int $offset
382
	 * @return self
383
	 * @throws InvalidArgumentException
384
	 */
385
	public function offset( $offset ) {
386
		$this->modifierBuilder->offset( $offset );
387
		return $this;
388
	}
389
390
	/**
391
	 * Returns the plain SPARQL string of this query.
392
	 *
393
	 * @param bool $includePrefixes
394
	 * @return string
395
	 * @throws InvalidArgumentException
396
	 * @throws RangeException
397
	 */
398
	public function getSPARQL( $includePrefixes = true ) {
399
		if ( !is_bool( $includePrefixes ) ) {
400
			throw new InvalidArgumentException( '$includePrefixes has to be a bool' );
401
		}
402
403
		$this->usageValidator->validate();
404
405
		$sparql = $includePrefixes ? $this->prefixBuilder->getSPARQL() : '';
406
		// @todo throw an exception instead of defaulting to SELECT?
407
		$sparql .= $this->queryForm ?: 'SELECT';
408
		$sparql .= ' ' . $this->uniqueness . $this->formatSelects() . ' WHERE';
409
		$sparql .= ' {' . $this->graphBuilder->getSPARQL() . ' }';
410
		$sparql .= $this->modifierBuilder->getSPARQL();
411
412
		return $sparql;
413
	}
414
415
	private function formatSelects() {
416
		return empty( $this->selects ) ? '*' : implode( ' ', $this->selects );
417
	}
418
419
	/**
420
	 * @see self::getSPARQL
421
	 *
422
	 * @return string
423
	 */
424
	public function __toString() {
425
		return $this->getSPARQL();
426
	}
427
428
	/**
429
	 * Returns the formatted SPARQL string of this query.
430
	 *
431
	 * @see QueryFormatter::format
432
	 *
433
	 * @return string
434
	 */
435
	public function format() {
436
		$formatter = new QueryFormatter();
437
		return $formatter->format( $this->getSPARQL() );
438
	}
439
440
}
441