Completed
Push — master ( eb3b3e...57be7d )
by mw
68:21 queued 68:12
created

MonolingualTextValue::getValueParser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 4
c 1
b 0
f 1
nc 2
nop 0
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 6
rs 9.4285
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\Localizer;
11
use SMWContainerSemanticData as ContainerSemanticData;
12
use SMWDataItem as DataItem;
13
use SMWDataValue as DataValue;
14
use SMWDIContainer as DIContainer;
15
16
/**
17
 * MonolingualTextValue requires two components, a language code and a
18
 * text.
19
 *
20
 * A text `foo@en` is expected to be invoked with a BCP47 language
21
 * code tag and a language dependent text component.
22
 *
23
 * Internally, the value is stored as container object that represents
24
 * the language code and text as separate entities in order to be queried
25
 * individually.
26
 *
27
 * External output representation depends on the context (wiki, html)
28
 * whether the language code is omitted or not.
29
 *
30
 * @license GNU GPL v2+
31
 * @since 2.4
32
 *
33
 * @author mwjames
34
 */
35
class MonolingualTextValue extends DataValue {
36
37
	/**
38
	 * @var DIProperty[]|null
39
	 */
40
	private static $properties = null;
41
42
	/**
43
	 * @var MonolingualTextValueParser
44
	 */
45
	private $monolingualTextValueParser = null;
46
47
	/**
48
	 * @var DataValueFactory
49
	 */
50
	private $dataValueFactory = null;
51
52
	/**
53
	 * @param string $typeid
54
	 */
55 16
	public function __construct( $typeid = '' ) {
56 16
		parent::__construct( '_mlt_rec' );
57 16
		$this->dataValueFactory = DataValueFactory::getInstance();
58 16
	}
59 16
60
	/**
61
	 * @see RecordValue::setFieldProperties
62
	 *
63
	 * @param SMWDIProperty[] $properties
64
	 */
65
	public function setFieldProperties( array $properties ) {
0 ignored issues
show
Unused Code introduced by
The parameter $properties 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...
66
		// Keep the interface while the properties for this type
67
		// are fixed.
68
	}
69
70
	/**
71
	 * @see DataValue::parseUserValue
72
	 * @note called by DataValue::setUserValue
73
	 *
74
	 * @param string $value
0 ignored issues
show
Bug introduced by
There is no parameter named $value. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
75
	 */
76
	protected function parseUserValue( $userValue ) {
77 14
78
		list( $text, $languageCode ) = $this->getValuesFromString( $userValue );
79 14
80
		$languageCodeValue = $this->newLanguageCodeValue( $languageCode );
81 14
82
		if (
83
			( $languageCode !== '' && $languageCodeValue->getErrors() !== array() ) ||
84 14
			( $languageCode === '' && $this->isEnabledFeature( SMW_DV_MLTV_LCODE ) ) ) {
85 14
			$this->addError( $languageCodeValue->getErrors() );
86 3
			return;
87 3
		}
88
89
		$dataValues = array();
90 11
91
		foreach ( $this->getPropertyDataItems() as $property ) {
0 ignored issues
show
Bug introduced by
The expression $this->getPropertyDataItems() of type array<integer,object<SMW\DIProperty>>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
92 11
93
			if (
94
				( $languageCode === '' && $property->getKey() === '_LCODE' ) ||
95 11
				( $text === '' && $property->getKey() === '_TEXT' ) ) {
96 11
				continue;
97 1
			}
98
99
			$value = $text;
100 11
101
			if ( $property->getKey() === '_LCODE' ) {
102 11
				$value = $languageCode;
103 10
			}
104
105
			$dataValue = $this->dataValueFactory->newDataValueByProperty(
106 11
				$property,
107
				$value,
108
				false,
109 11
				$this->m_contextPage
110 11
			);
111
112
			$dataValues[] = $dataValue;
113 11
		}
114
115
		// Generate a hash from the normalized representation so that foo@en being
116
		// the same as foo@EN independent of a user input
117
		$containerSemanticData = $this->newContainerSemanticData( $text . '@' . $languageCode );
118 11
119
		foreach ( $dataValues as $dataValue ) {
120 11
			$containerSemanticData->addDataValue( $dataValue );
121 11
		}
122
123
		$this->m_dataitem = new DIContainer( $containerSemanticData );
124 11
	}
125 11
126
	/**
127
	 * @note called by MonolingualTextValueDescriptionDeserializer::deserialize
128
	 * and MonolingualTextValue::parseUserValue
129
	 *
130
	 * No explicit check is made on the validity of a language code and is
131
	 * expected to be done before calling this method.
132
	 *
133
	 * @since 2.4
134
	 *
135
	 * @param string $userValue
136
	 *
137
	 * @return array
138
	 */
139
	public function getValuesFromString( $userValue ) {
140 14
		return $this->getValueParser()->parse( $userValue );
141 14
	}
142
143
	/**
144
	 * @see DataValue::loadDataItem
145
	 *
146
	 * @param DataItem $dataItem
147
	 *
148
	 * @return boolean
149
	 */
150
	protected function loadDataItem( DataItem $dataItem ) {
151 4
152
		if ( $dataItem->getDIType() === DataItem::TYPE_CONTAINER ) {
153 4
			$this->m_dataitem = $dataItem;
154
			return true;
155
		} elseif ( $dataItem->getDIType() === DataItem::TYPE_WIKIPAGE ) {
156 4
			$semanticData = new ContainerSemanticData( $dataItem );
0 ignored issues
show
Compatibility introduced by
$dataItem of type object<SMWDataItem> is not a sub-type of object<SMW\DIWikiPage>. It seems like you assume a child class of the class SMWDataItem 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...
157 4
			$semanticData->copyDataFrom( ApplicationFactory::getInstance()->getStore()->getSemanticData( $dataItem ) );
0 ignored issues
show
Compatibility introduced by
$dataItem of type object<SMWDataItem> is not a sub-type of object<SMW\DIWikiPage>. It seems like you assume a child class of the class SMWDataItem 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...
158 4
			$this->m_dataitem = new DIContainer( $semanticData );
159 4
			return true;
160 4
		}
161
162
		return false;
163 1
	}
164
165
	/**
166
	 * @see DataValue::getShortWikiText
167
	 */
168
	public function getShortWikiText( $linker = null ) {
169 7
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_SHORT, $linker );
170 7
	}
171
172
	/**
173
	 * @see DataValue::getShortHTMLText
174
	 */
175
	public function getShortHTMLText( $linker = null ) {
176
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_SHORT, $linker );
177
	}
178
179
	/**
180
	 * @see DataValue::getLongWikiText
181
	 */
182
	public function getLongWikiText( $linker = null ) {
183
		return $this->getDataValueFormatter()->format( DataValueFormatter::WIKI_LONG, $linker );
184
	}
185
186
	/**
187
	 * @see DataValue::getLongHTMLText
188
	 */
189
	public function getLongHTMLText( $linker = null ) {
190
		return $this->getDataValueFormatter()->format( DataValueFormatter::HTML_LONG, $linker );
191
	}
192
193
	/**
194
	 * @see DataValue::getWikiValue
195
	 */
196
	public function getWikiValue() {
197 5
		return $this->getDataValueFormatter()->format( DataValueFormatter::VALUE );
198 5
	}
199
200
	/**
201
	 * @since 2.4
202
	 * @note called by SMWResultArray::getNextDataValue
203
	 *
204
	 * @return DIProperty[]
205
	 */
206
	public static function getPropertyDataItems() {
207 12
208
		if ( self::$properties !== null && self::$properties !== array() ) {
209 12
			return self::$properties;
210 11
		}
211
212
		foreach ( array( '_TEXT', '_LCODE' ) as  $id ) {
213 1
			self::$properties[] = new DIProperty( $id );
214 1
		}
215
216
		return self::$properties;
217 1
	}
218
219
	/**
220
	 * @since 2.4
221
	 * @note called by SMWResultArray::loadContent
222
	 *
223
	 * @return DataItem[]
224
	 */
225
	public function getDataItems() {
226 2
227
		if ( !$this->isValid() ) {
228 2
			return array();
229
		}
230
231
		$result = array();
232 2
		$index = 0;
233 2
234
		foreach ( $this->getPropertyDataItems() as $diProperty ) {
0 ignored issues
show
Bug introduced by
The expression $this->getPropertyDataItems() of type array<integer,object<SMW\DIProperty>>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
235 2
			$values = $this->getDataItem()->getSemanticData()->getPropertyValues( $diProperty );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getSemanticData() does only exist in the following sub-classes of SMWDataItem: SMWDIContainer. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
236 2
			if ( count( $values ) > 0 ) {
237 2
				$result[$index] = reset( $values );
238 2
			} else {
239
				$result[$index] = null;
240
			}
241
			$index += 1;
242 2
		}
243
244
		return $result;
245 2
	}
246
247
	/**
248
	 * @since 2.4
249
	 *
250
	 * @return DataValue|null
251
	 */
252
	public function getTextValueByLanguage( $languageCode ) {
253 4
254
		if ( !$this->isValid() || $this->getDataItem() === array() ) {
255 4
			return null;
256
		}
257
258
		$semanticData = $this->getDataItem()->getSemanticData();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getSemanticData() does only exist in the following sub-classes of SMWDataItem: SMWDIContainer. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
259 4
260
		$dataItems = $semanticData->getPropertyValues( new DIProperty( '_LCODE' ) );
261 4
		$dataItem = reset( $dataItems );
262 4
263
		if ( $dataItem === false || ( $dataItem->getString() !== Localizer::asBCP47FormattedLanguageCode( $languageCode ) ) ) {
264 4
			return null;
265 1
		}
266
267
		$dataItems = $semanticData->getPropertyValues( new DIProperty( '_TEXT' ) );
268 3
		$dataItem = reset( $dataItems );
269 3
270
		if ( $dataItem === false ) {
271 3
			return null;
272
		}
273
274
		$dataValue = $this->dataValueFactory->newDataValueByItem(
275 3
			$dataItem,
276
			new DIProperty( '_TEXT' )
277 3
		);
278
279
		return $dataValue;
280 3
	}
281
282
	private function newContainerSemanticData( $value ) {
283 11
284
		if ( $this->m_contextPage === null ) {
285 11
			$containerSemanticData = ContainerSemanticData::makeAnonymousContainer();
286 4
			$containerSemanticData->skipAnonymousCheck();
287 4
		} else {
288
			$subobjectName = '_ML' . md5( $value );
289 7
290
			$subject = new DIWikiPage(
291 7
				$this->m_contextPage->getDBkey(),
292 7
				$this->m_contextPage->getNamespace(),
293 7
				$this->m_contextPage->getInterwiki(),
294 7
				$subobjectName
295
			);
296
297
			$containerSemanticData = new ContainerSemanticData( $subject );
298 7
		}
299
300
		return $containerSemanticData;
301 11
	}
302
303
	private function newLanguageCodeValue( $languageCode ) {
304 14
305
		$languageCodeValue = new LanguageCodeValue();
306 14
307
		if ( $this->m_property !== null ) {
308 14
			$languageCodeValue->setProperty( $this->m_property );
309 7
		}
310
311
		$languageCodeValue->setUserValue( $languageCode );
312 14
313
		return $languageCodeValue;
314 14
	}
315
316
	private function getValueParser() {
317
318
		if ( $this->monolingualTextValueParser === null ) {
319
			$this->monolingualTextValueParser = ValueParserFactory::getInstance()->newMonolingualTextValueParser();
0 ignored issues
show
Documentation Bug introduced by
It seems like \SMW\DataValues\ValuePar...ingualTextValueParser() of type object<SMW\DataValues\Va...lingualTextValueParser> is incompatible with the declared type object<SMW\DataValues\MonolingualTextValueParser> of property $monolingualTextValueParser.

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...
320
		}
321
322
		return $this->monolingualTextValueParser;
323
	}
324
325
}
326