Completed
Pull Request — master (#67)
by Matt
07:36 queued 15s
created

Wander   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 1
Metric Value
eloc 125
c 7
b 0
f 1
dl 0
loc 472
rs 6.4799
wmc 54

46 Methods

Rating   Name   Duplication   Size   Complexity  
A getTitle() 0 3 1
A setTitle() 0 5 1
A getStartTime() 0 3 1
A setStartTime() 0 5 1
A __construct() 0 3 1
A setDescription() 0 5 1
A setEndTime() 0 5 1
A getImages() 0 3 1
A getEndTime() 0 3 1
A getGpxFilename() 0 3 1
A getDescription() 0 3 1
A setGpxFilename() 0 5 1
A isTimeLengthSuspicious() 0 4 1
A getId() 0 3 1
A getImagesWithNoLatLng() 0 5 1
A getImagesWithNoTitle() 0 5 1
A getImagesWithNoLocation() 0 5 1
A getImagesWithNoRating() 0 5 1
A getImagesWithNoTags() 0 4 1
A setCumulativeElevationGain() 0 5 1
A removeAllImages() 0 7 2
A getMaxAltitude() 0 3 1
A setAvgPace() 0 5 1
A getAvgPace() 0 3 1
A getDistance() 0 3 1
A removeImage() 0 6 1
A getMinAltitude() 0 3 1
A setDistance() 0 5 1
A getImagesWithNoAutoTags() 0 4 1
A setMaxAltitude() 0 5 1
A setAvgSpeed() 0 5 1
A addImage() 0 8 2
A setMinAltitude() 0 5 1
A getCumulativeElevationGain() 0 3 1
A getAvgSpeed() 0 3 1
A getDuration() 0 8 2
A getAngleFromHome() 0 3 1
A getCentroid() 0 3 1
A setCentroid() 0 5 1
A setAngleFromHome() 0 5 1
A getImageCount() 0 4 1
A getSector() 0 8 4
A __toString() 0 7 2
A getCentroidAsString() 0 5 2
A getGeoJson() 0 3 1
A setGeoJson() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Wander 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.

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 Wander, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Entity;
4
5
use ApiPlatform\Core\Annotation\ApiProperty;
6
use App\Repository\WanderRepository;
7
use ApiPlatform\Core\Annotation\ApiResource;
8
use ApiPlatform\Core\Annotation\ApiSubresource;
9
use DateInterval;
10
use DateTime;
11
use Doctrine\Common\Collections\ArrayCollection;
12
use Doctrine\Common\Collections\Collection;
13
use Symfony\Component\Serializer\Annotation\Groups;
14
use Doctrine\ORM\Mapping as ORM;
15
use App\EventListener\WanderUploadListener;
16
use Carbon\Carbon;
17
use Carbon\CarbonInterval;
18
use Doctrine\Common\Collections\Criteria;
19
20
/**
21
 * @ORM\Entity(repositoryClass=WanderRepository::class)
22
 *
23
 * @ORM\EntityListeners({
24
 *  WanderUploadListener::class, App\EventListener\WanderDeleteListener::class
25
 * })
26
 *
27
 * @ApiResource(
28
 *  collectionOperations={"get"={"normalization_context"={"groups"="wander:list"}}},
29
 *  itemOperations={"get"={"normalization_context"={"groups"="wander:item"}}},
30
 *  order={"endTime"="ASC"},
31
 *  paginationEnabled=false
32
 * )
33
 * @ORM\HasLifecycleCallbacks()
34
 *
35
 */
36
class Wander
37
{
38
    /**
39
     * @ORM\Id
40
     * @ORM\GeneratedValue
41
     * @ORM\Column(type="integer")
42
     *
43
     * @Groups({"wander:list", "wander:item"})
44
     */
45
    private $id;
46
47
    /**
48
     * @ORM\Column(type="string", length=1024)
49
     *
50
     * @Groups({"wander:list", "wander:item"})
51
     */
52
    private $title;
53
54
    /**
55
     * @ORM\Column(type="datetime")
56
     *
57
     * @Groups({"wander:list", "wander:item"})
58
     */
59
    private $startTime;
60
61
    /**
62
     * @ORM\Column(type="datetime")
63
     *
64
     * @Groups({"wander:list", "wander:item"})
65
     */
66
    private $endTime;
67
68
    /**
69
     * @ORM\Column(type="text", nullable=true)
70
     *
71
     * @Groups({"wander:list", "wander:item"})
72
     */
73
    private $description;
74
75
    /**
76
     * @ORM\Column(type="string", length=255)
77
     *
78
     * @Groups({"wander:list", "wander:item"})
79
     */
80
    private $gpxFilename;
81
82
    /**
83
     * @ORM\OneToMany(targetEntity=Image::class, mappedBy="wander", cascade={"persist"})
84
     *
85
     * @Groups({"wander:list", "wander:item"})
86
     * @ApiSubresource
87
     */
88
    private $images;
89
90
    /**
91
     * @ORM\Column(type="float", nullable=true)
92
     *
93
     * Distance walked, in metres.
94
     *
95
     */
96
    private $distance;
97
98
    /**
99
     * @ORM\Column(type="float", nullable=true)
100
     */
101
    private $avgSpeed;
102
103
    /**
104
     * @ORM\Column(type="float", nullable=true)
105
     */
106
    private $avgPace;
107
108
    /**
109
     * @ORM\Column(type="float", nullable=true)
110
     */
111
    private $minAltitude;
112
113
    /**
114
     * @ORM\Column(type="float", nullable=true)
115
     */
116
    private $maxAltitude;
117
118
    /**
119
     * @ORM\Column(type="float", nullable=true)
120
     */
121
    private $cumulativeElevationGain;
122
123
    /**
124
     * @var string|null
125
     *
126
     * @ApiProperty(iri="http://schema.org/contentUrl")
127
     * @Groups({"wander:list", "wander:item"})
128
     */
129
    public $contentUrl;
130
131
    /**
132
     * @ORM\Column(type="array", nullable=true)
133
     */
134
    private $centroid = [];
135
136
    /**
137
     * @ORM\Column(type="float", nullable=true)
138
     */
139
    private $angleFromHome;
140
141
    public function __construct()
142
    {
143
        $this->images = new ArrayCollection();
144
    }
145
146
    public function getId(): ?int
147
    {
148
        return $this->id;
149
    }
150
151
    public function getTitle(): ?string
152
    {
153
        return $this->title;
154
    }
155
156
    public function setTitle(string $title): self
157
    {
158
        $this->title = $title;
159
160
        return $this;
161
    }
162
163
    public function getStartTime(): ?\DateTimeInterface
164
    {
165
        return $this->startTime;
166
    }
167
168
    public function setStartTime(\DateTimeInterface $startTime): self
169
    {
170
        $this->startTime = $startTime;
171
172
        return $this;
173
    }
174
175
    public function getEndTime(): ?\DateTimeInterface
176
    {
177
        return $this->endTime;
178
    }
179
180
    public function setEndTime(\DateTimeInterface $endTime): self
181
    {
182
        $this->endTime = $endTime;
183
184
        return $this;
185
    }
186
187
    public function getDescription(): ?string
188
    {
189
        return $this->description;
190
    }
191
192
    public function setDescription(?string $description): self
193
    {
194
        $this->description = $description;
195
196
        return $this;
197
    }
198
199
    public function getGpxFilename(): ?string
200
    {
201
        return $this->gpxFilename;
202
    }
203
204
    public function setGpxFilename(string $gpxFilename): self
205
    {
206
        $this->gpxFilename = $gpxFilename;
207
208
        return $this;
209
    }
210
211
    public function isTimeLengthSuspicious()
212
    {
213
        $interval = $this->startTime->diff($this->endTime, true);
214
        return $interval->h > 12;
215
    }
216
217
    /**
218
     * @return Collection|Image[]
219
     */
220
    public function getImages(): Collection
221
    {
222
        return $this->images;
223
    }
224
225
    /**
226
     * @return Collection|Image[]
227
     */
228
    public function getImagesWithNoTitle(): Collection
229
    {
230
        $criteria = Criteria::create()
231
            ->where(Criteria::expr()->isNull('title'));
232
        return $this->getImages()->matching($criteria);
233
    }
234
235
    /**
236
     * @return Collection|Image[]
237
     */
238
    public function getImagesWithNoLatLng(): Collection
239
    {
240
        $criteria = Criteria::create()
241
            ->where(Criteria::expr()->isNull('latlng'));
242
        return $this->getImages()->matching($criteria);
243
    }
244
245
    /**
246
     * @return Collection|Image[]
247
     */
248
    public function getImagesWithNoLocation(): Collection
249
    {
250
        $criteria = Criteria::create()
251
            ->where(Criteria::expr()->isNull('location'));
252
        return $this->getImages()->matching($criteria);
253
    }
254
255
    /**
256
     * @return Collection|Image[]
257
     */
258
    public function getImagesWithNoRating(): Collection
259
    {
260
        $criteria = Criteria::create()
261
            ->where(Criteria::expr()->isNull('rating'));
262
        return $this->getImages()->matching($criteria);
263
    }
264
265
    /**
266
     * @return Collection|Image[]
267
     */
268
    public function getImagesWithNoTags(): Collection
269
    {
270
        return $this->getImages()->filter(function($image) {
271
            return $image->getTags()->isEmpty();
272
        });
273
    }
274
275
    /**
276
     * @return Collection|Image[]
277
     */
278
    public function getImagesWithNoAutoTags(): Collection
279
    {
280
        return $this->getImages()->filter(function($image) {
281
            return $image->getAutoTagsCount() == 0;
282
        });
283
    }
284
285
    public function addImage(Image $image): self
286
    {
287
        if (!$this->images->contains($image)) {
288
            $this->images[] = $image;
289
            $image->setWander($this);
290
        }
291
292
        return $this;
293
    }
294
295
    public function removeImage(Image $image): self
296
    {
297
        $image->setWander(null);
298
        $this->images->removeElement($image);
299
300
        return $this;
301
    }
302
303
    /**
304
     * @ORM\PreRemove
305
     */
306
    public function removeAllImages(): self
307
    {
308
        $images = $this->images;
309
        foreach ($images as $image) {
310
            $this->removeImage($image);
311
        }
312
        return $this;
313
    }
314
315
    public function getDistance(): ?float
316
    {
317
        return $this->distance;
318
    }
319
320
    public function setDistance(?float $distance): self
321
    {
322
        $this->distance = $distance;
323
324
        return $this;
325
    }
326
327
    public function getAvgSpeed(): ?float
328
    {
329
        return $this->avgSpeed;
330
    }
331
332
    public function setAvgSpeed(?float $avgSpeed): self
333
    {
334
        $this->avgSpeed = $avgSpeed;
335
336
        return $this;
337
    }
338
339
    public function getAvgPace(): ?float
340
    {
341
        return $this->avgPace;
342
    }
343
344
    public function setAvgPace(?float $avgPace): self
345
    {
346
        $this->avgPace = $avgPace;
347
348
        return $this;
349
    }
350
351
    public function getMinAltitude(): ?float
352
    {
353
        return $this->minAltitude;
354
    }
355
356
    public function setMinAltitude(?float $minAltitude): self
357
    {
358
        $this->minAltitude = $minAltitude;
359
360
        return $this;
361
    }
362
363
    public function getMaxAltitude(): ?float
364
    {
365
        return $this->maxAltitude;
366
    }
367
368
    public function setMaxAltitude(?float $maxAltitude): self
369
    {
370
        $this->maxAltitude = $maxAltitude;
371
372
        return $this;
373
    }
374
375
    public function getCumulativeElevationGain(): ?float
376
    {
377
        return $this->cumulativeElevationGain;
378
    }
379
380
    public function setCumulativeElevationGain(?float $cumulativeElevationGain): self
381
    {
382
        $this->cumulativeElevationGain = $cumulativeElevationGain;
383
384
        return $this;
385
    }
386
387
    public function getDuration(): ?CarbonInterval
388
    {
389
        if (!isset($this->startTime, $this->endTime)) {
390
            return null;
391
        }
392
393
        $difference = CarbonInterval::instance($this->startTime->diff($this->endTime));
394
        return $difference;
395
    }
396
397
    /**
398
     * @return array<float>
399
     */
400
    public function getCentroid(): ?array
401
    {
402
        return $this->centroid;
403
    }
404
405
    /**
406
     * @param array<float> $centroid
407
     */
408
    public function setCentroid(?array $centroid): self
409
    {
410
        $this->centroid = $centroid;
411
412
        return $this;
413
    }
414
415
    // We could calculate the angle from the Centroid each time, but
416
    // for now I'm just going to store it to save time.
417
    public function getAngleFromHome(): ?float
418
    {
419
        return $this->angleFromHome;
420
    }
421
422
    public function setAngleFromHome(?float $angleFromHome): self
423
    {
424
        $this->angleFromHome = $angleFromHome;
425
426
        return $this;
427
    }
428
429
    // Utilities
430
431
    // TODO: This is more like display code. Perhaps this should be in
432
    // a Twig extension and we should stick to storing the angle in here.
433
434
    /**
435
     * @var array<int, string>
436
     */
437
    private static $compass = [
438
        0 => 'N',
439
        1 => 'NE',
440
        2 => 'NE',
441
        3 => 'E',
442
        4 => 'E',
443
        5 => 'SE',
444
        6 => 'SE',
445
        7 => 'S',
446
        8 => 'S',
447
        9 => 'SW',
448
        10 => 'SW',
449
        11 => 'W',
450
        12 => 'W',
451
        13 => 'NW',
452
        14 => 'NW',
453
        15 => 'N'
454
      ];
455
456
    /**
457
     * @ORM\Column(type="text", nullable=true)
458
     *
459
     * @Groups({"wander:list", "wander:item"})
460
     *
461
     */
462
    private $geoJson;
463
464
    public function getSector(): ?string
465
    {
466
        if ($this->angleFromHome !== null) {
467
            if ($this->angleFromHome >= 0 && $this->angleFromHome < 360.0) {
468
                return self::$compass[floor($this->angleFromHome / 22.5)];
469
            }
470
        }
471
        return null;
472
    }
473
474
    public function getCentroidAsString(): ?string {
475
        if ($this->centroid !== null) {
476
            return implode(", ", $this->centroid);
477
        }
478
        return null;
479
    }
480
481
    // TODO: Put this in but didn't use it in the end; maybe I don't need it
482
    // as in Twig we can use wander.images.count anyway. Take out?
483
    public function getImageCount(): int
484
    {
485
        // https://stackoverflow.com/a/8511611/300836
486
        return $this->images->count();
487
    }
488
489
    public function __toString():string
490
    {
491
        $result = $this->title ?? '';
492
        if (isset($this->startTime)) {
493
            $result .= ' (' . $this->startTime->format('j M Y') . ')';
494
        }
495
        return $result;
496
    }
497
498
    public function getGeoJson(): ?string
499
    {
500
        return $this->geoJson;
501
    }
502
503
    public function setGeoJson(?string $geoJson): self
504
    {
505
        $this->geoJson = $geoJson;
506
507
        return $this;
508
    }
509
}
510