Issues (18)

src/Entity/Image.php (2 issues)

1
<?php
2
3
namespace App\Entity;
4
5
use App\EventListener\ImageCalculatedFieldSetterListener;
6
use App\EventListener\WanderUploadListener;
7
use App\Repository\ImageRepository;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\Common\Collections\Collection;
10
use Doctrine\ORM\Mapping as ORM;
11
use Symfony\Component\HttpFoundation\File\File;
12
use Vich\UploaderBundle\Mapping\Annotation as Vich;
13
use Symfony\Component\Validator\Constraints as Assert;
14
use Symfony\Component\Serializer\Annotation\Groups;
15
use Symfony\Component\Serializer\Annotation\Ignore;
16
use App\EventListener\SearchIndexer;
17
use Beelab\TagBundle\Tag\TaggableInterface;
18
use Beelab\TagBundle\Tag\TagInterface;
19
use Doctrine\ORM\Event\LifecycleEventArgs;
20
use Doctrine\ORM\Mapping\Table;
21
use Doctrine\ORM\Mapping\Index;
22
use DateTimeInterface;
23
24
/**
25
 *
26
 * @ORM\Entity(repositoryClass=ImageRepository::class)
27
 *
28
 * @ORM\EntityListeners({
29
 *     ImageCalculatedFieldSetterListener::class,
30
 *     SearchIndexer::class
31
 * })
32
 *
33
 * @ORM\HasLifecycleCallbacks()
34
 *
35
 * This is just to control the stuff that goes back from our one controller
36
 * action that returns a JSON response, ImageController::upload
37
 *
38
 * @Vich\Uploadable
39
 */
40
41
class Image implements TaggableInterface
42
{
43
    /**
44
     * @ORM\Id
45
     * @ORM\GeneratedValue
46
     * @ORM\Column(type="integer")
47
     *
48
     * @Groups({"wander:item", "image:list"})
49
     * @var int
50
     */
51
    private $id;
52
53
    // TODO: We probably don't want this massive field being returned
54
    // as part of any API response, etc.
55
    /**
56
     * @Vich\UploadableField(mapping="image", fileNameProperty="name", size="sizeInBytes",
57
     *  mimeType="mimeType", originalName="originalName", dimensions="dimensions")
58
     *
59
     * @Ignore()
60
     * @var File|null
61
     */
62
    private $imageFile;
63
64
    /**
65
     * @ORM\Column(type="string", length=255, nullable=true)
66
     *
67
     * @Groups({"wander:item", "image:list"})
68
     * @var string|null
69
     */
70
    private $name; // For Vich, not for us. We use Title.
71
72
    /**
73
     * @ORM\Column(type="string", length=255, nullable=true)
74
     *
75
     * @Groups({"wander:item", "image:list"})
76
     * @var string|null
77
     */
78
    private $title;
79
80
    /**
81
     * @ORM\Column(type="text", nullable=true)
82
     *
83
     * @Groups({"wander:item", "image:list"})
84
     * @var string|null
85
     */
86
    private $description;
87
88
    /**
89
     * @ORM\Column(type="integer", nullable=true)
90
     *
91
     * @Groups({"wander:item"})
92
     * @var int|null
93
     */
94
    private $sizeInBytes;
95
96
    /**
97
     * @ORM\Column(type="string", length=255, nullable=true)
98
     *
99
     * @Groups({"wander:item"})
100
     * @var string|null
101
     */
102
    private $mimeType;
103
104
    /**
105
     * @ORM\Column(type="string", length=255, nullable=true)
106
     *
107
     * @Groups({"wander:item"})
108
     * @var string|null
109
     */
110
    private $originalName;
111
112
    /**
113
     * @ORM\Column(type="simple_array", nullable=true)
114
     *
115
     * @Groups({"wander:item"})
116
     * @var ?array<int>
117
     */
118
    private $dimensions = [];
119
120
    /**
121
     * @ORM\Column(type="datetime")
122
     *
123
     * @Groups({"wander:item"})
124
     *
125
     * @var \DateTimeInterface|null
126
     */
127
    private $updatedAt;
128
129
    /**
130
     * @ORM\Column(type="simple_array", nullable=true)
131
     * @Assert\AtLeastOneOf({
132
     *   @Assert\Count(
133
     *      min = 2,
134
     *      max = 2,
135
     *      exactMessage = "Co-ordinates must consist of a latitude, longitude pair (or nothing.)"
136
     *   ),
137
     *   @Assert\Count(
138
     *      min = 0,
139
     *      max = 0,
140
     *      exactMessage = "Co-ordinates must consist of a latitude, longitude pair (or nothing.)"
141
     *   )
142
     * })
143
     *
144
     * @Groups({"wander:item", "image:list"})
145
     * @var ?array<float>
146
     */
147
    private $latlng = [];
148
149
    /**
150
     * @var Collection<int, TagInterface>
151
     * @ORM\ManyToMany(targetEntity="Tag")
152
     */
153
    private $tags;
154
155
    /**
156
     * @ORM\Column(type="datetime", nullable=true)
157
     *
158
     * @Groups({"wander:item"})
159
     * @var DateTimeInterface
160
     */
161
    private $capturedAt;
162
163
    /**
164
     * @ORM\Column(type="integer", nullable=true)
165
     * @Groups({"wander:item"})
166
     * @var ?int
167
     */
168
    private $rating;
169
170
    // TODO: This @Ignore was here from when this was a many-to-many. Do we still
171
    // need it?
172
    /**
173
     * @ORM\ManyToOne(targetEntity=Wander::class, inversedBy="images")
174
     *
175
     * @Ignore()
176
     * @var ?Wander
177
     */
178
    private $wander;
179
180
    public function __construct()
181
    {
182
        $this->tags = new ArrayCollection();
183
    }
184
185
    /**
186
     * If manually uploading a file (i.e. not using Symfony Form) ensure an instance
187
     * of 'UploadedFile' is injected into this setter to trigger the update. If this
188
     * bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
189
     * must be able to accept an instance of 'File' as the bundle will inject one here
190
     * during Doctrine hydration.
191
     *
192
     * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile|null $imageFile
193
     */
194
    public function setImageFile(?File $imageFile = null): void
195
    {
196
        $this->imageFile = $imageFile;
197
198
        if (null !== $imageFile) {
199
            // It is required that at least one field changes if you are using doctrine
200
            // otherwise the event listeners won't be called and the file is lost
201
            $this->updatedAt = new \DateTimeImmutable();
202
        }
203
    }
204
205
    /**
206
     * @Ignore()
207
     */
208
    public function getImageFile(): ?File
209
    {
210
        return $this->imageFile;
211
    }
212
213
    public function getId(): ?int
214
    {
215
        return $this->id;
216
    }
217
218
    public function getName(): ?string
219
    {
220
        return $this->name;
221
    }
222
223
    public function setName(?string $name): self
224
    {
225
        $this->name = $name;
226
227
        return $this;
228
    }
229
230
    public function getTitle(): ?string
231
    {
232
        return $this->title;
233
    }
234
235
    public function getTitleOrId(): string
236
    {
237
        if ($this->title !== null && $this->title != "") {
238
            return $this->title;
239
        }
240
        return (string) "Image " . $this->id;
241
    }
242
243
    public function setTitle(?string $title): self
244
    {
245
        $this->title = $title;
246
247
        return $this;
248
    }
249
250
    public function getDescription(): ?string
251
    {
252
        return $this->description;
253
    }
254
255
    public function setDescription(?string $description): self
256
    {
257
        $this->description = $description;
258
259
        return $this;
260
    }
261
262
    public function getSizeInBytes(): ?int
263
    {
264
        return $this->sizeInBytes;
265
    }
266
267
    public function setSizeInBytes(?int $sizeInBytes): self
268
    {
269
        $this->sizeInBytes = $sizeInBytes;
270
271
        return $this;
272
    }
273
274
    public function getMimeType(): ?string
275
    {
276
        return $this->mimeType;
277
    }
278
279
    public function setMimeType(?string $mimeType): self
280
    {
281
        $this->mimeType = $mimeType;
282
283
        return $this;
284
    }
285
286
    public function getOriginalName(): ?string
287
    {
288
        return $this->originalName;
289
    }
290
291
    public function setOriginalName(?string $originalName): self
292
    {
293
        $this->originalName = $originalName;
294
295
        return $this;
296
    }
297
298
    /**
299
     * @return ?array<int>
300
     */
301
    public function getDimensions(): ?array
302
    {
303
        return $this->dimensions;
304
    }
305
306
    /**
307
     * @param ?array<int> $dimensions
308
     *
309
     */
310
    public function setDimensions(?array $dimensions): self
311
    {
312
        $this->dimensions = $dimensions;
313
314
        return $this;
315
    }
316
317
    public function getUpdatedAt(): ?\DateTimeInterface
318
    {
319
        return $this->updatedAt;
320
    }
321
322
    public function setUpdatedAt(\DateTimeInterface $updatedAt): self
323
    {
324
        $this->updatedAt = $updatedAt;
325
        return $this;
326
    }
327
328
    public function getLatitude(): ?float
329
    {
330
        if ($this->latlng === null ||
331
            !is_array($this->latlng) ||
332
            empty($this->latlng)) {
333
            return null;
334
        }
335
        return $this->latlng[0];
336
    }
337
338
    public function getLongitude(): ?float
339
    {
340
        if ($this->latlng === null ||
341
            !is_array($this->latlng) ||
342
            empty($this->latlng)) {
343
            return null;
344
        }
345
        return $this->latlng[1];
346
    }
347
348
    /**
349
     * @return ?array<float>
350
     */
351
    public function getLatlng(): ?array
352
    {
353
        return $this->latlng;
354
    }
355
356
    public function hasLatlng(): bool
357
    {
358
        return is_array($this->latlng) && count($this->latlng) == 2;
359
    }
360
361
    /**
362
     * @param ?array<int> $latlng
363
     */
364
    public function setLatlng(?array $latlng): self
365
    {
366
        $this->latlng = $latlng;
367
368
        return $this;
369
    }
370
371
    public function addTag(TagInterface $tag): void
372
    {
373
        if (!$this->tags->contains($tag)) {
374
            $this->tags->add($tag);
375
        }
376
    }
377
378
    public function clearTags(): void
379
    {
380
        $this->tags->clear();
381
    }
382
383
    public function removeTag(TagInterface $tag): void
384
    {
385
        $this->tags->removeElement($tag);
386
    }
387
388
    public function hasTag(TagInterface $tag): bool
389
    {
390
        return $this->tags->contains($tag);
391
    }
392
393
    /**
394
     * @return Collection<int, TagInterface>
395
     */
396
    public function getTags(): iterable
397
    {
398
        return $this->tags;
399
    }
400
401
    /**
402
     * @param Collection<int, TagInterface> $tags
403
     */
404
    public function setTags($tags): self
405
    {
406
        $this->clearTags();
407
        foreach ($tags as $tag) {
408
            $this->addTag($tag);
409
        }
410
        return $this;
411
    }
412
413
    /** @var string|null */
414
    private $tagsText;
415
416
    public function setTagsText(?string $tagsText): void
417
    {
418
        $this->tagsText = $tagsText;
419
        $this->updatedAt = new \DateTimeImmutable();
420
    }
421
422
    public function getTagsText(): ?string
423
    {
424
        $this->tagsText = \implode(', ', $this->tags->toArray());
425
        return $this->tagsText;
426
    }
427
428
    /**
429
     * @ORM\PostLoad
430
     */
431
    public function postLoad(): void
432
    {
433
        // Bodge to workaround behaviour of BeelabTagBundle, which updates
434
        // tags on persist, but only from the text tags. So if you don't
435
        // get/set the tags text, when you persist your entity all its
436
        // tags disappear. Sigh.
437
        $this->getTagsText();
438
    }
439
440
    /**
441
     * @return array<string>
442
     */
443
    public function getTagNames(): array
444
    {
445
        return empty($this->tagsText) ? [] : \array_map('trim', explode(',', $this->tagsText));
446
    }
447
448
    public function getCapturedAt(): ?\DateTimeInterface
449
    {
450
        return $this->capturedAt;
451
    }
452
453
    public function setCapturedAt(\DateTimeInterface $capturedAt): self
454
    {
455
        $this->capturedAt = $capturedAt;
456
457
        return $this;
458
    }
459
460
    public function hasCapturedAt(): bool
461
    {
462
        return $this->capturedAt !== null;
463
    }
464
465
    public function getWander(): ?Wander
466
    {
467
        return $this->wander;
468
    }
469
470
    public function setWander(?Wander $wander): self
471
    {
472
        $this->wander = $wander;
473
        return $this;
474
    }
475
476
    public function hasWander(): bool
477
    {
478
        return ($this->wander !== null);
479
    }
480
481
    public function getRating(): ?int
482
    {
483
        return $this->rating;
484
    }
485
486
    public function setRating(?int $rating): self
487
    {
488
        $this->rating = $rating;
489
490
        return $this;
491
    }
492
493
494
    /* Computed (set up by Doctrine postLoad listener) */
495
496
    /**
497
     * @Groups({"wander:item"})
498
     * @var string
499
     */
500
    private $imageUri;
501
502
    /**
503
     * @Groups({"wander:item", "image:list"})
504
     * @var string
505
     */
506
    private $markerImageUri;
507
508
    /**
509
     * @Groups({"wander:item", "image:list"})
510
     * @var string
511
     */
512
    private $mediumImageUri;
513
514
    /**
515
     * @Groups({"wander:item", "image:list"})
516
     * @var string
517
     */
518
    private $imageShowUri;
519
520
    /**
521
     * @ORM\Column(type="array", nullable=true)
522
     * @var array<string>
523
     */
524
    private $autoTags = [];
525
526
    /**
527
     * @ORM\Column(type="string", length=255, nullable=true)
528
     * @var ?string
529
     */
530
    private $location;
531
532
    /**
533
     * @ORM\OneToOne(targetEntity=Wander::class, inversedBy="featuredImage", cascade={"persist"})
534
     * @var ?Wander
535
     */
536
    private $featuringWander;
537
538
    /**
539
     * @ORM\Column(type="array", nullable=true)
540
     * @var array<string>
541
     */
542
    private $textTags = [];
543
544
    public function setImageUri(string $imageUri): void
545
    {
546
        $this->imageUri = $imageUri;
547
    }
548
549
    public function getImageUri(): ?string {
550
        return $this->imageUri;
551
    }
552
553
    public function setMarkerImageUri(string $markerImageUri): void
554
    {
555
        $this->markerImageUri = $markerImageUri;
556
    }
557
    public function getMarkerImageUri(): ?string {
558
        return $this->markerImageUri;
559
    }
560
561
    public function setMediumImageUri(string $mediumImageUri): void
562
    {
563
        $this->mediumImageUri = $mediumImageUri;
564
    }
565
    public function getMediumImageUri(): ?string
566
    {
567
        return $this->mediumImageUri;
568
    }
569
570
    public function setImageShowUri(string $imageShowUri): void
571
    {
572
        $this->imageShowUri = $imageShowUri;
573
    }
574
    public function getImageShowUri(): ?string
575
    {
576
        return $this->imageShowUri;
577
    }
578
579
    /**
580
     * @return array<string>
581
     */
582
    public function getAutoTags(): ?array
583
    {
584
        return $this->autoTags;
585
    }
586
587
    /**
588
     * @param ?string[] $autoTags
589
     */
590
    public function setAutoTags(?array $autoTags): self
591
    {
592
        // TODO: I think we should probably just declare our parameter non-nullable,
593
        // but I'm not going to try that just yet.
594
        $this->autoTags = $autoTags === null ? [] : $autoTags;
0 ignored issues
show
The condition $autoTags === null is always false.
Loading history...
595
        return $this;
596
    }
597
598
    public function getAutoTagsCount(): int
599
    {
600
        return count($this->autoTags);
601
    }
602
603
    public function getLocation(): ?string
604
    {
605
        return $this->location;
606
    }
607
608
    public function hasLocation(): bool
609
    {
610
        return $this->location !== null && $this->location <> '';
611
    }
612
613
    public function setLocation(?string $location): self
614
    {
615
        $this->location = $location;
616
617
        return $this;
618
    }
619
620
    public function getFeaturingWander(): ?Wander
621
    {
622
        return $this->featuringWander;
623
    }
624
625
    public function setFeaturingWander(?Wander $featuringWander): self
626
    {
627
        $this->featuringWander = $featuringWander;
628
629
        return $this;
630
    }
631
632
    public function setAsFeaturedImage(): void
633
    {
634
        $wander = $this->wander;
635
        if ($wander === null) {
636
            throw new \Exception("Can't call setAsFeaturedImage unless the Image is associated with a Wander.");
637
        }
638
        $this->setFeaturingWander($wander);
639
    }
640
641
    // Used when building drop-down list of Images to choose as selection on Wander edit screen
642
    public function __toString(): string
643
    {
644
        $result = $this->title ?? (string) $this->id;
645
        if (isset($this->capturedAt)) {
646
            $result .= ' (' . $this->capturedAt->format('j M Y') . ')';
647
        }
648
        if (isset($this->rating)) {
649
            $result .= ' ' . str_repeat('★', $this->rating);
0 ignored issues
show
It seems like $this->rating can also be of type null; however, parameter $times of str_repeat() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

649
            $result .= ' ' . str_repeat('★', /** @scrutinizer ignore-type */ $this->rating);
Loading history...
650
        }
651
        return $result;
652
    }
653
654
    /**
655
     * @return ?array<string>
656
     */
657
    public function getTextTags(): ?array
658
    {
659
        return $this->textTags;
660
    }
661
662
    public function getTextTagsCount(): int
663
    {
664
        return count($this->textTags);
665
    }
666
667
668
    /**
669
     * @param array<string> $textTags
670
     */
671
    public function setTextTags(array $textTags): self
672
    {
673
        $this->textTags = $textTags;
674
        return $this;
675
    }
676
}
677
678