Completed
Push — master ( 89e759...3a13dd )
by mw
99:35 queued 64:30
created

TurtleTriplesBuilder::doSerialize()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.243

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 21
ccs 7
cts 10
cp 0.7
crap 3.243
rs 9.3142
c 0
b 0
f 0
1
<?php
2
3
namespace SMW\SPARQLStore;
4
5
use SMW\DIWikiPage;
6
use SMW\ApplicationFactory;
7
use SMW\Exporter\Element;
8
use SMW\Exporter\Element\ExpElement;
9
use SMW\Exporter\Element\ExpNsResource;
10
use SMW\Exporter\Element\ExpResource;
11
use SMW\SemanticData;
12
use Onoi\Cache\Cache;
13
use SMWExpData as ExpData;
14
use SMWExporter as Exporter;
15
use SMWTurtleSerializer as TurtleSerializer;
16
17
/**
18
 * @license GNU GPL v2+
19
 * @since 2.0
20
 *
21
 * @author Markus Krötzsch
22
 * @author mwjames
23
 */
24
class TurtleTriplesBuilder {
25
26
	/**
27
	 * ID used for the InMemoryPoolCache
28
	 */
29
	const POOLCACHE_ID = 'sparql.turtle.triplesbuilder';
30
31
	/**
32
	 * @var SemanticData
33
	 */
34
	private $semanticData = null;
0 ignored issues
show
Unused Code introduced by
The property $semanticData is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
35
36
	/**
37
	 * @var RepositoryRedirectLookup
38
	 */
39
	private $repositoryRedirectLookup = null;
40
41
	/**
42
	 * @var null|string
43
	 */
44
	private $triples = null;
45
46
	/**
47
	 * @var array
48
	 */
49
	private $prefixes = array();
50
51
	/**
52
	 * @var boolean
53
	 */
54
	private $hasTriplesForUpdate = false;
55
56
	/**
57
	 * @var integer
58
	 */
59
	private $triplesChunkSize = 80;
60
61
	/**
62
	 * @var Cache
63
	 */
64
	private $dataItemExportInMemoryCache;
65
66
	/**
67
	 * @since 2.0
68
	 *
69
	 * @param RepositoryRedirectLookup $repositoryRedirectLookup
70
	 * @param Cache|null $cache
71 3
	 */
72 3
	public function __construct( RepositoryRedirectLookup $repositoryRedirectLookup, Cache $cache = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $cache 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...
73 3
		$this->repositoryRedirectLookup = $repositoryRedirectLookup;
74 3
		$this->dataItemExportInMemoryCache = ApplicationFactory::getInstance()->getInMemoryPoolCache()->getPoolCacheById( self::POOLCACHE_ID );
0 ignored issues
show
Documentation Bug introduced by
It seems like \SMW\ApplicationFactory:...yId(self::POOLCACHE_ID) of type object<SMW\Cache> is incompatible with the declared type object<Onoi\Cache\Cache> of property $dataItemExportInMemoryCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
75 3
	}
76
77
	/**
78
	 * @since 2.3
79
	 *
80
	 * @param integer $chunkSize
0 ignored issues
show
Documentation introduced by
There is no parameter named $chunkSize. Did you maybe mean $triplesChunkSize?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
81
	 */
82 1
	public function setTriplesChunkSize( $triplesChunkSize ) {
83 1
		$this->triplesChunkSize = (int)$triplesChunkSize;
84 1
	}
85
86
	/**
87
	 * @since 2.0
88
	 *
89
	 * @param SemanticData $semanticData
90
	 */
91 2
	public function doBuildTriplesFrom( SemanticData $semanticData ) {
92 2
93
		$this->hasTriplesForUpdate = false;
94
		$this->triples  = '';
95
		$this->prefixes = array();
96
97
		$this->doSerialize( $semanticData );
98
	}
99
100 1
	/**
101
	 * @since 2.0
102 1
	 *
103
	 * @return boolean
104
	 */
105
	public function hasTriples() {
106 1
		return $this->hasTriplesForUpdate;
107
	}
108
109
	/**
110
	 * @since 2.3
111
	 *
112
	 * @return string
113
	 */
114
	public function getTriples() {
115
		return $this->triples === null ? '' : $this->triples;
116
	}
117
118 1
	/**
119
	 * Split the triples into group of chunks as it can happen that some subjects
120 1
	 * contain SPARQL strings that exceed 1800 lines which may reach the capacity
121
	 * limit of a RespositoryConnector (#1110).
122 1
	 *
123 1
	 * @since 2.3
124
	 *
125
	 * @return array
126 1
	 */
127
	public function getChunkedTriples() {
128
129
		$chunkedTriples = array();
130 1
131 1
		if ( $this->triples === null ) {
132
			return $chunkedTriples;
133
		}
134 1
135 1
		if ( strpos( $this->triples, " ." ) === false ) {
136
			return $chunkedTriples;
137
		}
138 1
139
		$triplesArrayChunks = array_chunk(
140
			explode( " .", $this->triples ), $this->triplesChunkSize
141
		);
142
143
		foreach( $triplesArrayChunks as $triplesChunk ) {
144
			$chunkedTriples[] = implode( " .", $triplesChunk ) . "\n";
145
		}
146 1
147
		return $chunkedTriples;
148 1
	}
149
150
	/**
151
	 * @since 2.0
152 1
	 *
153
	 * @return array
154
	 */
155
	public function getPrefixes() {
156
		return $this->prefixes;
157
	}
158
159
	/**
160 1
	 * @since 2.0
161
	 */
162 1
	public static function reset() {
163
		TurtleSerializer::reset();
164
	}
165
166 1
	private function doSerialize( SemanticData $semanticData ) {
167
168
		$expDataArray = $this->prepareUpdateExpData( $semanticData );
169
170
		if ( count( $expDataArray ) > 0 ) {
171
172
			$this->hasTriplesForUpdate = true;
173
174
			$turtleSerializer = new TurtleSerializer( true );
175
			$turtleSerializer->startSerialization();
176
177
			foreach ( $expDataArray as $expData ) {
178 2
				$turtleSerializer->serializeExpData( $expData );
179
			}
180 2
181 2
			$turtleSerializer->finishSerialization();
182 2
183
			$this->triples = $turtleSerializer->flushContent();
184 2
			$this->prefixes = $turtleSerializer->flushSparqlPrefixes();
185
		}
186 2
	}
187
188 2
	/**
189
	 * Prepare an array of SMWExpData elements that should be written to
190 2
	 * the SPARQL store. The result is empty if no updates should be done.
191 2
	 * Note that this is different from writing an SMWExpData element that
192
	 * has no content.
193 2
	 * Otherwise, the first SMWExpData object in the array is a translation
194 2
	 * of the given input data, but with redirects resolved. Further
195
	 * SMWExpData objects might be included in the resulting list to
196
	 * capture necessary stub declarations for objects that do not have
197 2
	 * any data in the RDF store yet.
198
	 *
199 2
	 * @since 1.6
200 2
	 *
201
	 * @param SemanticData $semanticData
202
	 *
203 2
	 * @return array of SMWExpData
204
	 */
205
	private function prepareUpdateExpData( SemanticData $semanticData ) {
206
207
		$result = array();
208
209
		$expData = Exporter::getInstance()->makeExportData( $semanticData );
0 ignored issues
show
Compatibility introduced by
$semanticData of type object<SMW\SemanticData> is not a sub-type of object<SMWSemanticData>. It seems like you assume a child class of the class SMW\SemanticData to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
210
		$newExpData = $this->expandUpdateExpData( $expData, $result, false );
211
		array_unshift( $result, $newExpData );
212
213
		return $result;
214
	}
215
216
	/**
217
	 * Find a normalized representation of the given SMWExpElement that can
218
	 * be used in an update of the stored data. Normalization uses
219
	 * redirects. The type of the ExpElement might change, especially into
220
	 * SMWExpData in order to store auxiliary properties.
221
	 * Moreover, the method records any auxiliary data that should be
222
	 * written to the store when including this SMWExpElement into updates.
223 2
	 * This auxiliary data is collected in a call-by-ref array.
224
	 *
225 2
	 * @since 1.6
226
	 *
227 2
	 * @param Element $expElement object containing the update data
228 2
	 * @param $auxiliaryExpData array of SMWExpData
229 2
	 *
230
	 * @return ExpElement
231 2
	 */
232
	private function expandUpdateExpElement( Element $expElement, array &$auxiliaryExpData ) {
233
234
		if ( $expElement instanceof ExpResource ) {
235
			return $this->expandUpdateExpResource( $expElement, $auxiliaryExpData );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->expandUpdateExpRe...nt, $auxiliaryExpData); of type SMWExpNsResource|SMW\Exporter\Element\ExpResource adds the type SMWExpNsResource to the return on line 235 which is incompatible with the return type documented by SMW\SPARQLStore\TurtleTr...:expandUpdateExpElement of type SMW\Exporter\Element\ExpElement.
Loading history...
236
		}
237
238
		if ( $expElement instanceof ExpData ) {
239
			return $this->expandUpdateExpData( $expElement, $auxiliaryExpData, true );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->expandUpda...uxiliaryExpData, true); (SMWExpData) is incompatible with the return type documented by SMW\SPARQLStore\TurtleTr...:expandUpdateExpElement of type SMW\Exporter\Element\ExpElement.

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...
240
		}
241
242
		return $expElement;
243
	}
244
245
	/**
246
	 * Find a normalized representation of the given SMWExpResource that can
247
	 * be used in an update of the stored data. Normalization uses
248
	 * redirects. The type of the ExpElement might change, especially into
249
	 * SMWExpData in order to store auxiliary properties.
250 2
	 * Moreover, the method records any auxiliary data that should be
251
	 * written to the store when including this SMWExpElement into updates.
252 2
	 * This auxiliary data is collected in a call-by-ref array.
253 2
	 *
254
	 * @since 1.6
255
	 *
256 2
	 * @param ExpResource $expResource object containing the update data
257
	 * @param $auxiliaryExpData array of SMWExpData
258
	 *
259
	 * @return ExpElement
260 2
	 */
261
	private function expandUpdateExpResource( ExpResource $expResource, array &$auxiliaryExpData ) {
262
263
		$exists = true;
264
265
		if ( $expResource instanceof ExpNsResource ) {
266
			$elementTarget = $this->repositoryRedirectLookup->findRedirectTargetResource( $expResource, $exists );
0 ignored issues
show
Documentation introduced by
$expResource is of type object<SMW\Exporter\Element\ExpNsResource>, but the function expects a object<SMWExpNsResource>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
267
		} else {
268
			$elementTarget = $expResource;
269
		}
270
271
		if ( !$exists && $elementTarget->getDataItem() instanceof DIWikiPage && $elementTarget->getDataItem()->getDBKey() !== '' ) {
272
273
			$diWikiPage = $elementTarget->getDataItem();
274
			$hash = $diWikiPage->getHash();
275
276
			if ( !$this->dataItemExportInMemoryCache->contains( $hash ) ) {
277
				$this->dataItemExportInMemoryCache->save( $hash, Exporter::getInstance()->makeExportDataForSubject( $diWikiPage, true ) );
278
			}
279 2
280
			$auxiliaryExpData[$hash] = $this->dataItemExportInMemoryCache->fetch( $hash );
281 2
		}
282
283 2
		return $elementTarget;
284 2
	}
285
286 2
	/**
287
	 * Find a normalized representation of the given SMWExpData that can
288
	 * be used in an update of the stored data. Normalization uses
289 2
	 * redirects.
290
	 * Moreover, the method records any auxiliary data that should be
291
	 * written to the store when including this SMWExpElement into updates.
292
	 * This auxiliary data is collected in a call-by-ref array.
293
	 *
294
	 * @since 1.6
295
	 * @param ExpData $expData object containing the update data
296
	 * @param $auxiliaryExpData array of SMWExpData
297
	 * @param $expandSubject boolean controls if redirects/auxiliary data should also be sought for subject
298
	 *
299
	 * @return ExpData
300
	 */
301 2
	private function expandUpdateExpData( ExpData $expData, array &$auxiliaryExpData, $expandSubject ) {
302
303
		$subjectExpResource = $expData->getSubject();
304
305
		if ( $expandSubject ) {
306
307
			$expandedExpElement = $this->expandUpdateExpElement( $subjectExpResource, $auxiliaryExpData );
308
309
			if ( $expandedExpElement instanceof ExpData ) {
310
				$newExpData = $expandedExpElement;
311
			} else { // instanceof SMWExpResource
312
				$newExpData = new ExpData( $subjectExpResource );
313
			}
314
		} else {
315
			$newExpData = new ExpData( $subjectExpResource );
316
		}
317
318
		foreach ( $expData->getProperties() as $propertyResource ) {
319 2
320
			$propertyTarget = $this->expandUpdateExpElement( $propertyResource, $auxiliaryExpData );
321 2
322
			foreach ( $expData->getValues( $propertyResource ) as $element ) {
323 2
				$newExpData->addPropertyObjectValue(
324
					$propertyTarget,
0 ignored issues
show
Documentation introduced by
$propertyTarget is of type object<SMW\Exporter\Element\ExpElement>, but the function expects a object<SMWExpNsResource>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
325
					$this->expandUpdateExpElement( $element, $auxiliaryExpData )
326
				);
327
			}
328
		}
329
330
		return $newExpData;
331
	}
332
333
}
334