SMGeoCoordsValue   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 94.83%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 11
dl 0
loc 256
rs 10
c 0
b 0
f 0
ccs 55
cts 58
cp 0.9483

13 Methods

Rating   Name   Duplication   Size   Complexity  
A loadDataItem() 0 13 3
A getFormattedCoord() 0 18 2
A getQueryDescription() 0 19 4
A parserDistance() 0 12 3
A parseUserValue() 0 22 3
A findValueParts() 0 8 2
A tryParseAndSetDataItem() 0 16 2
A getShortWikiText() 0 13 3
A getShortHTMLText() 0 3 1
A getLongWikiText() 0 25 3
A getLongHTMLText() 0 3 1
A getWikiValue() 0 3 1
A getServiceLinkParams() 0 9 1
1
<?php
2
3
use DataValues\Geo\Formatters\GeoCoordinateFormatter;
4
use DataValues\Geo\Parsers\GeoCoordinateParser;
5
use DataValues\Geo\Values\LatLongValue;
6
use ValueParsers\ParseException;
7
8
/**
9
 * Implementation of datavalues that are geographic coordinates.
10
 * 
11
 * @since 0.6
12
 * 
13
 * @file SM_GeoCoordsValue.php
14
 * @ingroup SemanticMaps
15
 * @ingroup SMWDataValues
16
 * 
17
 * @licence GNU GPL v2+
18
 * @author Jeroen De Dauw < [email protected] >
19
 * @author Markus Krötzsch
20
 */
21
class SMGeoCoordsValue extends SMWDataValue {
22
23
	protected $wikiValue;
24
25
	/**
26
	 * @see SMWDataValue::setDataItem
27
	 * 
28
	 * @since 1.0
29
	 * 
30
	 * @param SMWDataItem $dataItem
31
	 * 
32
	 * @return boolean
33
	 */
34 1
	protected function loadDataItem( SMWDataItem $dataItem ) {
35 1
		if ( $dataItem instanceof SMWDIGeoCoord ) {
36 1
			 $formattedValue = $this->getFormattedCoord( $dataItem );
37
38 1
			if ( $formattedValue !== null ) {
39 1
				$this->wikiValue = $formattedValue;
40 1
				$this->m_dataitem = $dataItem;
41 1
				return true;
42
			}
43
		}
44
45
		return false;
46
	}
47
48
	/**
49
	 * @since 3.0
50
	 *
51
	 * @param SMWDIGeoCoord $dataItem
52
	 * @param string|null $format
53
	 *
54
	 * @return string|null
55
	 */
56 1
	protected function getFormattedCoord( SMWDIGeoCoord $dataItem, $format = null ) {
57 1
		global $smgQPCoodFormat;
58
59 1
		$options = new \ValueFormatters\FormatterOptions( [
60 1
			GeoCoordinateFormatter::OPT_FORMAT => $format === null ? $smgQPCoodFormat : $format, // TODO
61
		] );
62
63
		// TODO: $smgQPCoodDirectional
64
65 1
		$coordinateFormatter = new GeoCoordinateFormatter( $options );
66
67 1
		$value = new LatLongValue(
68 1
			$dataItem->getLatitude(),
69 1
			$dataItem->getLongitude()
70
		);
71
72 1
		return $coordinateFormatter->format( $value );
73
	}
74
	
75
	/**
76
	 * Overwrite SMWDataValue::getQueryDescription() to be able to process
77
	 * comparators between all values.
78
	 * 
79
	 * @since 0.6
80
	 * 
81
	 * @param string $value
82
	 * 
83
	 * @return SMWDescription
84
	 * @throws InvalidArgumentException
85
	 */
86 6
	public function getQueryDescription( $value ) {
87 6
		if ( !is_string( $value ) ) {
88
			throw new InvalidArgumentException( '$value needs to be a string' );
89
		}
90
91 6
		list( $distance, $comparator ) = $this->parseUserValue( $value );
92 6
		$distance = $this->parserDistance( $distance );
93
94 6
		$this->setUserValue( $value );
95
96
		switch ( true ) {
97 6
			case !$this->isValid() :
98
				return new SMWThingDescription();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \SMWThingDescription(); (SMWThingDescription) is incompatible with the return type of the parent method SMWDataValue::getQueryDescription of type Description.

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...
99 6
			case $distance !== false :
100 3
				return new SMAreaValueDescription( $this->getDataItem(), $comparator, $distance );
101
			default :
102 3
				return new SMGeoCoordsValueDescription( $this->getDataItem(), null, $comparator );
103
		}
104
	}
105
106 6
	protected function parserDistance( $distance ) {
107 6
		if ( $distance !== false ) {
108 3
			$distance = substr( trim( $distance ), 0, -1 );
109
110 3
			if ( !MapsDistanceParser::isDistance( $distance ) ) {
111
				$this->addError( wfMessage( 'semanticmaps-unrecognizeddistance', $distance )->text() );
112
				$distance = false;
113
			}
114
		}
115
116 6
		return $distance;
117
	}
118
119
	/**
120
	 * @see SMWDataValue::parseUserValue
121
	 *
122
	 * @since 0.6
123
	 */
124 6
	protected function parseUserValue( $value ) {
125 6
		if ( !is_string( $value ) ) {
126
			throw new InvalidArgumentException( '$value needs to be a string' );
127
		}
128
129 6
		$this->wikiValue = $value;
130
131 6
		$comparator = SMW_CMP_EQ;
132 6
		$distance = false;
133
134 6
		if ( $value === '' ) {
135
			$this->addError( wfMessage( 'smw_novalues' )->text() );
136
		} else {
137 6
			SMWDataValue::prepareValue( $value, $comparator );
0 ignored issues
show
Deprecated Code introduced by
The method SMWDataValue::prepareValue() has been deprecated with message: 2.3

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
138
139 6
			list( $coordinates, $distance ) = $this->findValueParts( $value );
140
141 6
			$this->tryParseAndSetDataItem( $coordinates );
142
		}
143
144 6
		return [ $distance, $comparator ];
145
	}
146
147 6
	protected function findValueParts( $value ) {
148 6
		$parts = explode( '(', $value );
149
150 6
		$coordinates = trim( array_shift( $parts ) );
151 6
		$distance = count( $parts ) > 0 ? trim( array_shift( $parts ) ) : false;
152
153 6
		return [ $coordinates, $distance ];
154
	}
155
156
	/**
157
	 * @param string $coordinates
158
	 */
159 6
	protected function tryParseAndSetDataItem( $coordinates ) {
160 6
		$options = new \ValueParsers\ParserOptions();
161 6
		$parser = new GeoCoordinateParser( $options );
162
163
		try {
164 6
			$value = $parser->parse( $coordinates );
165 6
			$this->m_dataitem = new SMWDIGeoCoord( $value->getLatitude(), $value->getLongitude() );
166
		}
167
		catch ( ParseException $parseException ) {
168
			$this->addError( wfMessage( 'maps_unrecognized_coords', $coordinates, 1 )->text() );
169
170
			// Make sure this is always set
171
			// TODO: Why is this needed?!
172
			$this->m_dataitem = new SMWDIGeoCoord( [ 'lat' => 0, 'lon' => 0 ] );
173
		}
174 6
	}
175
176
	/**
177
	 * @see SMWDataValue::getShortWikiText
178
	 * 
179
	 * @since 0.6
180
	 */
181 1
	public function getShortWikiText( $linked = null ) {
182 1
		if ( $this->isValid() ) {
183 1
			if ( $this->m_caption === false ) {
184 1
				return $this->getFormattedCoord( $this->m_dataitem );
0 ignored issues
show
Compatibility introduced by
$this->m_dataitem of type object<SMWDataItem> is not a sub-type of object<SMWDIGeoCoord>. 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...
185
			}
186
			else {
187
				return $this->m_caption; 
188
			}
189
		}
190
		else {
191
			return $this->getErrorText();
192
		}
193
	}
194
	
195
	/**
196
	 * @see SMWDataValue::getShortHTMLText
197
	 * 
198
	 * @since 0.6
199
	 */
200
	public function getShortHTMLText( $linker = null ) {
201
		return $this->getShortWikiText( $linker );
202
	}
203
	
204
	/**
205
	 * @see SMWDataValue::getLongWikiText
206
	 * 
207
	 * @since 0.6
208
	 */
209
	public function getLongWikiText( $linked = null ) {
210
		if ( $this->isValid() ) {
211
			SMWOutputs::requireHeadItem( SMW_HEADER_TOOLTIP );
212
213
			// TODO: fix lang keys so they include the space and coordinates.
214
			$coordinateSet = $this->m_dataitem->getCoordinateSet();
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 getCoordinateSet() does only exist in the following sub-classes of SMWDataItem: SMWDIGeoCoord. 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...
215
			
216
			$text = $this->getFormattedCoord( $this->m_dataitem );
0 ignored issues
show
Compatibility introduced by
$this->m_dataitem of type object<SMWDataItem> is not a sub-type of object<SMWDIGeoCoord>. 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...
217
218
			$lines = [
219
				wfMessage( 'semanticmaps-latitude', $coordinateSet['lat'] )->inContentLanguage()->escaped(),
220
				wfMessage( 'semanticmaps-longitude', $coordinateSet['lon'] )->inContentLanguage()->escaped(),
221
			];
222
			
223
			if ( array_key_exists( 'alt', $coordinateSet ) ) {
224
				$lines[] = wfMessage( 'semanticmaps-altitude', $coordinateSet['alt'] )->inContentLanguage()->escaped();
225
			}
226
			
227
			return 	'<span class="smwttinline">' . htmlspecialchars( $text ) . '<span class="smwttcontent">' .
228
		        	 	implode( '<br />', $lines ) .
229
		        	'</span></span>';
230
		} else {
231
			return $this->getErrorText();
232
		}		
233
	}
234
235
	/**
236
	 * @see SMWDataValue::getLongHTMLText
237
	 * 
238
	 * @since 0.6
239
	 */
240
	public function getLongHTMLText( $linker = null ) {
241
		return $this->getLongWikiText( $linker );
242
	}
243
244
	/**
245
	 * @see SMWDataValue::getWikiValue
246
	 * 
247
	 * @since 0.6
248
	 */
249
	public function getWikiValue() {
250
		return $this->wikiValue;
251
	}
252
253
	/**
254
	 * Create links to mapping services based on a wiki-editable message. The parameters
255
	 * available to the message are:
256
	 * 
257
	 * $1: The location in non-directional float notation.
258
	 * $2: The location in directional DMS notation.
259
	 * $3: The latitude in non-directional float notation.
260
	 * $4 The longitude in non-directional float notation.
261
	 * 
262
	 * @since 0.6.4
263
	 * 
264
	 * @return array
265
	 */
266
	protected function getServiceLinkParams() {
267
		$coordinateSet = $this->m_dataitem->getCoordinateSet();
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 getCoordinateSet() does only exist in the following sub-classes of SMWDataItem: SMWDIGeoCoord. 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...
268
		return [
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($this->getF...$coordinateSet['lon']); (array) is incompatible with the return type of the parent method SMWDataValue::getServiceLinkParams of type boolean.

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...
269
			$this->getFormattedCoord( $this->m_dataitem, 'float' ), // TODO
0 ignored issues
show
Compatibility introduced by
$this->m_dataitem of type object<SMWDataItem> is not a sub-type of object<SMWDIGeoCoord>. 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...
270
			$this->getFormattedCoord( $this->m_dataitem, 'dms' ), // TODO
0 ignored issues
show
Compatibility introduced by
$this->m_dataitem of type object<SMWDataItem> is not a sub-type of object<SMWDIGeoCoord>. 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...
271
			$coordinateSet['lat'],
272
			$coordinateSet['lon']
273
		];
274
	}
275
276
}
277