Completed
Push — master ( b08b65...b78d2e )
by mw
230:52 queued 205:17
created

GenericHttpRepositoryConnector::getPrefixString()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 16
nop 2
dl 0
loc 16
ccs 10
cts 10
cp 1
crap 5
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace SMW\SPARQLStore\RepositoryConnectors;
4
5
use Onoi\HttpRequest\HttpRequest;
6
use SMW\SPARQLStore\BadHttpResponseMapper;
7
use SMW\SPARQLStore\Exception\BadHttpDatabaseResponseException;
8
use SMW\SPARQLStore\QueryEngine\RepositoryResult;
9
use SMW\SPARQLStore\QueryEngine\XmlResponseParser;
10
use SMW\SPARQLStore\RepositoryClient;
11
use SMW\SPARQLStore\RepositoryConnection;
12
use SMWExporter as Exporter;
13
14
/**
15
 * Basic database connector for exchanging data via SPARQL.
16
 *
17
 * @license GNU GPL v2+
18
 * @since 1.6
19
 *
20
 * @author Markus Krötzsch
21
 */
22
class GenericHttpRepositoryConnector implements RepositoryConnection {
23
24
	/**
25
	 * Flag denoting endpoints being capable of querying
26
	 */
27
	const EP_TYPE_QUERY = 1;
28
29
	/**
30
	 * Flag denoting endpoints being capable of updating
31
	 */
32
	const EP_TYPE_UPDATE = 2;
33
34
	/**
35
	 * Flag denoting endpoints being capable of SPARQL HTTP graph management
36
	 */
37
	const EP_TYPE_DATA = 4;
38
39
	/**
40
	 * @var RepositoryClient
41
	 */
42
	protected $repositoryClient;
43
44
	/**
45
	 * @note Handles the curl handle and is reused throughout the instance to
46
	 * safe some initialization effort
47
	 *
48
	 * @var HttpRequest
49
	 */
50
	protected $httpRequest;
51
52
	/**
53
	 * @var BadHttpResponseMapper
54
	 */
55
	private $badHttpResponseMapper;
56
57
	/**
58
	 * @note It is suggested to use the RepositoryConnectionProvider to create
59
	 * a valid instance
60
	 *
61
	 * @since 2.2
62
	 *
63
	 * @param RepositoryClient $repositoryClient
64
	 * @param HttpRequest $httpRequest
65
	 */
66 57
	public function __construct( RepositoryClient $repositoryClient, HttpRequest $httpRequest ) {
67 57
		$this->repositoryClient = $repositoryClient;
68 57
		$this->httpRequest = $httpRequest;
69
70 57
		$this->httpRequest->setOption( CURLOPT_FORBID_REUSE, false );
71 57
		$this->httpRequest->setOption( CURLOPT_FRESH_CONNECT, false );
72 57
		$this->httpRequest->setOption( CURLOPT_RETURNTRANSFER, true ); // put result into variable
73 57
		$this->httpRequest->setOption( CURLOPT_FAILONERROR, true );
74
75 57
		$this->setConnectionTimeoutInSeconds( 10 );
76 57
	}
77
78
	/**
79
	 * Get the URI of the default graph that this database connector is
80
	 * using, or the empty string if none is used (no graph related
81
	 * statements in queries/updates).
82
	 *
83
	 * @return string graph UIR or empty
84
	 */
85
	public function getDefaultGraph() {
86
		return $this->repositoryClient->getDefaultGraph();
87
	}
88
89
	/**
90
	 * Check if the database can be contacted.
91
	 *
92
	 * @todo SPARQL endpoints sometimes return errors if no (valid) query
93
	 * is posted. The current implementation tries to catch this, but this
94
	 * might not be entirely correct. Especially, the SPARQL 1.1 HTTP error
95
	 * codes for Update are not defined yet (April 15 2011).
96
	 *
97
	 * @param $pingQueryEndpoint boolean true if the query endpoint should be
98
	 * pinged, false if the update endpoint should be pinged
99
	 *
100
	 * @return boolean to indicate success
101
	 */
102
	public function ping( $endpointType = self::EP_TYPE_QUERY ) {
103
		if ( $endpointType == self::EP_TYPE_QUERY ) {
104
			$this->httpRequest->setOption( CURLOPT_URL, $this->repositoryClient->getQueryEndpoint() );
105
			$this->httpRequest->setOption( CURLOPT_NOBODY, true );
106
			$this->httpRequest->setOption( CURLOPT_POST, true );
107
		} elseif ( $endpointType == self::EP_TYPE_UPDATE ) {
108
109
			if ( $this->repositoryClient->getUpdateEndpoint() === '' ) {
110
				return false;
111
			}
112
113
			$this->httpRequest->setOption( CURLOPT_URL, $this->repositoryClient->getUpdateEndpoint() );
114
			$this->httpRequest->setOption( CURLOPT_NOBODY, false ); // 4Store gives 404 instead of 500 with CURLOPT_NOBODY
115
116
		} else { // ( $endpointType == self::EP_TYPE_DATA )
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
117
118
			if ( $this->repositoryClient->getDataEndpoint() === '' ) {
119
				return false;
120
			}
121
122
			// try an empty POST
123
			return $this->doHttpPost( '' );
124
		}
125
126
		$this->httpRequest->execute();
127
128
		if ( $this->httpRequest->getLastErrorCode() == 0 ) {
129
			return true;
130
		}
131
132
		// valid HTTP responses from a complaining SPARQL endpoint that is alive and kicking
133
		$httpCode = $this->httpRequest->getLastTransferInfo( CURLINFO_HTTP_CODE );
134
		return ( ( $httpCode == 500 ) || ( $httpCode == 400 ) );
135
	}
136
137
	/**
138
	 * SELECT wrapper.
139
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
140
	 * rdfs, property, xsd, so these do not have to be included in
141
	 * $extraNamespaces.
142
	 *
143
	 * @param $vars mixed array or string, field name(s) to be retrieved, can be '*'
144
	 * @param $where string WHERE part of the query, without surrounding { }
145
	 * @param $options array (associative) of options, e.g. array( 'LIMIT' => '10' )
146
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
147
	 *
148
	 * @return RepositoryResult
149
	 */
150
	public function select( $vars, $where, $options = array(), $extraNamespaces = array() ) {
151
		return $this->doQuery( $this->getSparqlForSelect( $vars, $where, $options, $extraNamespaces ) );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doQuery($t...ns, $extraNamespaces)); (SMW\SPARQLStore\QueryEngine\RepositoryResult) is incompatible with the return type declared by the interface SMW\SPARQLStore\RepositoryConnection::select of type SMW\SPARQLStore\RepositoryResult.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
152
	}
153
154
	/**
155
	 * Build the SPARQL query that is used by GenericHttpDatabaseConnector::select().
156
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
157
	 * rdfs, property, xsd, so these do not have to be included in
158
	 * $extraNamespaces.
159
	 *
160
	 * @param $where string WHERE part of the query, without surrounding { }
161
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
162
	 *
163
	 * @return string SPARQL query
164
	 */
165
	public function getSparqlForSelect( $vars, $where, $options = array(), $extraNamespaces = array() ) {
166
167
		$sparql = self::getPrefixString( $extraNamespaces ) . 'SELECT ';
168
169
		if ( array_key_exists( 'DISTINCT', $options ) ) {
170
			$sparql .= 'DISTINCT ';
171
		}
172
173
		if ( is_array( $vars ) ) {
174
			$sparql .= implode( ',', $vars );
175
		} else {
176
			$sparql .= $vars;
177
		}
178
179
		$sparql .= " WHERE {\n" . $where . "\n}";
180
181
		if ( array_key_exists( 'ORDER BY', $options ) ) {
182
			$sparql .= "\nORDER BY " . $options['ORDER BY'];
183
		}
184
185
		if ( array_key_exists( 'OFFSET', $options ) ) {
186
			$sparql .= "\nOFFSET " . $options['OFFSET'];
187
		}
188
189
		if ( array_key_exists( 'LIMIT', $options ) ) {
190
			$sparql .= "\nLIMIT " . $options['LIMIT'];
191
		}
192
193
		return $sparql;
194
	}
195
196
	/**
197
	 * ASK wrapper.
198
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
199
	 * rdfs, property, xsd, so these do not have to be included in
200
	 * $extraNamespaces.
201
	 *
202
	 * @param $where string WHERE part of the query, without surrounding { }
203
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
204
	 *
205
	 * @return RepositoryResult
206
	 */
207 7
	public function ask( $where, $extraNamespaces = array() ) {
208 7
		return $this->doQuery( $this->getSparqlForAsk( $where, $extraNamespaces ) );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->doQuery($t...re, $extraNamespaces)); (SMW\SPARQLStore\QueryEngine\RepositoryResult) is incompatible with the return type declared by the interface SMW\SPARQLStore\RepositoryConnection::ask of type SMW\SPARQLStore\RepositoryResult.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
209
	}
210
211
	/**
212
	 * Build the SPARQL query that is used by GenericHttpDatabaseConnector::ask().
213
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
214
	 * rdfs, property, xsd, so these do not have to be included in
215
	 * $extraNamespaces.
216
	 *
217
	 * @param $where string WHERE part of the query, without surrounding { }
218
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
219
	 *
220
	 * @return string SPARQL query
221
	 */
222 7
	public function getSparqlForAsk( $where, $extraNamespaces = array() ) {
223 7
		return self::getPrefixString( $extraNamespaces ) . "ASK {\n" . $where . "\n}";
224
	}
225
226
	/**
227
	 * SELECT wrapper for counting results.
228
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
229
	 * rdfs, property, xsd, so these do not have to be included in
230
	 * $extraNamespaces.
231
	 *
232
	 * @param $variable string variable name or '*'
233
	 * @param $where string WHERE part of the query, without surrounding { }
234
	 * @param $options array (associative) of options, e.g. array('LIMIT' => '10')
235
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
236
	 *
237
	 * @return RepositoryResult
238
	 */
239 1
	public function selectCount( $variable, $where, $options = array(), $extraNamespaces = array() ) {
240
241 1
		$sparql = self::getPrefixString( $extraNamespaces ) . 'SELECT (COUNT(';
242
243 1
		if ( array_key_exists( 'DISTINCT', $options ) ) {
244 1
			$sparql .= 'DISTINCT ';
245
		}
246
247 1
		$sparql .= $variable . ") AS ?count) WHERE {\n" . $where . "\n}";
248
249 1
		if ( array_key_exists( 'OFFSET', $options ) ) {
250 1
			$sparql .= "\nOFFSET " . $options['OFFSET'];
251
		}
252
253 1
		if ( array_key_exists( 'LIMIT', $options ) ) {
254 1
			$sparql .= "\nLIMIT " . $options['LIMIT'];
255
		}
256
257 1
		return $this->doQuery( $sparql );
258
	}
259
260
	/**
261
	 * DELETE wrapper.
262
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
263
	 * rdfs, property, xsd, so these do not have to be included in
264
	 * $extraNamespaces.
265
	 *
266
	 * @param $deletePattern string CONSTRUCT pattern of tripples to delete
267
	 * @param $where string condition for data to delete
268
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
269
	 *
270
	 * @return boolean stating whether the operations succeeded
271
	 */
272 5
	public function delete( $deletePattern, $where, $extraNamespaces = array() ) {
273
274 5
		$defaultGraph = $this->repositoryClient->getDefaultGraph();
275
276 5
		$sparql = self::getPrefixString( $extraNamespaces ) .
277 5
			( ( $defaultGraph !== '' )? "WITH <{$defaultGraph}> " : '' ) .
278 5
			"DELETE { $deletePattern } WHERE { $where }";
279
280 5
		return $this->doUpdate( $sparql );
281
	}
282
283
	/**
284
	 * Convenience method for deleting all triples that have a subject that
285
	 * occurs in a triple with the given property and object. This is used
286
	 * in SMW to delete subobjects with all their data. Some RDF stores fail
287
	 * on complex delete queries, hence a wrapper function is provided to
288
	 * allow more pedestrian implementations.
289
	 *
290
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
291
	 * rdfs, property, xsd, so these do not have to be included in
292
	 * $extraNamespaces.
293
	 *
294
	 * @param $propertyName string Turtle name of marking property
295
	 * @param $objectName string Turtle name of marking object/value
296
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
297
	 *
298
	 * @return boolean stating whether the operations succeeded
299
	 */
300
	public function deleteContentByValue( $propertyName, $objectName, $extraNamespaces = array() ) {
301
		return $this->delete( "?s ?p ?o", "?s $propertyName $objectName . ?s ?p ?o", $extraNamespaces );
302
	}
303
304
	/**
305
	 * Convenience method for deleting all triples of the entire store
306
	 *
307
	 * @return boolean
308
	 */
309
	public function deleteAll() {
310
		return $this->delete( "?s ?p ?o", "?s ?p ?o" );
311
	}
312
313
	/**
314
	 * INSERT DELETE wrapper.
315
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
316
	 * rdfs, property, xsd, so these do not have to be included in
317
	 * $extraNamespaces.
318
	 *
319
	 * @param $insertPattern string CONSTRUCT pattern of tripples to insert
320
	 * @param $deletePattern string CONSTRUCT pattern of tripples to delete
321
	 * @param $where string condition for data to delete
322
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
323
	 *
324
	 * @return boolean stating whether the operations succeeded
325
	 */
326
	public function insertDelete( $insertPattern, $deletePattern, $where, $extraNamespaces = array() ) {
327
328
		$defaultGraph = $this->repositoryClient->getDefaultGraph();
329
330
		$sparql = self::getPrefixString( $extraNamespaces ) .
331
			( ( $defaultGraph !== '' )? "WITH <{$defaultGraph}> " : '' ) .
332
			"DELETE { $deletePattern } INSERT { $insertPattern } WHERE { $where }";
333
334
		return $this->doUpdate( $sparql );
335
	}
336
337
	/**
338
	 * INSERT DATA wrapper.
339
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
340
	 * rdfs, property, xsd, so these do not have to be included in
341
	 * $extraNamespaces.
342
	 *
343
	 * @param $triples string of triples to insert
344
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
345
	 *
346
	 * @return boolean stating whether the operations succeeded
347
	 */
348 5
	public function insertData( $triples, $extraNamespaces = array() ) {
349
350 5
		if ( $this->repositoryClient->getDataEndpoint() !== '' ) {
351 5
			$turtle = self::getPrefixString( $extraNamespaces, false ) . $triples;
352 5
			return $this->doHttpPost( $turtle );
353
		}
354
355
		$defaultGraph = $this->repositoryClient->getDefaultGraph();
356
357
		$sparql = self::getPrefixString( $extraNamespaces, true ) .
358
			"INSERT DATA  " .
359
			( ( $defaultGraph !== '' )? " { GRAPH <{$defaultGraph}> " : '' ) .
360
			"{ $triples } " .
361
			( ( $defaultGraph !== '' )? " } " : '' );
362
363
		return $this->doUpdate( $sparql );
364
	}
365
366
	/**
367
	 * DELETE DATA wrapper.
368
	 * The function declares the standard namespaces wiki, swivt, rdf, owl,
369
	 * rdfs, property, xsd, so these do not have to be included in
370
	 * $extraNamespaces.
371
	 *
372
	 * @param $triples string of triples to delete
373
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
374
	 *
375
	 * @return boolean stating whether the operations succeeded
376
	 */
377
	public function deleteData( $triples, $extraNamespaces = array() ) {
378
379
		$defaultGraph = $this->repositoryClient->getDefaultGraph();
380
381
		$sparql = self::getPrefixString( $extraNamespaces ) .
382
			"DELETE DATA { " .
383
			( ( $defaultGraph !== '' )? "GRAPH <{$defaultGraph}> " : '' ) .
384
			"{ $triples } }";
385
386
		return $this->doUpdate( $sparql );
387
	}
388
389
390
	/**
391
	 * Execute a SPARQL query and return an RepositoryResult object
392
	 * that contains the results. The method throws exceptions based on
393
	 * GenericHttpDatabaseConnector::mapHttpRequestError(). If errors occur and this
394
	 * method does not throw anything, then an empty result with an error
395
	 * code is returned.
396
	 *
397
	 * @note This function sets the graph that is to be used as part of the
398
	 * request. Queries should not include additional graph information.
399
	 *
400
	 * @param $sparql string with the complete SPARQL query (SELECT or ASK)
401
	 *
402
	 * @return RepositoryResult
403
	 */
404 9
	public function doQuery( $sparql ) {
405
406 9
		if ( $this->repositoryClient->getQueryEndpoint() === '' ) {
407 4
			throw new BadHttpDatabaseResponseException( BadHttpDatabaseResponseException::ERROR_NOSERVICE, $sparql, 'not specified' );
408
		}
409
410 5
		$this->httpRequest->setOption( CURLOPT_URL, $this->repositoryClient->getQueryEndpoint() );
411
412 5
		$this->httpRequest->setOption( CURLOPT_HTTPHEADER, array(
413 5
			'Accept: application/sparql-results+xml,application/xml;q=0.8',
414
			'Content-Type: application/x-www-form-urlencoded;charset=UTF-8'
415
		) );
416
417 5
		$this->httpRequest->setOption( CURLOPT_POST, true );
418
419 5
		$defaultGraph = $this->repositoryClient->getDefaultGraph();
420
421 5
		$parameterString = "query=" . urlencode( $sparql ) .
422 5
			( ( $defaultGraph !== '' )? '&default-graph-uri=' . urlencode( $defaultGraph ) : '' );
423
424 5
		$this->httpRequest->setOption( CURLOPT_POSTFIELDS, $parameterString );
425
426 5
		$httpResponse = $this->httpRequest->execute();
427
428 5
		if ( $this->httpRequest->getLastErrorCode() == 0 ) {
429 4
			$xmlResponseParser = new XmlResponseParser();
430 4
			return $xmlResponseParser->parse( $httpResponse );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $xmlResponseParser->parse($httpResponse); (SMW\SPARQLStore\QueryEngine\RepositoryResult) is incompatible with the return type declared by the interface SMW\SPARQLStore\RepositoryConnection::doQuery of type SMW\SPARQLStore\RepositoryResult.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
431
		}
432
433 1
		$this->mapHttpRequestError( $this->repositoryClient->getQueryEndpoint(), $sparql );
434
435 1
		$repositoryResult = new RepositoryResult();
436 1
		$repositoryResult->setErrorCode( RepositoryResult::ERROR_UNREACHABLE );
437
438 1
		return $repositoryResult;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $repositoryResult; (SMW\SPARQLStore\QueryEngine\RepositoryResult) is incompatible with the return type declared by the interface SMW\SPARQLStore\RepositoryConnection::doQuery of type SMW\SPARQLStore\RepositoryResult.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
439
	}
440
441
	/**
442
	 * Execute a SPARQL update and return a boolean to indicate if the
443
	 * operations was successful. The method throws exceptions based on
444
	 * GenericHttpDatabaseConnector::mapHttpRequestError(). If errors occur and this
445
	 * method does not throw anything, then false is returned.
446
	 *
447
	 * @note When this is written, it is not clear if the update protocol
448
	 * supports a default-graph-uri parameter. Hence the target graph for
449
	 * all updates is generally encoded in the query string and not fixed
450
	 * when sending the query. Direct callers to this function must include
451
	 * the graph information in the queries that they build.
452
	 *
453
	 * @param $sparql string with the complete SPARQL update query (INSERT or DELETE)
454
	 *
455
	 * @return boolean
456
	 */
457 6
	public function doUpdate( $sparql ) {
458
459 6
		if ( $this->repositoryClient->getUpdateEndpoint() === '' ) {
460 3
			throw new BadHttpDatabaseResponseException( BadHttpDatabaseResponseException::ERROR_NOSERVICE, $sparql, 'not specified' );
461
		}
462
463 3
		$this->httpRequest->setOption( CURLOPT_URL, $this->repositoryClient->getUpdateEndpoint() );
464 3
		$this->httpRequest->setOption( CURLOPT_POST, true );
465
466 3
		$parameterString = "update=" . urlencode( $sparql );
467
468 3
		$this->httpRequest->setOption( CURLOPT_POSTFIELDS, $parameterString );
469 3
		$this->httpRequest->setOption( CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' ) );
470
471 3
		$this->httpRequest->execute();
472
473 3
		if ( $this->httpRequest->getLastErrorCode() == 0 ) {
474 3
			return true;
475
		}
476
477
		$this->mapHttpRequestError( $this->repositoryClient->getUpdateEndpoint(), $sparql );
478
		return false;
479
	}
480
481
	/**
482
	 * Execute a HTTP-based SPARQL POST request according to
483
	 * http://www.w3.org/2009/sparql/docs/http-rdf-update/.
484
	 * The method throws exceptions based on
485
	 * GenericHttpDatabaseConnector::mapHttpRequestError(). If errors occur and this
486
	 * method does not throw anything, then an empty result with an error
487
	 * code is returned.
488
	 *
489
	 * @note This protocol is not part of the SPARQL standard and may not
490
	 * be supported by all stores. To avoid using it, simply do not provide
491
	 * a data endpoint URL when configuring the SPARQL database. If used,
492
	 * the protocol might lead to a better performance since there is less
493
	 * parsing required to fetch the data from the request.
494
	 * @note Some stores (e.g. 4Store) support another mode of posting data
495
	 * that may be implemented in a special database handler.
496
	 *
497
	 * @param $payload string Turtle serialization of data to send
498
	 *
499
	 * @return boolean
500
	 */
501 15
	public function doHttpPost( $payload ) {
502
503 15
		if ( $this->repositoryClient->getDataEndpoint() === '' ) {
504 5
			throw new BadHttpDatabaseResponseException( BadHttpDatabaseResponseException::ERROR_NOSERVICE, "SPARQL POST with data: $payload", 'not specified' );
505
		}
506
507 10
		$defaultGraph = $this->repositoryClient->getDefaultGraph();
508
509 10
		$this->httpRequest->setOption( CURLOPT_URL, $this->repositoryClient->getDataEndpoint() .
510 10
			( ( $defaultGraph !== '' )? '?graph=' . urlencode( $defaultGraph ) : '?default' ) );
511 10
		$this->httpRequest->setOption( CURLOPT_POST, true );
512
513
		// POST as file (fails in 4Store)
514 10
		$payloadFile = tmpfile();
515 10
		fwrite( $payloadFile, $payload );
516 10
		fseek( $payloadFile, 0 );
517
518 10
		$this->httpRequest->setOption( CURLOPT_INFILE, $payloadFile );
519 10
		$this->httpRequest->setOption( CURLOPT_INFILESIZE, strlen( $payload ) );
520 10
		$this->httpRequest->setOption( CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-turtle' ) );
521
522 10
		$this->httpRequest->execute();
523
524 10
		if ( $this->httpRequest->getLastErrorCode() == 0 ) {
525 5
			return true;
526
		}
527
528
		// TODO The error reporting based on SPARQL (Update) is not adequate for the HTTP POST protocol
529 5
		$this->mapHttpRequestError( $this->repositoryClient->getDataEndpoint(), $payload );
530
		return false;
531
	}
532
533
	/**
534
	 * Create the standard PREFIX declarations for SPARQL or Turtle,
535
	 * possibly with additional namespaces involved.
536
	 *
537
	 * @param $extraNamespaces array (associative) of namespaceId => namespaceUri
538
	 * @param $forSparql boolean true to use SPARQL prefix syntax, false to use Turtle prefix syntax
539
	 *
540
	 * @return string
541
	 */
542 22
	public static function getPrefixString( $extraNamespaces = array(), $forSparql = true ) {
543 22
		$prefixString = '';
544 22
		$prefixIntro = $forSparql ? 'PREFIX ' : '@prefix ';
545 22
		$prefixOutro = $forSparql ? "\n" : " .\n";
546
547 22
		foreach ( array( 'wiki', 'rdf', 'rdfs', 'owl', 'swivt', 'property', 'xsd' ) as $shortname ) {
548 22
			$prefixString .= "{$prefixIntro}{$shortname}: <" . Exporter::getInstance()->getNamespaceUri( $shortname ) . ">$prefixOutro";
549 22
			unset( $extraNamespaces[$shortname] ); // avoid double declaration
550
		}
551
552 22
		foreach ( $extraNamespaces as $shortname => $uri ) {
553 7
			$prefixString .= "{$prefixIntro}{$shortname}: <$uri>$prefixOutro";
554
		}
555
556 22
		return $prefixString;
557
	}
558
559
	/**
560
	 * @param $endpoint string URL of endpoint that was used
561
	 * @param $sparql string query that caused the problem
562
	 */
563 8
	protected function mapHttpRequestError( $endpoint, $sparql ) {
564
565 8
		if ( $this->badHttpResponseMapper === null ) {
566 8
			$this->badHttpResponseMapper = new BadHttpResponseMapper( $this->httpRequest );
567
		}
568
569 8
		$this->badHttpResponseMapper->mapResponseToHttpRequest( $endpoint, $sparql );
570 1
	}
571
572
	/**
573
	 * @since  2.0
574
	 *
575
	 * @param integer $timeout
576
	 *
577
	 * @return SparqlDatabase
578
	 */
579 57
	public function setConnectionTimeoutInSeconds( $timeout = 10 ) {
580 57
		$this->httpRequest->setOption( CURLOPT_CONNECTTIMEOUT, $timeout );
581 57
		return $this;
582
	}
583
584
}
585