Completed
Push — master ( 43acf6...5d1976 )
by mw
12s
created

UniquenessConstraintValueValidator::canValidate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 3
eloc 2
nc 3
nop 1
crap 3
1
<?php
2
3
namespace SMW\DataValues\ValueValidators;
4
5
use SMW\ApplicationFactory;
6
use SMW\CachedPropertyValuesPrefetcher;
7
use SMW\DIWikiPage;
8
use SMWDataValue as DataValue;
9
10
/**
11
 * @private
12
 *
13
 * Only allow values that are unique where uniqueness is establised for the first (
14
 * in terms of time which also entails that after a full rebuild the first value
15
 * found is being categorised as established value) value assigned to a property
16
 * (that requires this trait) and any value that compares to an establised
17
 * value with the same literal representation is being identified as violating the
18
 * uniqueness constraint.
19
 *
20
 * @note This class is optimized for performance which means that each match will be
21
 * cached to avoid making unnecessary query requests to the QueryEngine.
22
 *
23
 * A linked list will ensure that a subject which is associated with a unique value
24
 * is being purged in case it is altered or its uniqueness constraint is no longer
25
 * valid.
26
 *
27
 * @license GNU GPL v2+
28
 * @since 2.4
29
 *
30
 * @author mwjames
31
 */
32
class UniquenessConstraintValueValidator implements ConstraintValueValidator {
33
34
	/**
35
	 * @var CachedPropertyValuesPrefetcher
36
	 */
37
	private $cachedPropertyValuesPrefetcher;
38
39
	/**
40
	 * @var QueryFactory
41
	 */
42
	private $queryFactory;
43
44
	/**
45
	 * @var boolean
46
	 */
47
	private $hasConstraintViolation = false;
48
49
	/**
50
	 * @since 2.4
51
	 *
52
	 * @param CachedPropertyValuesPrefetcher $cachedPropertyValuesPrefetcher
53
	 */
54 163
	public function __construct( CachedPropertyValuesPrefetcher $cachedPropertyValuesPrefetcher = null ) {
55 163
		$this->cachedPropertyValuesPrefetcher = $cachedPropertyValuesPrefetcher;
56
57 163
		if ( $this->cachedPropertyValuesPrefetcher === null ) {
58 162
			$this->cachedPropertyValuesPrefetcher = ApplicationFactory::getInstance()->getCachedPropertyValuesPrefetcher();
59
		}
60
61 163
		$this->queryFactory = ApplicationFactory::getInstance()->newQueryFactory();
0 ignored issues
show
Documentation Bug introduced by
It seems like \SMW\ApplicationFactory:...ce()->newQueryFactory() of type object<SMW\QueryFactory> is incompatible with the declared type object<SMW\DataValues\Va...alidators\QueryFactory> of property $queryFactory.

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...
62 163
	}
63
64
	/**
65
	 * @since 2.4
66
	 *
67
	 * {@inheritDoc}
68
	 */
69 170
	public function hasConstraintViolation() {
70 170
		return $this->hasConstraintViolation;
71
	}
72
73
	/**
74
	 * @since 2.4
75
	 *
76
	 * {@inheritDoc}
77
	 */
78 171
	public function validate( $dataValue ) {
79
80 171
		$this->hasConstraintViolation = false;
81
82 171
		if ( !$this->canValidate( $dataValue ) ) {
83 170
			return $this->hasConstraintViolation;
84
		}
85
86 3
		$property = $dataValue->getProperty();
87
88 3
		if ( !$dataValue->getPropertySpecificationLookup()->hasUniquenessConstraintFor( $property ) ) {
89 2
			return $this->hasConstraintViolation;
90
		}
91
92 3
		$blobStore = $this->cachedPropertyValuesPrefetcher->getBlobStore();
93 3
		$dataItem = $dataValue->getDataItem();
94
95 3
		$hash = $this->cachedPropertyValuesPrefetcher->getHashFor(
96 3
			$property->getKey() . ':' . $dataItem->getHash()
97
		);
98
99 3
		$container = $blobStore->read(
100
			$hash
101
		);
102
103 3
		$key = 'PVUC';
104
105 3
		if ( !$container->has( $key ) ) {
106 3
			$page = $this->tryFindMatchResultFor(
107
				$hash,
108
				$dataValue
109
			);
110
111 3
			$container->set( $key, $page );
112
113 3
			$blobStore->save(
114
				$container
115
			);
116
		}
117
118 3
		$wikiPage = $container->get( $key );
119
120
		// Verify that the contextPage (where the annotation has its origin) is
121
		// matchable to the request and in case it is not a match inform the user
122
		// about the origin
123 3
		if ( $wikiPage instanceof DIWikiPage && !$dataValue->getContextPage()->equals( $wikiPage ) ) {
124 2
			$dataValue->addErrorMsg(
125
				array(
126 2
					'smw-datavalue-uniqueness-constraint-error',
127 2
					$property->getLabel(),
128 2
					$dataValue->getWikiValue(),
129 2
					$wikiPage->getTitle()->getPrefixedText()
130
				)
131
			);
132
133 2
			$this->hasConstraintViolation = true;
134
		}
135 3
	}
136
137 3
	private function tryFindMatchResultFor( $hash, $dataValue ) {
138
139 3
		$descriptionFactory = $this->queryFactory->newDescriptionFactory();
140 3
		$contextPage = $dataValue->getContextPage();
141
142
		// Exclude the current page from the result match to check whether another
143
		// page matches the condition and if so then the value can no longer be
144
		// assigned and is not unique
145 3
		$description = $descriptionFactory->newConjunction( array(
146 3
			$descriptionFactory->newFromDataValue( $dataValue ),
147 3
			$descriptionFactory->newValueDescription( $contextPage, null, SMW_CMP_NEQ ) // NEQ
148
		) );
149
150 3
		$query = $this->queryFactory->newQuery( $description );
151 3
		$query->setLimit( 1 );
152
153 3
		$dataItems = $this->cachedPropertyValuesPrefetcher->queryPropertyValuesFor(
154
			$query
155
		);
156
157 3
		if ( !is_array( $dataItems ) || $dataItems === array() ) {
158
			// No other assignments were found therefore it is assumed that at
159
			// the time of the query request, the "contextPage" holds a unique
160
			// value for the property
161 2
			$page = $contextPage;
162
163
164
		} else {
165 1
			$page = end( $dataItems );
166
		}
167
168
		// Create a linked list so that when the subject is altered or deleted
169
		// the related uniqueness container can be removed as well
170 3
		$blobStore = $this->cachedPropertyValuesPrefetcher->getBlobStore();
171
172 3
		$container = $blobStore->read(
173 3
			$this->cachedPropertyValuesPrefetcher->getRootHashFor( $page )
174
		);
175
176 3
		$container->addToLinkedList( $hash );
177
178 3
		$blobStore->save(
179
			$container
180
		);
181
182 3
		return $page;
183
	}
184
185 171
	private function canValidate( $dataValue ) {
186 171
		return $dataValue instanceof DataValue && $dataValue->getContextPage() !== null && $dataValue->isEnabledFeature( SMW_DV_PVUC );
187
	}
188
189
}
190