Completed
Push — geostuff ( 279618 )
by Jeroen De
04:40
created

Geocoders::getGeocoderInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

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 7
ccs 0
cts 4
cp 0
rs 9.4285
cc 2
eloc 4
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\Geocoder;
8
use MWException;
9
use ValueParsers\ParseException;
10
11
/**
12
 * Class for geocoder functionality of the Maps extension.
13
 *
14
 * FIXME: this is procedural spaghetti
15
 *
16
 * @since 0.4
17
 *
18
 * @licence GNU GPL v2+
19
 * @author Jeroen De Dauw < [email protected] >
20
 */
21
final class Geocoders {
22
23
	/**
24
	 * Associative with geoservice identifiers as keys containing the class
25
	 * name of the geocoders. This is used for registration of a geocoder
26
	 * without immediately instantiating it.
27
	 *
28
	 * @since 0.7
29
	 *
30
	 * @var array of string => string
31
	 */
32
	public static $registeredGeocoders = [];
33
34
	/**
35
	 * The global geocoder cache, holding geocoded data when enabled.
36
	 *
37
	 * @since 0.7
38
	 *
39
	 * @var array
40
	 */
41
	private static $globalGeocoderCache = [];
42
43
	/**
44
	 * Can geocoding happen, ie are there any geocoders available.
45
	 *
46
	 * @since 1.0.3
47
	 * @var boolean
48
	 */
49
	protected static $canGeocode = false;
50
51
	/**
52
	 * Returns if this class can do geocoding operations.
53
	 * Ie. if there are any geocoders available.
54
	 *
55
	 * @since 0.7
56
	 *
57
	 * @return boolean
58
	 */
59
	public static function canGeocode() {
60
		self::init();
61
		return self::$canGeocode;
62
	}
63
64
	/**
65
	 * Gets a list of available geocoders.
66
	 *
67
	 * @since 1.0.3
68
	 *
69
	 * @return array
70
	 */
71
	public static function getAvailableGeocoders() {
72
		self::init();
73
		return array_keys( self::$registeredGeocoders );
74
	}
75
76
	/**
77
	 * Initiate the geocoding functionality.
78
	 *
79
	 * @since 1.0.3
80
	 *
81
	 * @return boolean Indicates if init happened
82
	 */
83
	public static function init() {
84
		static $initiated = false;
85
86
		if ( $initiated ) {
87
			return false;
88
		}
89
90
		$initiated = true;
91
92
		// Register the geocoders.
93
		\Hooks::run( 'GeocoderFirstCallInit' );
94
95
		// Determine if there are any geocoders.
96
		self::$canGeocode = count( self::$registeredGeocoders ) > 0;
97
98
		return true;
99
	}
100
101
	/**
102
	 * This function first determines whether the provided string is a pair or coordinates
103
	 * or an address. If it's the later, an attempt to geocode will be made. The function will
104
	 * return the coordinates or false, in case a geocoding attempt was made but failed.
105
	 *
106
	 * @since 0.7
107
	 *
108
	 * @param string $coordsOrAddress
109
	 * @param string $geoService
110
	 * @param boolean $checkForCoords
111
	 *
112
	 * @return LatLongValue|false
113
	 */
114
	public static function attemptToGeocode( $coordsOrAddress, $geoService = '', $checkForCoords = true ) {
115
		if ( $checkForCoords ) {
116
			$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...
117
118
			try {
119
				return $coordinateParser->parse( $coordsOrAddress );
120
			}
121
			catch ( ParseException $parseException ) {
122
				return self::geocode( $coordsOrAddress, $geoService );
123
			}
124
		} else {
125
			return self::geocode( $coordsOrAddress, $geoService );
126
		}
127
	}
128
129
	/**
130
	 * Geocodes an address with the provided geocoding service and returns the result
131
	 * as an array, or false when the geocoding failed.
132
	 *
133
	 * @since 0.7
134
	 *
135
	 * @param string $address
136
	 * @param string $geoService
137
	 *
138
	 * @return LatLongValue|false
139
	 * @throws MWException
140
	 */
141
	public static function geocode( $address, $geoService = '' ) {
142
		if ( !is_string( $address ) ) {
143
			throw new MWException( 'Parameter $address must be a string at ' . __METHOD__ );
144
		}
145
146
		if ( !self::canGeocode() ) {
147
			return false;
148
		}
149
150
		$geocoder = self::getValidGeocoderInstance( $geoService );
151
152
		// This means there was no suitable geocoder found, so return false.
153
		if ( $geocoder === false ) {
154
			return false;
155
		}
156
157
		$cacheResult = self::cacheRead( $address );
158
159
		// This means the cache returned an already computed set of coordinates.
160
		if ( $cacheResult !== false ) {
161
			assert( $cacheResult instanceof LatLongValue );
162
			return $cacheResult;
163
		}
164
165
		$coordinates = $geocoder->geocode( $address );
166
167
		if ( $coordinates === null ) {
168
			return false;
169
		}
170
171
		self::cacheWrite( $address, $coordinates );
172
173
		return $coordinates;
174
	}
175
176
	/**
177
	 * Returns already coordinates already known from previous geocoding operations,
178
	 * or false if there is no match found in the cache.
179
	 *
180
	 * @since 0.7
181
	 *
182
	 * @param string $address
183
	 *
184
	 * @return LatLongValue|boolean false
185
	 */
186
	private static function cacheRead( $address ) {
187
		global $egMapsEnableGeoCache;
188
189
		if ( $egMapsEnableGeoCache && array_key_exists( $address, self::$globalGeocoderCache ) ) {
190
			return self::$globalGeocoderCache[$address];
191
		}
192
193
		return false;
194
	}
195
196
	/**
197
	 * Writes the geocoded result to the cache if the cache is on.
198
	 *
199
	 * @since 0.7
200
	 *
201
	 * @param string $address
202
	 * @param LatLongValue $coordinates
203
	 */
204
	private static function cacheWrite( $address, LatLongValue $coordinates ) {
205
		global $egMapsEnableGeoCache;
206
207
		// Add the obtained coordinates to the cache when there is a result and the cache is enabled.
208
		if ( $egMapsEnableGeoCache && $coordinates ) {
209
			self::$globalGeocoderCache[$address] = $coordinates;
210
		}
211
	}
212
213
	/**
214
	 * Registers a geocoder linked to an identifier.
215
	 *
216
	 * @since 0.7
217
	 *
218
	 * @param string $geocoderIdentifier
219
	 * @param string|\Maps\Geocoders\Geocoder $geocoder
220
	 */
221
	public static function registerGeocoder( $geocoderIdentifier, $geocoder ) {
222
		self::$registeredGeocoders[$geocoderIdentifier] = $geocoder;
223
	}
224
225
	/**
226
	 * Returns the instance of the geocoder linked to the provided identifier
227
	 * or the default one when it's not valid. False is returned when there
228
	 * are no geocoders available.
229
	 *
230
	 * @since 0.7
231
	 *
232
	 * @param string $geocoderIdentifier
233
	 *
234
	 * @return Geocoder|bool
235
	 */
236
	private static function getValidGeocoderInstance( $geocoderIdentifier ) {
237
		return self::getGeocoderInstance( self::getValidGeocoderIdentifier( $geocoderIdentifier ) );
238
	}
239
240
	/**
241
	 * Returns the instance of a geocoder. This function assumes there is a
242
	 * geocoder linked to the identifier you provide - if you are not sure
243
	 * it does, use getValidGeocoderInstance instead.
244
	 *
245
	 * @since 0.7
246
	 *
247
	 * @param string $geocoderIdentifier
248
	 *
249
	 * @return Geocoder|bool
250
	 */
251
	private static function getGeocoderInstance( $geocoderIdentifier ) {
252
		if ( !array_key_exists( $geocoderIdentifier, self::$registeredGeocoders ) ) {
253
			return false;
254
		}
255
256
		return self::$registeredGeocoders[$geocoderIdentifier];
257
	}
258
259
	/**
260
	 * Returns a valid geocoder idenifier. If the given one is a valid main identifier,
261
	 * it will simply be returned. If it's an alias, it will be turned into the correponding
262
	 * main identifier. If it's not recognized at all (or empty), the default will be used.
263
	 * Only call this function when there are geocoders available, else an erro will be thrown.
264
	 *
265
	 * @since 0.7
266
	 *
267
	 * @param string $geocoderIdentifier
268
	 *
269
	 * @return string|bool
270
	 * @throws MWException
271
	 */
272
	private static function getValidGeocoderIdentifier( $geocoderIdentifier ) {
273
		global $egMapsDefaultGeoService;
274
		static $validatedDefault = false;
275
276
		if ( $geocoderIdentifier === '' || !array_key_exists( $geocoderIdentifier, self::$registeredGeocoders ) ) {
277
			if ( !$validatedDefault ) {
278
				if ( !array_key_exists( $egMapsDefaultGeoService, self::$registeredGeocoders ) ) {
279
					$services = array_keys( self::$registeredGeocoders );
280
					$egMapsDefaultGeoService = array_shift( $services );
281
					if ( is_null( $egMapsDefaultGeoService ) ) {
282
						throw new MWException( 'Tried to geocode while there are no geocoders available at ' . __METHOD__  );
283
					}
284
				}
285
			}
286
287
			if ( !array_key_exists( $egMapsDefaultGeoService, self::$registeredGeocoders ) ) {
288
				return false;
289
			}
290
291
			$geocoderIdentifier = $egMapsDefaultGeoService;
292
		}
293
294
		return $geocoderIdentifier;
295
	}
296
297
}
298