Passed
Push — master ( 03a6f5...40da0b )
by Luca
11:01 queued 15s
created

Card::setFile()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 7
ccs 3
cts 5
cp 0.6
rs 10
cc 2
nc 3
nop 1
crap 2.2559
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Model;
6
7
use Application\Api\Exception;
8
use Application\Service\DatingRule;
9
use Application\Traits\CardSimpleProperties;
10
use Application\Traits\HasAddress;
11
use Application\Traits\HasCode;
12
use Application\Traits\HasImage;
13
use Application\Traits\HasInstitution;
14
use Application\Traits\HasParentInterface;
15
use Application\Traits\HasRichTextName;
16
use Application\Traits\HasSite;
17
use Application\Traits\HasSiteInterface;
18
use Application\Traits\HasValidation;
19
use Application\Traits\HasYearRange;
20
use Doctrine\Common\Collections\ArrayCollection;
21
use Doctrine\Common\Collections\Collection as DoctrineCollection;
22
use Doctrine\ORM\Mapping as ORM;
23
use GraphQL\Doctrine\Annotation as API;
24
use GraphQL\Doctrine\Definition\EntityID;
25
use Imagine\Filter\Basic\Autorotate;
26
use Imagine\Image\ImagineInterface;
27
use InvalidArgumentException;
28
use Psr\Http\Message\UploadedFileInterface;
29
use Throwable;
30
31
/**
32
 * A card containing an image and some information about it
33
 *
34
 * @ORM\HasLifecycleCallbacks
35
 * @ORM\Entity(repositoryClass="Application\Repository\CardRepository")
36
 * @ORM\Table(indexes={
37
 *     @ORM\Index(name="card_name_idx", columns={"name"}),
38
 *     @ORM\Index(name="card_plain_name_idx", columns={"plain_name"}),
39
 *     @ORM\Index(name="card_locality_idx", columns={"locality"}),
40
 *     @ORM\Index(name="card_area_idx", columns={"area"}),
41
 * },
42
 * uniqueConstraints={
43
 *     @ORM\UniqueConstraint(name="unique_code", columns={"code", "site"})
44
 * })
45
 * @API\Filters({
46
 *     @API\Filter(field="nameOrExpandedName", operator="Application\Api\Input\Operator\NameOrExpandedNameOperatorType", type="string"),
47
 *     @API\Filter(field="artistOrTechniqueAuthor", operator="Application\Api\Input\Operator\ArtistOrTechniqueAuthorOperatorType", type="string"),
48
 *     @API\Filter(field="localityOrInstitutionLocality", operator="Application\Api\Input\Operator\LocalityOrInstitutionLocalityOperatorType", type="string"),
49
 *     @API\Filter(field="datingYearRange", operator="Application\Api\Input\Operator\DatingYearRangeOperatorType", type="int"),
50
 *     @API\Filter(field="cardYearRange", operator="Application\Api\Input\Operator\CardYearRangeOperatorType", type="int"),
51
 *     @API\Filter(field="custom", operator="Application\Api\Input\Operator\LocationOperatorType", type="string"),
52
 * })
53
 * @API\Sorting({"Application\Api\Input\Sorting\Artists"})
54
 */
55
class Card extends AbstractModel implements HasSiteInterface
56
{
57
    use HasCode;
58
    use HasRichTextName;
59
    use HasInstitution;
60
    use HasAddress;
61
    use CardSimpleProperties;
62
    use HasValidation;
63
    use HasYearRange;
64
    use HasSite;
65
    use HasImage {
66
        setFile as traitSetFile;
67
    }
68
69
    private const IMAGE_PATH = 'data/images/';
70
71
    const VISIBILITY_PRIVATE = 'private';
72
    const VISIBILITY_MEMBER = 'member';
73
    const VISIBILITY_PUBLIC = 'public';
74
75
    /**
76
     * @var string
77
     * @ORM\Column(type="CardVisibility", options={"default" = Card::VISIBILITY_PRIVATE})
78
     */
79
    private $visibility = self::VISIBILITY_PRIVATE;
80
81
    /**
82
     * @var int
83
     * @ORM\Column(type="integer")
84
     */
85
    private $fileSize = 0;
86
87
    /**
88
     * @var int
89
     * @ORM\Column(type="integer")
90
     */
91
    private $width = 0;
92
93
    /**
94
     * @var int
95
     * @ORM\Column(type="integer")
96
     */
97
    private $height = 0;
98
99
    /**
100
     * @var string
101
     *
102
     * @ORM\Column(type="string", options={"default" = ""})
103
     */
104
    private $dating = '';
105
106
    /**
107
     * @var DoctrineCollection
108
     *
109
     * @ORM\ManyToMany(targetEntity="Collection")
110
     */
111
    private $collections;
112
113
    /**
114
     * @var DoctrineCollection
115
     *
116
     * @ORM\ManyToMany(targetEntity="Artist")
117
     */
118
    private $artists;
119
120
    /**
121
     * @var DoctrineCollection
122
     *
123
     * @ORM\ManyToMany(targetEntity="AntiqueName")
124
     */
125
    private $antiqueNames;
126
127
    /**
128
     * @var DoctrineCollection
129
     *
130
     * @ORM\ManyToMany(targetEntity="Tag")
131
     */
132
    private $tags;
133
134
    /**
135
     * @var DoctrineCollection
136
     *
137
     * @ORM\OneToMany(targetEntity="Dating", mappedBy="card")
138
     */
139
    private $datings;
140
141
    /**
142
     * @var null|Card
143
     * @ORM\ManyToOne(targetEntity="Card")
144
     * @ORM\JoinColumns({
145
     *     @ORM\JoinColumn(onDelete="SET NULL")
146
     * })
147
     */
148
    private $original;
149
150
    /**
151
     * @var null|DocumentType
152
     * @ORM\ManyToOne(targetEntity="DocumentType")
153
     * @ORM\JoinColumns({
154
     *     @ORM\JoinColumn(onDelete="SET NULL")
155
     * })
156
     */
157
    private $documentType;
158
159
    /**
160
     * @var null|Domain
161
     *
162
     * @ORM\ManyToOne(targetEntity="Domain")
163
     * @ORM\JoinColumns({
164
     *     @ORM\JoinColumn(onDelete="SET NULL")
165
     * })
166
     */
167
    private $domain;
168
169
    /**
170
     * @var DoctrineCollection
171
     *
172
     * @ORM\ManyToMany(targetEntity="Period")
173
     */
174
    private $periods;
175
176
    /**
177
     * @var DoctrineCollection
178
     *
179
     * @ORM\ManyToMany(targetEntity="Material")
180
     */
181
    private $materials;
182
183
    /**
184
     * @var DoctrineCollection
185
     *
186
     * @ORM\ManyToMany(targetEntity="Card")
187
     */
188
    private $cards;
189
190
    /**
191
     * @var null|Change
192
     *
193
     * @ORM\OneToOne(targetEntity="Change", mappedBy="suggestion")
194
     */
195
    private $change;
196
197
    /**
198
     * @var string
199
     * @ORM\Column(type="string", length=191)
200
     */
201
    private $documentSize = '';
202
203
    /**
204
     * @var int
205
     * @ORM\Column(name="legacy_id", type="integer", nullable=true)
206
     */
207
    private $legacyId;
208
209
    /**
210
     * Constructor
211
     */
212 48
    public function __construct(string $name = '')
213
    {
214 48
        $this->setName($name);
215
216 48
        $this->collections = new ArrayCollection();
217 48
        $this->artists = new ArrayCollection();
218 48
        $this->antiqueNames = new ArrayCollection();
219 48
        $this->tags = new ArrayCollection();
220 48
        $this->datings = new ArrayCollection();
221 48
        $this->cards = new ArrayCollection();
222 48
        $this->periods = new ArrayCollection();
223 48
        $this->materials = new ArrayCollection();
224 48
    }
225
226
    /**
227
     * Return whether this is publicly available to everybody, or only member, or only owner
228
     *
229
     * @API\Field(type="Application\Api\Enum\CardVisibilityType")
230
     */
231 17
    public function getVisibility(): string
232
    {
233 17
        return $this->visibility;
234
    }
235
236
    /**
237
     * Set whether this is publicly available to everybody, or only member, or only owner
238
     *
239
     * @API\Input(type="Application\Api\Enum\CardVisibilityType")
240
     */
241 27
    public function setVisibility(string $visibility): void
242
    {
243 27
        if ($this->visibility === $visibility) {
244 11
            return;
245
        }
246
247 24
        $user = User::getCurrent();
248 24
        if ($visibility === self::VISIBILITY_PUBLIC && $user->getRole() !== User::ROLE_ADMINISTRATOR) {
249 2
            throw new Exception('Only administrator can make a card public');
250
        }
251
252 23
        $this->visibility = $visibility;
253 23
    }
254
255
    /**
256
     * Get collections this card belongs to
257
     *
258
     * @API\Field(type="Collection[]")
259
     */
260 10
    public function getCollections(): DoctrineCollection
261
    {
262 10
        return $this->collections;
263
    }
264
265
    /**
266
     * Get the card dating.
267
     *
268
     * This is a free form string that will be parsed to **try** and extract
269
     * some actual date range of dates. Any string is valid, but some parseable
270
     * values would typically be:
271
     *
272
     * - (1620-1652)
273
     * - 01.05.1917
274
     * - XIIIe siècle
275
     * - 1927
276
     * - c. 1100
277
     * - Fin du XIIe siècle
278
     */
279 1
    public function getDating(): string
280
    {
281 1
        return $this->dating;
282
    }
283
284
    /**
285
     * Set the card dating.
286
     *
287
     * This is a free form string that will be parsed to **try** and extract
288
     * some actual date range of dates. Any string is valid, but some parseable
289
     * values would typically be:
290
     *
291
     * - (1620-1652)
292
     * - 01.05.1917
293
     * - XIIIe siècle
294
     * - 1927
295
     * - c. 1100
296
     * - Fin du XIIe siècle
297
     */
298 10
    public function setDating(string $dating): void
299
    {
300 10
        if ($dating === $this->dating) {
301 1
            return;
302
        }
303 10
        $this->dating = $dating;
304
305 10
        $this->computeDatings();
306 10
    }
307
308
    /**
309
     * Return the automatically computed dating periods
310
     *
311
     * @API\Field(type="Dating[]")
312
     */
313 4
    public function getDatings(): DoctrineCollection
314
    {
315 4
        return $this->datings;
316
    }
317
318
    /**
319
     * Set all artists at once by their names.
320
     *
321
     * Non-existing artists will be created automatically.
322
     *
323
     * @param null|string[] $artistNames
324
     */
325 9
    public function setArtists(?array $artistNames): void
326
    {
327 9
        if (null === $artistNames) {
0 ignored issues
show
introduced by
The condition null === $artistNames is always false.
Loading history...
328
            return;
329
        }
330
331 9
        $this->artists->clear();
332
333 9
        $artistNames = _em()->getRepository(Artist::class)->getOrCreateByNames($artistNames, $this->getSite());
334 9
        foreach ($artistNames as $a) {
335 9
            $this->artists->add($a);
336
        }
337 9
    }
338
339
    /**
340
     * Set all materials at once.
341
     *
342
     * @param null|Material[] $materials
343
     */
344 7
    public function setMaterials(?array $materials): void
345
    {
346 7
        if (null === $materials) {
0 ignored issues
show
introduced by
The condition null === $materials is always false.
Loading history...
347
            return;
348
        }
349
350 7
        $this->setEntireCollection($materials, $this->materials, Material::class);
351 7
        $this->addEntireHierarchy($this->materials);
352 7
    }
353
354
    /**
355
     * Set all antiqueNames at once.
356
     *
357
     * @param null|AntiqueName[] $antiqueNames
358
     */
359 7
    public function setAntiqueNames(?array $antiqueNames): void
360
    {
361 7
        if (null === $antiqueNames) {
0 ignored issues
show
introduced by
The condition null === $antiqueNames is always false.
Loading history...
362
            return;
363
        }
364
365 7
        $this->setEntireCollection($antiqueNames, $this->antiqueNames, AntiqueName::class);
366 7
    }
367
368
    /**
369
     * Set all periods at once.
370
     *
371
     * @param null|Period[] $periods
372
     */
373 7
    public function setPeriods(?array $periods): void
374
    {
375 7
        if (null === $periods) {
0 ignored issues
show
introduced by
The condition null === $periods is always false.
Loading history...
376
            return;
377
        }
378
379 7
        $this->setEntireCollection($periods, $this->periods, Period::class);
380 7
    }
381
382
    /**
383
     * Set all tags at once.
384
     *
385
     * @param null|Tag[] $tags
386
     */
387 8
    public function setTags(?array $tags): void
388
    {
389 8
        if (null === $tags) {
0 ignored issues
show
introduced by
The condition null === $tags is always false.
Loading history...
390
            return;
391
        }
392
393 8
        $this->setEntireCollection($tags, $this->tags, Tag::class);
394 8
        $this->addEntireHierarchy($this->tags);
395 8
    }
396
397 8
    private function setEntireCollection(array $entityIds, DoctrineCollection $collection, string $class): void
398
    {
399 8
        $collection->clear();
400
401 8
        $ids = array_map(function (EntityID $m) {
402 8
            return $m->getId();
403 8
        }, $entityIds);
404
405 8
        $repository = _em()->getRepository($class);
406 8
        $objects = $repository->findBy([
407 8
            'id' => $ids,
408 8
            'site' => $this->getSite(),
409
        ]);
410
411 8
        foreach ($objects as $object) {
412 1
            $collection->add($object);
413
        }
414 8
    }
415
416
    /**
417
     * Get artists
418
     *
419
     * @API\Field(type="Artist[]")
420
     */
421 7
    public function getArtists(): DoctrineCollection
422
    {
423 7
        return $this->artists;
424
    }
425
426
    /**
427
     * Get antiqueNames
428
     *
429
     * @API\Field(type="AntiqueName[]")
430
     */
431
    public function getAntiqueNames(): DoctrineCollection
432
    {
433
        return $this->antiqueNames;
434
    }
435
436
    /**
437
     * Add tag
438
     */
439 1
    public function addTag(Tag $tag): void
440
    {
441 1
        if (!$this->tags->contains($tag)) {
442 1
            $this->tags[] = $tag;
443
        }
444 1
        $this->addEntireHierarchy($this->tags);
445 1
    }
446
447
    /**
448
     * Remove tag
449
     */
450 1
    public function removeTag(Tag $tag): void
451
    {
452 1
        $this->tags->removeElement($tag);
453 1
        $this->addEntireHierarchy($this->tags);
454 1
    }
455
456
    /**
457
     * Get tags
458
     *
459
     * @API\Field(type="Tag[]")
460
     */
461 1
    public function getTags(): DoctrineCollection
462
    {
463 1
        return $this->tags;
464
    }
465
466
    /**
467
     * The original card if this is a suggestion
468
     *
469
     * @return null|Card
470
     */
471 4
    public function getOriginal(): ?self
472
    {
473 4
        return $this->original;
474
    }
475
476
    /**
477
     * Defines this card as suggestion for the $original
478
     *
479
     * @param null|Card $original
480
     */
481 1
    public function setOriginal(?self $original): void
482
    {
483 1
        $this->original = $original;
484 1
    }
485
486 1
    public function getDocumentType(): ?DocumentType
487
    {
488 1
        return $this->documentType;
489
    }
490
491 2
    public function setDocumentType(?DocumentType $documentType): void
492
    {
493 2
        $this->documentType = $documentType;
494 2
    }
495
496 2
    public function getDomain(): ?Domain
497
    {
498 2
        return $this->domain;
499
    }
500
501 1
    public function setDomain(?Domain $domain): void
502
    {
503 1
        $this->domain = $domain;
504 1
    }
505
506
    /**
507
     * Get periods
508
     *
509
     * @API\Field(type="Period[]")
510
     */
511 2
    public function getPeriods(): DoctrineCollection
512
    {
513 2
        return $this->periods;
514
    }
515
516
    /**
517
     * Add Period
518
     */
519 2
    public function addPeriod(Period $period): void
520
    {
521 2
        if (!$this->periods->contains($period)) {
522 2
            $this->periods[] = $period;
523
        }
524 2
    }
525
526
    /**
527
     * Remove Period
528
     */
529
    public function removePeriod(Period $period): void
530
    {
531
        $this->periods->removeElement($period);
532
    }
533
534
    /**
535
     * Get materials
536
     *
537
     * @API\Field(type="Material[]")
538
     */
539 1
    public function getMaterials(): DoctrineCollection
540
    {
541 1
        return $this->materials;
542
    }
543
544
    /**
545
     * Add Material
546
     */
547 2
    public function addMaterial(Material $material): void
548
    {
549 2
        if (!$this->materials->contains($material)) {
550 2
            $this->materials[] = $material;
551
        }
552
553 2
        $this->addEntireHierarchy($this->materials);
554 2
    }
555
556
    /**
557
     * Remove Material
558
     */
559
    public function removeMaterial(Material $material): void
560
    {
561
        $this->materials->removeElement($material);
562
        $this->addEntireHierarchy($this->materials);
563
    }
564
565
    /**
566
     * Add this card into the given collection
567
     */
568 6
    public function addCollection(Collection $collection): void
569
    {
570 6
        if (!$this->collections->contains($collection)) {
571 6
            $this->collections->add($collection);
572
        }
573
574
        // If we are new and don't have a code yet, set one automatically
575 6
        if (!$this->getId() && !$this->getCode() && $this->canUpdateCode()) {
576 2
            $code = _em()->getRepository(self::class)->getNextCodeAvailable($collection);
577 2
            $this->setCode($code);
578
        }
579 6
    }
580
581
    /**
582
     * Remove this card from given collection
583
     */
584 2
    public function removeCollection(Collection $collection): void
585
    {
586 2
        $this->collections->removeElement($collection);
587 2
    }
588
589
    /**
590
     * Notify the Card that a Dating was added.
591
     * This should only be called by Dating::setCard()
592
     */
593 4
    public function datingAdded(Dating $dating): void
594
    {
595 4
        $this->datings->add($dating);
596 4
    }
597
598
    /**
599
     * Notify the Card that a Dating was removed.
600
     * This should only be called by Dating::setCard()
601
     */
602 1
    public function datingRemoved(Dating $dating): void
603
    {
604 1
        $this->datings->removeElement($dating);
605 1
    }
606
607
    /**
608
     * Get file size in bytes
609
     */
610 7
    public function getFileSize(): int
611
    {
612 7
        return $this->fileSize;
613
    }
614
615
    /**
616
     * Set file size in bytes
617
     *
618
     * @API\Exclude
619
     */
620 11
    public function setFileSize(int $fileSize): void
621
    {
622 11
        $this->fileSize = $fileSize;
623 11
    }
624
625
    /**
626
     * Get image width
627
     */
628 7
    public function getWidth(): int
629
    {
630 7
        return $this->width;
631
    }
632
633
    /**
634
     * Set image width
635
     *
636
     * @API\Exclude
637
     */
638 11
    public function setWidth(int $width): void
639
    {
640 11
        $this->width = $width;
641 11
    }
642
643
    /**
644
     * Get image height
645
     */
646 7
    public function getHeight(): int
647
    {
648 7
        return $this->height;
649
    }
650
651
    /**
652
     * Set image height
653
     *
654
     * @API\Exclude
655
     */
656 11
    public function setHeight(int $height): void
657
    {
658 11
        $this->height = $height;
659 11
    }
660
661
    /**
662
     * Set the image file
663
     *
664
     * @API\Input(type="?GraphQL\Upload\UploadType")
665
     */
666 9
    public function setFile(UploadedFileInterface $file): void
667
    {
668
        try {
669 9
            $this->traitSetFile($file);
670 9
            $this->readFileInfo();
671
        } catch (Throwable $e) {
672
            throw new \Application\Api\Exception('Erreur avec le fichier : ' . $file->getClientFilename(), 0, $e);
673
        }
674 9
    }
675
676
    /**
677
     * Get legacy id
678
     */
679
    public function getLegacyId(): ?int
680
    {
681
        return $this->legacyId;
682
    }
683
684
    /**
685
     * Set legacy id
686
     *
687
     * @API\Exclude
688
     */
689
    public function setLegacyId(int $legacyId): void
690
    {
691
        $this->legacyId = $legacyId;
692
    }
693
694
    /**
695
     * Read dimension and size from file on disk
696
     */
697 9
    private function readFileInfo(): void
698
    {
699
        global $container;
700 9
        $path = $this->getPath();
701
702
        /** @var ImagineInterface $imagine */
703 9
        $imagine = $container->get(ImagineInterface::class);
704 9
        $image = $imagine->open($path);
705
706
        // Auto-rotate image if EXIF says it's rotated
707 9
        $autorotate = new Autorotate();
708 9
        $autorotate->apply($image);
709 9
        $image->save($path);
710
711 9
        $size = $image->getSize();
712
713 9
        $this->setWidth($size->getWidth());
714 9
        $this->setHeight($size->getHeight());
715 9
        $this->setFileSize(filesize($path));
716 9
    }
717
718 12
    private function computeDatings(): void
719
    {
720 12
        $rule = new DatingRule();
721
722
        // Delete all existing
723 12
        foreach ($this->datings as $d) {
724 2
            _em()->remove($d);
725
        }
726 12
        $this->datings->clear();
727
728
        // Add new one
729 12
        $datings = $rule->compute($this->dating);
730 12
        foreach ($datings as $d) {
731 3
            _em()->persist($d);
732 3
            $d->setCard($this);
733
        }
734 12
    }
735
736
    /**
737
     * Copy most of this card data into the given card
738
     *
739
     * @param Card $original
740
     */
741 3
    public function copyInto(self $original): void
742
    {
743
        // Trigger loading of proxy
744 3
        $original->getName();
745
746
        $blacklist = [
747 3
            'id',
748
            'visibility',
749
            '__initializer__',
750
            '__cloner__',
751
            '__isInitialized__',
752
        ];
753
754 3
        if (!$this->hasImage()) {
755 1
            $blacklist[] = 'filename';
756 1
            $blacklist[] = 'width';
757 1
            $blacklist[] = 'height';
758 1
            $blacklist[] = 'fileSize';
759
        }
760
761
        // Copy scalars
762 3
        foreach ($this as $property => $value) {
763 3
            if (in_array($property, $blacklist, true)) {
764 3
                continue;
765
            }
766
767 3
            if (is_scalar($value) || $value === null) {
768 3
                $original->$property = $value;
769
            }
770
        }
771
772
        // Copy a few collection and entities
773 3
        $original->artists = clone $this->artists;
774 3
        $original->tags = clone $this->tags;
775 3
        $original->materials = clone $this->materials;
776 3
        $original->periods = clone $this->periods;
777 3
        $original->computeDatings();
778 3
        $original->institution = $this->institution;
779 3
        $original->country = $this->country;
780 3
        $original->documentType = $this->documentType;
781 3
        $original->domain = $this->domain;
782
783
        // Copy file on disk
784 3
        if ($this->filename) {
785 2
            $original->generateUniqueFilename($this->filename);
786 2
            copy($this->getPath(), $original->getPath());
787
        }
788 3
    }
789
790
    /**
791
     * Get related cards
792
     *
793
     * @API\Field(type="Card[]")
794
     */
795 2
    public function getCards(): DoctrineCollection
796
    {
797 2
        return $this->cards;
798
    }
799
800
    /**
801
     * Add related card
802
     *
803
     * @param Card $card
804
     */
805 3
    public function addCard(self $card): void
806
    {
807 3
        if ($card === $this) {
808 1
            throw new InvalidArgumentException('A card cannot be related to itself');
809
        }
810
811 2
        if (!$this->cards->contains($card)) {
812 2
            $this->cards[] = $card;
813
        }
814
815 2
        if (!$card->cards->contains($this)) {
816 2
            $card->cards[] = $this;
817
        }
818 2
    }
819
820
    /**
821
     * Remove related card
822
     *
823
     * @param Card $card
824
     */
825 1
    public function removeCard(self $card): void
826
    {
827 1
        $this->cards->removeElement($card);
828 1
        $card->cards->removeElement($this);
829 1
    }
830
831
    /**
832
     * Return the change this card is a suggestion for, if any
833
     */
834 4
    public function getChange(): ?Change
835
    {
836 4
        return $this->change;
837
    }
838
839
    /**
840
     * Notify the Card that it was added to a Change.
841
     * This should only be called by Change::addCard()
842
     */
843 4
    public function changeAdded(?Change $change): void
844
    {
845 4
        $this->change = $change;
846 4
    }
847
848
    /**
849
     * Set documentSize
850
     */
851 7
    public function setDocumentSize(string $documentSize): void
852
    {
853 7
        $this->documentSize = $documentSize;
854 7
    }
855
856
    /**
857
     * Get documentSize
858
     */
859
    public function getDocumentSize(): string
860
    {
861
        return $this->documentSize;
862
    }
863
864
    /**
865
     * Ensure that the entire hierarchy is added, but also make sure that
866
     * a non-leaf tag is added without one of his leaf.
867
     */
868 10
    private function addEntireHierarchy(DoctrineCollection $collection): void
869
    {
870 10
        $objects = $collection->toArray();
871 10
        $collection->clear();
872
873
        /** @var HasParentInterface $object */
874 10
        foreach ($objects as $object) {
875 3
            if ($object->hasChildren()) {
876 1
                continue;
877
            }
878
879 3
            $collection->add($object);
880
881 3
            foreach ($object->getParentHierarchy() as $parent) {
882 2
                if (!$collection->contains($parent)) {
883 2
                    $collection->add($parent);
884
                }
885
            }
886
        }
887 10
    }
888
}
889