Completed
Push — geosetting ( 8287b4 )
by Jeroen De
04:01
created

Geocoders::getDecoratedGeocoder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Maps;
4
5
use DataValues\Geo\Parsers\GeoCoordinateParser;
6
use DataValues\Geo\Values\LatLongValue;
7
use Maps\Geocoders\CachingGeocoder;
8
use Maps\Geocoders\Geocoder;
9
use MWException;
10
use ValueParsers\ParseException;
11
12
/**
13
 * Class for geocoder functionality of the Maps extension.
14
 *
15
 * FIXME: this is procedural spaghetti
16
 *
17
 * @since 0.4
18
 *
19
 * @licence GNU GPL v2+
20
 * @author Jeroen De Dauw < [email protected] >
21
 */
22
final class Geocoders {
23
24
	/**
25
	 * Associative with geoservice identifiers as keys containing the class
26
	 * name of the geocoders. This is used for registration of a geocoder
27
	 * without immediately instantiating it.
28
	 *
29
	 * @since 0.7
30
	 *
31
	 * @var array of string => string
32
	 */
33
	public static $registeredGeocoders = [];
34
35
	/**
36
	 * Can geocoding happen, ie are there any geocoders available.
37
	 *
38
	 * @since 1.0.3
39
	 * @var boolean
40
	 */
41
	protected static $canGeocode = false;
42
43
	/**
44
	 * Returns if this class can do geocoding operations.
45
	 * Ie. if there are any geocoders available.
46
	 *
47
	 * @since 0.7
48
	 *
49
	 * @return boolean
50
	 */
51
	public static function canGeocode() {
52
		self::init();
53
		return self::$canGeocode;
54
	}
55
56
	/**
57
	 * Gets a list of available geocoders.
58
	 *
59
	 * @since 1.0.3
60
	 *
61
	 * @return array
62
	 */
63
	public static function getAvailableGeocoders() {
64
		self::init();
65
		return array_keys( self::$registeredGeocoders );
66
	}
67
68
	/**
69
	 * Initiate the geocoding functionality.
70
	 *
71
	 * @since 1.0.3
72
	 *
73
	 * @return boolean Indicates if init happened
74
	 */
75
	public static function init() {
76
		static $initiated = false;
77
78
		if ( $initiated ) {
79
			return false;
80
		}
81
82
		$initiated = true;
83
84
		// Register the geocoders.
85
		\Hooks::run( 'GeocoderFirstCallInit' );
86
87
		// Determine if there are any geocoders.
88
		self::$canGeocode = count( self::$registeredGeocoders ) > 0;
89
90
		return true;
91
	}
92
93
	/**
94
	 * This function first determines whether the provided string is a pair or coordinates
95
	 * or an address. If it's the later, an attempt to geocode will be made. The function will
96
	 * return the coordinates or false, in case a geocoding attempt was made but failed.
97
	 *
98
	 * @since 0.7
99
	 *
100
	 * @param string $coordsOrAddress
101
	 * @param string $geoService
102
	 * @param boolean $checkForCoords
103
	 *
104
	 * @return LatLongValue|false
105
	 */
106
	public static function attemptToGeocode( $coordsOrAddress, $geoService = '', $checkForCoords = true ) {
107
		if ( $checkForCoords ) {
108
			$coordinateParser = new GeoCoordinateParser();
0 ignored issues
show
Deprecated Code introduced by
The class DataValues\Geo\Parsers\GeoCoordinateParser has been deprecated with message: since 2.0, use the base class instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
109
110
			try {
111
				return $coordinateParser->parse( $coordsOrAddress );
112
			}
113
			catch ( ParseException $parseException ) {
114
				return self::geocode( $coordsOrAddress, $geoService );
115
			}
116
		} else {
117
			return self::geocode( $coordsOrAddress, $geoService );
118
		}
119
	}
120
121
	/**
122
	 * Geocodes an address with the provided geocoding service and returns the result
123
	 * as an array, or false when the geocoding failed.
124
	 *
125
	 * @since 0.7
126
	 *
127
	 * @param string $address
128
	 * @param string $geoService
129
	 *
130
	 * @return LatLongValue|false
131
	 * @throws MWException
132
	 */
133
	public static function geocode( $address, $geoService = '' ) {
134
		if ( !is_string( $address ) ) {
135
			throw new MWException( 'Parameter $address must be a string at ' . __METHOD__ );
136
		}
137
138
		if ( !self::canGeocode() ) {
139
			return false;
140
		}
141
142
		$geocoder = self::getValidGeocoderInstance( $geoService );
143
144
		// This means there was no suitable geocoder found, so return false.
145
		if ( $geocoder === false ) {
146
			return false;
147
		}
148
149
		$coordinates = self::getDecoratedGeocoder( $geocoder )->geocode( $address );
0 ignored issues
show
Bug introduced by
It seems like $geocoder defined by self::getValidGeocoderInstance($geoService) on line 142 can also be of type boolean; however, Maps\Geocoders::getDecoratedGeocoder() does only seem to accept object<Maps\Geocoders\Geocoder>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
150
		
151
		if( $coordinates == null ) {
152
			return false;
153
		}
154
155
		return $coordinates;
156
	}
157
158
	private static function getDecoratedGeocoder( Geocoder $geocoder ) {
0 ignored issues
show
Coding Style introduced by
getDecoratedGeocoder uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
159
		if ( $GLOBALS['egMapsEnableGeoCache'] ) {
160
			return new CachingGeocoder(
161
				$geocoder,
162
				wfGetCache( CACHE_ANYTHING )
163
			);
164
		}
165
166
		return $geocoder;
167
	}
168
169
	/**
170
	 * Registers a geocoder linked to an identifier.
171
	 *
172
	 * @since 0.7
173
	 *
174
	 * @param string $geocoderIdentifier
175
	 * @param string|\Maps\Geocoders\Geocoder $geocoder
176
	 */
177
	public static function registerGeocoder( $geocoderIdentifier, $geocoder ) {
178
		self::$registeredGeocoders[$geocoderIdentifier] = $geocoder;
179
	}
180
181
	/**
182
	 * Returns the instance of the geocoder linked to the provided identifier
183
	 * or the default one when it's not valid. False is returned when there
184
	 * are no geocoders available.
185
	 *
186
	 * @since 0.7
187
	 *
188
	 * @param string $geocoderIdentifier
189
	 *
190
	 * @return Geocoder|bool
191
	 */
192
	private static function getValidGeocoderInstance( $geocoderIdentifier ) {
193
		return self::getGeocoderInstance( self::getValidGeocoderIdentifier( $geocoderIdentifier ) );
194
	}
195
196
	/**
197
	 * Returns the instance of a geocoder. This function assumes there is a
198
	 * geocoder linked to the identifier you provide - if you are not sure
199
	 * it does, use getValidGeocoderInstance instead.
200
	 *
201
	 * @since 0.7
202
	 *
203
	 * @param string $geocoderIdentifier
204
	 *
205
	 * @return Geocoder|bool
206
	 */
207
	private static function getGeocoderInstance( $geocoderIdentifier ) {
208
		if ( !array_key_exists( $geocoderIdentifier, self::$registeredGeocoders ) ) {
209
			return false;
210
		}
211
212
		return self::$registeredGeocoders[$geocoderIdentifier];
213
	}
214
215
	/**
216
	 * Returns a valid geocoder idenifier. If the given one is a valid main identifier,
217
	 * it will simply be returned. If it's an alias, it will be turned into the correponding
218
	 * main identifier. If it's not recognized at all (or empty), the default will be used.
219
	 * Only call this function when there are geocoders available, else an erro will be thrown.
220
	 *
221
	 * @since 0.7
222
	 *
223
	 * @param string $geocoderIdentifier
224
	 *
225
	 * @return string|bool
226
	 * @throws MWException
227
	 */
228
	private static function getValidGeocoderIdentifier( $geocoderIdentifier ) {
229
		global $egMapsDefaultGeoService;
230
		static $validatedDefault = false;
231
232
		if ( $geocoderIdentifier === '' || !array_key_exists( $geocoderIdentifier, self::$registeredGeocoders ) ) {
233
			if ( !$validatedDefault ) {
234
				if ( !array_key_exists( $egMapsDefaultGeoService, self::$registeredGeocoders ) ) {
235
					$services = array_keys( self::$registeredGeocoders );
236
					$egMapsDefaultGeoService = array_shift( $services );
237
					if ( is_null( $egMapsDefaultGeoService ) ) {
238
						throw new MWException( 'Tried to geocode while there are no geocoders available at ' . __METHOD__  );
239
					}
240
				}
241
			}
242
243
			if ( !array_key_exists( $egMapsDefaultGeoService, self::$registeredGeocoders ) ) {
244
				return false;
245
			}
246
247
			$geocoderIdentifier = $egMapsDefaultGeoService;
248
		}
249
250
		return $geocoderIdentifier;
251
	}
252
253
}
254