Completed
Push — master ( 2472c5...a814e8 )
by mw
35:03
created

src/DataValues/ReferenceValue.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SMW\DataValues;
4
5
use SMW\ApplicationFactory;
6
use SMW\DataValueFactory;
7
use SMW\DataValues\ValueFormatters\DataValueFormatter;
8
use SMW\DIProperty;
9
use SMW\DIWikiPage;
10
use SMW\Message;
11
use SMWContainerSemanticData as ContainerSemanticData;
12
use SMWDataItem as DataItem;
13
use SMWDataValue as DataValue;
14
use SMWDIContainer as DIContainer;
15
use SMWPropertyListValue as PropertyListValue;
16
17
/**
18
 * ReferenceValue allows to define additional DV to describe the state of a
19
 * SourceValue in terms of provenance or referential evidence. ReferenceValue is
20
 * stored as separate entity to the original subject in order to encapsulate the
21
 * SourceValue from the remaining annotations kept in reference to a subject.
22
 *
23
 * Defining which fields are required can vary and therefore is left to the user
24
 * to specify such requirements using the `'Has fields' property.
25
 *
26
 * For example, declaring `[[Has fields::SomeValue;Date;SomeUrl;...]]` on a
27
 * `SomeProperty` property page is to define:
28
 *
29
 * - a property called `SomeValue` with its own specification
30
 * - a date property with the Date type
31
 * - a property called `SomeUrl` with its own specification
32
 * - ... any other property the users expects to require when making a value
33
 *   annotation of this type
34
 *
35
 * An annotation `[[SomeProperty::Foo;12-12-1212;http://example.org]]` is expected
36
 * to be a concatenated string and be separated by ';' to indicate the next value
37
 * string which will corespondent to the index of the `Has fields` declaration.
38
 *
39
 * @license GNU GPL v2+
40
 * @since 2.5
41
 *
42
 * @author mwjames
43
 */
44
class ReferenceValue extends AbstractMultiValue {
45
46
	/**
47
	 * @var DIProperty[]|null
48
	 */
49
	private $properties = null;
50
51
	/**
52
	 * @param string $typeid
53
	 */
54 8
	public function __construct( $typeid = '' ) {
55 8
		parent::__construct( '_ref_rec' );
56 8
	}
57
58
	/**
59
	 * @since 2.5
60
	 *
61
	 * {@inheritDoc}
62
	 */
63
	public function setFieldProperties( array $properties ) {
64
		foreach ( $properties as $property ) {
65
			if ( $property instanceof DIProperty ) {
66
				$this->properties[] = $property;
67
			}
68
		}
69
	}
70
71
	/**
72
	 * @since 2.5
73
	 *
74
	 * {@inheritDoc}
75
	 */
76
	public function getProperties() {
77
		return $this->properties;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->properties; of type SMW\DIProperty[]|null adds the type SMW\DIProperty[] to the return on line 77 which is incompatible with the return type declared by the abstract method SMW\DataValues\AbstractMultiValue::getProperties of type SMW\DataValues\DIProperty[]|null.
Loading history...
78
	}
79
80
	/**
81
	 * @since 2.5
82
	 *
83
	 * {@inheritDoc}
84
	 */
85 5
	public function getValuesFromString( $value ) {
86
		// #664 / T17732
87 5
		$value = str_replace( "\;", "-3B", $value );
88
89
		// Bug 21926 / T23926
90
		// Values that use html entities are encoded with a semicolon
91 5
		$value = htmlspecialchars_decode( $value, ENT_QUOTES );
92 5
		$values = preg_split( '/[\s]*;[\s]*/u', trim( $value ) );
93
94 5
		return str_replace( "-3B", ";", $values );
95
	}
96
97
	/**
98
	 * @see DataValue::getShortWikiText
99
	 * @since 2.5
100
	 *
101
	 * {@inheritDoc}
102
	 */
103 2
	public function getShortWikiText( $linker = null ) {
104 2
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_SHORT, $linker );
105
	}
106
107
	/**
108
	 * @see DataValue::getShortHTMLText
109
	 * @since 2.5
110
	 *
111
	 * {@inheritDoc}
112
	 */
113
	public function getShortHTMLText( $linker = null ) {
114
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_SHORT, $linker );
115
	}
116
117
	/**
118
	 * @see DataValue::getLongWikiText
119
	 * @since 2.5
120
	 *
121
	 * {@inheritDoc}
122
	 */
123
	public function getLongWikiText( $linker = null ) {
124
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_LONG, $linker );
125
	}
126
127
	/**
128
	 * @see DataValue::getLongHTMLText
129
	 * @since 2.5
130
	 *
131
	 * {@inheritDoc}
132
	 */
133
	public function getLongHTMLText( $linker = null ) {
134
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_LONG, $linker );
135
	}
136
137
	/**
138
	 * @see DataValue::getWikiValue
139
	 * @since 2.5
140
	 *
141
	 * {@inheritDoc}
142
	 */
143 1
	public function getWikiValue() {
144 1
		return $this->getDataValueFormatter()->format( DataValueFormatter::VALUE );
145
	}
146
147
	/**
148
	 * @since 2.5
149
	 *
150
	 * {@inheritDoc}
151
	 */
152 5
	public function getPropertyDataItems() {
153
154 5
		if ( $this->properties === null ) {
155 5
			$this->properties = $this->findPropertyDataItems( $this->getProperty() );
156
157 5
			if ( count( $this->properties ) == 0 ) {
158
				$this->addErrorMsg( array( 'smw-datavalue-reference-invalid-fields-definition' ), Message::PARSE );
159
			}
160
		}
161
162 5
		return $this->properties;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->properties; (array) is incompatible with the return type declared by the abstract method SMW\DataValues\AbstractM...e::getPropertyDataItems of type SMW\DataValues\DIProperty[]|null.

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...
163
	}
164
165
	/**
166
	 * @since 2.5
167
	 *
168
	 * {@inheritDoc}
169
	 */
170
	public function getDataItems() {
171
		return parent::getDataItems();
172
	}
173
174
	/**
175
	 * @note called by DataValue::setUserValue
176
	 * @see DataValue::parseUserValue
177
	 *
178
	 * {@inheritDoc}
179
	 */
180 5
	protected function parseUserValue( $value ) {
181
182 5
		if ( $value === '' ) {
183 1
			$this->addErrorMsg( array( 'smw_novalues' ) );
184 1
			return;
185
		}
186
187 4
		$containerSemanticData = $this->newContainerSemanticData( $value );
188 4
		$sortKeys = array();
189
190 4
		$values = $this->getValuesFromString( $value );
191 4
		$index = 0; // index in value array
192
193 4
		$propertyIndex = 0; // index in property list
194 4
		$empty = true;
195
196 4
		foreach ( $this->getPropertyDataItems() as $property ) {
197
198 4
			if ( !array_key_exists( $index, $values ) || $this->getErrors() !== array() ) {
199 1
				break; // stop if there are no values left
200
			}
201
202
			// generating the DVs:
203 4
			if ( ( $values[$index] === '' ) || ( $values[$index] == '?' ) ) { // explicit omission
204
				$index++;
205
			} else {
206 4
				$dataValue = DataValueFactory::getInstance()->newDataValueByProperty(
207
					$property,
208 4
					$values[$index],
209 4
					false,
210 4
					$this->getContextPage()
211
				);
212
213 4
				if ( $dataValue->isValid() ) { // valid DV: keep
214 4
					$containerSemanticData->addPropertyObjectValue( $property, $dataValue->getDataItem() );
215 4
					$sortKeys[] = $dataValue->getDataItem()->getSortKey();
216
217 4
					$index++;
218 4
					$empty = false;
219 1
				} elseif ( ( count( $values ) - $index ) == ( count( $this->properties ) - $propertyIndex ) ) {
220 1
					$containerSemanticData->addPropertyObjectValue( $property, $dataValue->getDataItem() );
221 1
					$this->addError( $dataValue->getErrors() );
222 1
					++$index;
223
				}
224
			}
225 4
			++$propertyIndex;
226
		}
227
228 4
		if ( $empty && $this->getErrors() === array()  ) {
229
			$this->addErrorMsg( array( 'smw_novalues' ) );
230
		}
231
232 4
		$this->m_dataitem = new DIContainer( $containerSemanticData );
233
234
		// Composite sortkey is to ensure that Store::getPropertyValues can
235
		// apply sorting during value selection
236 4
		$this->m_dataitem->addCompositeSortKey( implode( ';', $sortKeys ) );
237 4
	}
238
239
	/**
240
	 * @see DataValue::loadDataItem
241
	 */
242 1
	protected function loadDataItem( DataItem $dataItem ) {
243
244 1
		if ( $dataItem->getDIType() === DataItem::TYPE_CONTAINER ) {
245
			$this->m_dataitem = $dataItem;
246
			return true;
247 1
		} elseif ( $dataItem->getDIType() === DataItem::TYPE_WIKIPAGE ) {
248 1
			$semanticData = new ContainerSemanticData( $dataItem );
249 1
			$semanticData->copyDataFrom( ApplicationFactory::getInstance()->getStore()->getSemanticData( $dataItem ) );
250 1
			$this->m_dataitem = new DIContainer( $semanticData );
251 1
			return true;
252
		}
253
254 1
		return false;
255
	}
256
257 5
	private function findPropertyDataItems( DIProperty $property = null ) {
258
259 5
		if ( $property === null ) {
260
			return array();
261
		}
262
263 5
		$propertyDiWikiPage = $property->getDiWikiPage();
264
265 5
		if ( $propertyDiWikiPage === null ) {
266
			return array();
267
		}
268
269 5
		$listDiProperty = new DIProperty( '_LIST' );
270 5
		$dataItems = ApplicationFactory::getInstance()->getStore()->getPropertyValues( $propertyDiWikiPage, $listDiProperty );
271
272 5
		if ( count( $dataItems ) == 1 ) {
273 5
			$propertyListValue = new PropertyListValue( '__pls' );
274 5
			$propertyListValue->setDataItem( reset( $dataItems ) );
275
276 5
			if ( $propertyListValue->isValid() ) {
277 5
				return $propertyListValue->getPropertyDataItems();
278
			}
279
		}
280
281
		return array();
282
	}
283
284 4
	private function newContainerSemanticData( $value ) {
285
286 4
		if ( $this->m_contextPage === null ) {
287 2
			$containerSemanticData = ContainerSemanticData::makeAnonymousContainer();
288 2
			$containerSemanticData->skipAnonymousCheck();
289
		} else {
290 2
			$subobjectName = '_REF' . md5( $value );
291
292 2
			$subject = new DIWikiPage(
293 2
				$this->m_contextPage->getDBkey(),
294 2
				$this->m_contextPage->getNamespace(),
295 2
				$this->m_contextPage->getInterwiki(),
296
				$subobjectName
297
			);
298
299 2
			$containerSemanticData = new ContainerSemanticData( $subject );
300
		}
301
302 4
		return $containerSemanticData;
303
	}
304
305
}
306