Mapper   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
dl 0
loc 464
rs 6.4799
c 0
b 0
f 0
wmc 54
lcom 1
cbo 13

13 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 12 3
A renderJavascript() 0 11 2
A generateRenderOptions() 0 14 6
A searchLocation() 0 12 1
D location() 0 80 27
A map() 0 17 1
A streetview() 0 19 1
A marker() 0 19 2
A informationWindow() 0 22 3
A polyline() 0 27 2
A polygon() 0 28 2
A rectangle() 0 28 2
A circle() 0 29 2

How to fix   Complexity   

Complex Class

Complex classes like Mapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Cornford\Googlmapper;
4
5
use Cornford\Googlmapper\Contracts\MappingInterface;
6
use Cornford\Googlmapper\Exceptions\MapperArgumentException;
7
use Cornford\Googlmapper\Exceptions\MapperException;
8
use Cornford\Googlmapper\Exceptions\MapperInstanceException;
9
use Cornford\Googlmapper\Exceptions\MapperSearchException;
10
use Cornford\Googlmapper\Exceptions\MapperSearchKeyException;
11
use Cornford\Googlmapper\Exceptions\MapperSearchLimitException;
12
use Cornford\Googlmapper\Exceptions\MapperSearchResponseException;
13
use Cornford\Googlmapper\Exceptions\MapperSearchResultException;
14
use Cornford\Googlmapper\Exceptions\MapperSearchResultMalformedException;
15
use Cornford\Googlmapper\Models\Location;
16
use Cornford\Googlmapper\Models\Map;
17
use Cornford\Googlmapper\Models\Streetview;
18
use Exception;
19
20
class Mapper extends MapperBase implements MappingInterface
21
{
22
    private const GOOGLE_RESPONSE_OK = 'OK';
23
    private const GOOGLE_RESPONSE_ZERO_RESULTS = 'ZERO_RESULTS';
24
    private const GOOGLE_RESPONSE_QUERY_LIMIT = 'OVER_QUERY_LIMIT';
25
    private const GOOGLE_RESPONSE_DENIED = 'REQUEST_DENIED';
26
    private const GOOGLE_RESPONSE_INVALID = 'INVALID_REQUEST';
27
    private const GOOGLE_RESPONSE_UNKNOWN = 'UNKNOWN_ERROR';
28
29
    /**
30
     * Renders and returns Google Map code.
31
     *
32
     * @param int $item
33
     *
34
     * @return string
35
     */
36
    public function render($item = -1)
37
    {
38
        if (!$this->isEnabled()) {
39
            return;
40
        }
41
42
        return $this->view->make('googlmapper::mapper')
0 ignored issues
show
Bug introduced by
The method withView() does not seem to exist on object<Illuminate\Contracts\View\View>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
43
            ->withView($this->view)
44
            ->withOptions($this->generateRenderOptions($item))
45
            ->withItems($item > -1 ? [$item => $this->getItem($item)] : $this->getItems())
46
            ->render();
47
    }
48
49
    /**
50
     * Renders and returns Google Map javascript code.
51
     *
52
     * @return string
53
     */
54
    public function renderJavascript()
55
    {
56
        if (!$this->isEnabled()) {
57
            return;
58
        }
59
60
        return $this->view->make('googlmapper::javascript')
0 ignored issues
show
Bug introduced by
The method withView() does not seem to exist on object<Illuminate\Contracts\View\View>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
61
            ->withView($this->view)
62
            ->withOptions($this->generateRenderOptions())
63
            ->render();
64
    }
65
66
    /**
67
     * Generates the render options for Google Map.
68
     *
69
     * @param int $item
70
     *
71
     * @return string
72
     */
73
    protected function generateRenderOptions($item = -1)
74
    {
75
        $options = $this->getOptions();
76
77
        foreach (($item > -1 ? [$this->getItem($item)] : $this->getItems()) as $model) {
78
            foreach ($model->getOptions() as $key => $option) {
79
                if (array_key_exists($key, $this->getOptions()) && $this->getOptions()[$key] !== $option) {
80
                    $options[$key] = $option;
81
                }
82
            }
83
        }
84
85
        return $options;
86
    }
87
88
    /**
89
     * Search for a location against Google Maps Api.
90
     *
91
     * @param string $location
92
     *
93
     * @return mixed
94
     */
95
    protected function searchLocation($location)
96
    {
97
        $request = file_get_contents(
98
            sprintf(
99
                'https://maps.googleapis.com/maps/api/geocode/json?address=%s&sensor=false&key=%s',
100
                urlencode($location),
101
                $this->getKey()
102
            )
103
        );
104
105
        return json_decode($request);
106
    }
107
108
    /**
109
     * Locate a location and return a Location instance.
110
     *
111
     * @param string $location
112
     *
113
     * @throws MapperArgumentException
114
     * @throws MapperSearchException
115
     * @throws MapperSearchResponseException
116
     * @throws MapperSearchResultException
117
     * @throws MapperSearchKeyException
118
     * @throws MapperSearchLimitException
119
     * @throws MapperException
120
     *
121
     * @return Location
122
     */
123
    public function location($location)
124
    {
125
        $location = strip_tags($location);
126
        if (empty($location)) {
127
            throw new MapperArgumentException('Invalid location search term provided.');
128
        }
129
130
        try {
131
            $result = $this->searchLocation($location);
132
        } catch (Exception $exception) {
133
            throw new MapperSearchException('Unable to perform location search, the error was:' .
134
                ' "' . $exception->getMessage() .  '".');
135
        }
136
137
        if (
138
            isset($result->status) &&
139
            $result->status == self::GOOGLE_RESPONSE_DENIED &&
140
            property_exists($result, 'error_message') &&
141
            $result->error_message == 'The provided API key is invalid.'
142
        ) {
143
            throw new MapperSearchKeyException('Unable to perform location search, provided API key is invalid.');
144
        }
145
146
        if (isset($result->status) && $result->status == self::GOOGLE_RESPONSE_QUERY_LIMIT) {
147
            throw new MapperSearchLimitException('Unable to perform location search, your API key is over your quota.');
148
        }
149
150
        if (
151
            isset($result->status) &&
152
            in_array(
153
                $result->status,
154
                [
155
                    self::GOOGLE_RESPONSE_DENIED,
156
                    self::GOOGLE_RESPONSE_INVALID,
157
                    self::GOOGLE_RESPONSE_UNKNOWN
158
                ]
159
            )
160
        ) {
161
            throw new MapperSearchResponseException('An error occurred performing the location search, the error was:' .
162
                ' "' . (property_exists($result, 'error_message') ? $result->error_message : 'Unknown') .  '".');
163
        }
164
165
        if (
166
            (isset($result->status) && $result->status == self::GOOGLE_RESPONSE_ZERO_RESULTS) ||
167
            !isset($result->results) ||
168
            (isset($result->results) && count($result->results) == 0)
169
        ) {
170
            throw new MapperSearchResultException('No results found for the location search.');
171
        }
172
173
        if (
174
            !isset($result->results[0]->formatted_address) ||
175
            !isset($result->results[0]->address_components[0]->types) ||
176
            !isset($result->results[0]->geometry->location->lat) ||
177
            !isset($result->results[0]->geometry->location->lng) ||
178
            !isset($result->results[0]->place_id) ||
179
            isset($result->status) && $result->status != self::GOOGLE_RESPONSE_OK
180
        ) {
181
            throw new MapperSearchResultMalformedException('The location search return invalid result data.');
182
        }
183
184
        $postalCode = null;
185
186
        foreach ($result->results[0]->address_components as $addressComponent) {
187
            if (count($addressComponent->types) > 0 && $addressComponent->types[0] == 'postal_code') {
188
                $postalCode = $addressComponent->long_name;
189
            }
190
        }
191
192
        return new Location([
193
            'mapper'     => $this,
194
            'search'     => $location,
195
            'address'    => $result->results[0]->formatted_address,
196
            'postalCode' => $postalCode,
197
            'type'       => ($result->results[0]->address_components[0]->types[0] ?? null),
198
            'latitude'   => $result->results[0]->geometry->location->lat,
199
            'longitude'  => $result->results[0]->geometry->location->lng,
200
            'placeId'    => $result->results[0]->place_id,
201
        ]);
202
    }
203
204
    /**
205
     * Add a new map.
206
     *
207
     * @param float $latitude
208
     * @param float $longitude
209
     * @param array $options
210
     *
211
     * @return self
212
     */
213
    public function map($latitude, $longitude, array $options = [])
214
    {
215
        $parameters = array_replace_recursive(
216
            $this->getOptions(),
217
            [
218
                'latitude' => $latitude,
219
                'longitude' => $longitude,
220
                'map' => 'map_' . count($this->getItems())
221
            ],
222
            $options
223
        );
224
225
        $item = new Map($parameters);
226
        $this->addItem($item);
227
228
        return $this;
229
    }
230
231
    /**
232
     * Add a new street view map.
233
     *
234
     * @param float $latitude
235
     * @param float $longitude
236
     * @param int   $heading
237
     * @param int   $pitch
238
     * @param array $options
239
     *
240
     * @return self
241
     */
242
    public function streetview($latitude, $longitude, $heading, $pitch, array $options = [])
243
    {
244
        $parameters = array_replace_recursive(
245
            $this->getOptions(),
246
            [
247
                'latitude' => $latitude,
248
                'longitude' => $longitude,
249
                'heading' => $heading,
250
                'pitch' => $pitch,
251
                'map' => 'map_' . count($this->getItems())
252
            ],
253
            $options
254
        );
255
256
        $item = new Streetview($parameters);
257
        $this->addItem($item);
258
259
        return $this;
260
    }
261
262
    /**
263
     * Add a new map marker.
264
     *
265
     * @param float $latitude
266
     * @param float $longitude
267
     * @param array $options
268
     *
269
     * @throws MapperException
270
     *
271
     * @return self
272
     */
273
    public function marker($latitude, $longitude, array $options = [])
274
    {
275
        $items = $this->getItems();
276
277
        if (empty($items)) {
278
            throw new MapperInstanceException('No map found to add a marker to.');
279
        }
280
        $item = end($items);
281
        $parameters = $this->getOptions();
282
        $options = array_replace_recursive(
283
            ['markers' => $parameters['markers']],
284
            $item->getOptions()['markers'],
285
            $options
286
        );
287
288
        $item->marker($latitude, $longitude, $options);
289
290
        return $this;
291
    }
292
293
    /**
294
     * Add a new map information window.
295
     *
296
     * @param float  $latitude
297
     * @param float  $longitude
298
     * @param string $content
299
     * @param array  $options
300
     *
301
     * @throws MapperException
302
     *
303
     * @return self
304
     */
305
    public function informationWindow($latitude, $longitude, $content = '', array $options = [])
306
    {
307
        $items = $this->getItems();
308
309
        if (empty($items)) {
310
            throw new MapperInstanceException('No map found to add a information window to.');
311
        }
312
313
        $item = end($items);
314
315
        $parameters = $this->getOptions();
316
        $options = array_replace_recursive(
317
            ['markers' => $parameters['markers']],
318
            $item->getOptions()['markers'],
319
            $options,
320
            ($content !== '' ? ['markers' => ['content' => $content]] : [])
321
        );
322
323
        $item->marker($latitude, $longitude, $options);
324
325
        return $this;
326
    }
327
328
    /**
329
     * Add a new map polyline.
330
     *
331
     * @param array $coordinates
332
     * @param array $options
333
     *
334
     * @throws MapperException
335
     *
336
     * @return self
337
     */
338
    public function polyline(array $coordinates = [], array $options = [])
339
    {
340
        $items = $this->getItems();
341
342
        if (empty($items)) {
343
            throw new MapperInstanceException('No map found to add a polyline to.');
344
        }
345
346
        $defaults = [
347
            'coordinates' => $coordinates,
348
            'geodesic' => false,
349
            'strokeColor' => '#FF0000',
350
            'strokeOpacity' => 0.8,
351
            'strokeWeight' => 2,
352
            'editable' => false
353
        ];
354
355
        $item = end($items);
356
        $options = array_replace_recursive(
357
            $defaults,
358
            $options
359
        );
360
361
        $item->shape('polyline', $coordinates, $options);
362
363
        return $this;
364
    }
365
366
    /**
367
     * Add a new map polygon.
368
     *
369
     * @param array $coordinates
370
     * @param array $options
371
     *
372
     * @throws MapperException
373
     *
374
     * @return self
375
     */
376
    public function polygon(array $coordinates = [], array $options = [])
377
    {
378
        $items = $this->getItems();
379
380
        if (empty($items)) {
381
            throw new MapperInstanceException('No map found to add a polygon to.');
382
        }
383
384
        $defaults = [
385
            'coordinates' => $coordinates,
386
            'strokeColor' => '#FF0000',
387
            'strokeOpacity' => 0.8,
388
            'strokeWeight' => 2,
389
            'fillColor' => '#FF0000',
390
            'fillOpacity' => 0.35,
391
            'editable' => false
392
        ];
393
394
        $item = end($items);
395
        $options = array_replace_recursive(
396
            $defaults,
397
            $options
398
        );
399
400
        $item->shape('polygon', $coordinates, $options);
401
402
        return $this;
403
    }
404
405
    /**
406
     * Add a new map rectangle.
407
     *
408
     * @param array $coordinates
409
     * @param array $options
410
     *
411
     * @throws MapperException
412
     *
413
     * @return self
414
     */
415
    public function rectangle(array $coordinates = [], array $options = [])
416
    {
417
        $items = $this->getItems();
418
419
        if (empty($items)) {
420
            throw new MapperInstanceException('No map found to add a rectangle to.');
421
        }
422
423
        $defaults = [
424
            'coordinates' => $coordinates,
425
            'strokeColor' => '#FF0000',
426
            'strokeOpacity' => 0.8,
427
            'strokeWeight' => 2,
428
            'fillColor' => '#FF0000',
429
            'fillOpacity' => 0.35,
430
            'editable' => false
431
        ];
432
433
        $item = end($items);
434
        $options = array_replace_recursive(
435
            $defaults,
436
            $options
437
        );
438
439
        $item->shape('rectangle', $coordinates, $options);
440
441
        return $this;
442
    }
443
444
    /**
445
     * Add a new map circle.
446
     *
447
     * @param array $coordinates
448
     * @param array $options
449
     *
450
     * @throws MapperException
451
     *
452
     * @return self
453
     */
454
    public function circle(array $coordinates = [], array $options = [])
455
    {
456
        $items = $this->getItems();
457
458
        if (empty($items)) {
459
            throw new MapperInstanceException('No map found to add a circle to.');
460
        }
461
462
        $defaults = [
463
            'coordinates' => $coordinates,
464
            'strokeColor' => '#FF0000',
465
            'strokeOpacity' => 0.8,
466
            'strokeWeight' => 2,
467
            'fillColor' => '#FF0000',
468
            'fillOpacity' => 0.35,
469
            'radius' => 100000,
470
            'editable' => false
471
        ];
472
473
        $item = end($items);
474
        $options = array_replace_recursive(
475
            $defaults,
476
            $options
477
        );
478
479
        $item->shape('circle', $coordinates, $options);
480
481
        return $this;
482
    }
483
}
484