Completed
Pull Request — develop (#578)
by Alejandro
08:17
created

ShortUrl::getMaxVisits()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\Core\Entity;
6
7
use Cake\Chronos\Chronos;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\Common\Collections\Collection;
10
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
11
use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface;
12
use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver;
13
use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException;
14
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
15
use Zend\Diactoros\Uri;
16
17
use function array_reduce;
18
use function count;
19
use function Functional\contains;
20
use function Functional\invoke;
21
use function Shlinkio\Shlink\Core\generateRandomShortCode;
22
23
class ShortUrl extends AbstractEntity
24
{
25
    private string $longUrl;
26
    private string $shortCode;
27
    private Chronos $dateCreated;
28
    /** @var Collection|Visit[] */
29
    private Collection $visits;
30
    /** @var Collection|Tag[] */
31
    private Collection $tags;
32
    private ?Chronos $validSince;
33
    private ?Chronos $validUntil;
34
    private ?int $maxVisits;
35
    private ?Domain $domain;
36
    private bool $customSlugWasProvided;
37
38 57
    public function __construct(
39
        string $longUrl,
40
        ?ShortUrlMeta $meta = null,
41
        ?DomainResolverInterface $domainResolver = null
42
    ) {
43 57
        $meta = $meta ?? ShortUrlMeta::createEmpty();
44
45 57
        $this->longUrl = $longUrl;
46 57
        $this->dateCreated = Chronos::now();
47 57
        $this->visits = new ArrayCollection();
48 57
        $this->tags = new ArrayCollection();
49 57
        $this->validSince = $meta->getValidSince();
50 57
        $this->validUntil = $meta->getValidUntil();
51 57
        $this->maxVisits = $meta->getMaxVisits();
52 57
        $this->customSlugWasProvided = $meta->hasCustomSlug();
53 57
        $this->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode();
54 57
        $this->domain = ($domainResolver ?? new SimpleDomainResolver())->resolveDomain($meta->getDomain());
55
    }
56
57 24
    public function getLongUrl(): string
58
    {
59 24
        return $this->longUrl;
60
    }
61
62 23
    public function getShortCode(): string
63
    {
64 23
        return $this->shortCode;
65
    }
66
67 12
    public function getDateCreated(): Chronos
68
    {
69 12
        return $this->dateCreated;
70
    }
71
72
    /**
73
     * @return Collection|Tag[]
74
     */
75 22
    public function getTags(): Collection
76
    {
77 22
        return $this->tags;
78
    }
79
80
    /**
81
     * @param Collection|Tag[] $tags
82
     */
83 6
    public function setTags(Collection $tags): self
84
    {
85 6
        $this->tags = $tags;
86 6
        return $this;
87
    }
88
89 1
    public function updateMeta(ShortUrlMeta $shortCodeMeta): void
90
    {
91 1
        if ($shortCodeMeta->hasValidSince()) {
92 1
            $this->validSince = $shortCodeMeta->getValidSince();
93
        }
94 1
        if ($shortCodeMeta->hasValidUntil()) {
95 1
            $this->validUntil = $shortCodeMeta->getValidUntil();
96
        }
97 1
        if ($shortCodeMeta->hasMaxVisits()) {
98 1
            $this->maxVisits = $shortCodeMeta->getMaxVisits();
99
        }
100
    }
101
102
    /**
103
     * @throws ShortCodeCannotBeRegeneratedException
104
     */
105 4
    public function regenerateShortCode(): self
106
    {
107
        // In ShortUrls where a custom slug was provided, do nothing
108 4
        if ($this->customSlugWasProvided) {
109 1
            throw ShortCodeCannotBeRegeneratedException::forShortUrlWithCustomSlug();
110
        }
111
112
        // The short code can be regenerated only on ShortUrl which have not been persisted yet
113 3
        if ($this->id !== null) {
114 1
            throw ShortCodeCannotBeRegeneratedException::forShortUrlAlreadyPersisted();
115
        }
116
117 2
        $this->shortCode = generateRandomShortCode();
118 2
        return $this;
119
    }
120
121 13
    public function getValidSince(): ?Chronos
122
    {
123 13
        return $this->validSince;
124
    }
125
126 13
    public function getValidUntil(): ?Chronos
127
    {
128 13
        return $this->validUntil;
129
    }
130
131 14
    public function getVisitsCount(): int
132
    {
133 14
        return count($this->visits);
134
    }
135
136
    /**
137
     * @param Collection|Visit[] $visits
138
     * @return ShortUrl
139
     * @internal
140
     */
141 4
    public function setVisits(Collection $visits): self
142
    {
143 4
        $this->visits = $visits;
144 4
        return $this;
145
    }
146
147 13
    public function getMaxVisits(): ?int
148
    {
149 13
        return $this->maxVisits;
150
    }
151
152
    public function maxVisitsReached(): bool
153
    {
154
        return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits;
155
    }
156
157 14
    public function toString(array $domainConfig): string
158
    {
159 14
        return (string) (new Uri())->withPath($this->shortCode)
160 14
                                   ->withScheme($domainConfig['schema'] ?? 'http')
161 14
                                   ->withHost($this->resolveDomain($domainConfig['hostname'] ?? ''));
162
    }
163
164 15
    private function resolveDomain(string $fallback = ''): string
165
    {
166 15
        if ($this->domain === null) {
167 14
            return $fallback;
168
        }
169
170 1
        return $this->domain->getAuthority();
171
    }
172
173 9
    public function matchesCriteria(ShortUrlMeta $meta, array $tags): bool
174
    {
175 9
        if ($meta->hasMaxVisits() && $meta->getMaxVisits() !== $this->maxVisits) {
176 1
            return false;
177
        }
178 9
        if ($meta->hasDomain() && $meta->getDomain() !== $this->resolveDomain()) {
179
            return false;
180
        }
181 9
        if ($meta->hasValidSince() && ! $meta->getValidSince()->eq($this->validSince)) {
0 ignored issues
show
Bug introduced by
It seems like $this->validSince can also be of type null; however, parameter $dt of Cake\Chronos\Chronos::eq() does only seem to accept Cake\Chronos\ChronosInterface, 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

181
        if ($meta->hasValidSince() && ! $meta->getValidSince()->eq(/** @scrutinizer ignore-type */ $this->validSince)) {
Loading history...
182
            return false;
183
        }
184 9
        if ($meta->hasValidUntil() && ! $meta->getValidUntil()->eq($this->validUntil)) {
185
            return false;
186
        }
187
188 9
        $shortUrlTags = invoke($this->getTags(), '__toString');
189 9
        $hasAllTags = count($shortUrlTags) === count($tags) && array_reduce(
190 9
            $tags,
191 9
            fn (bool $hasAllTags, string $tag) => $hasAllTags && contains($shortUrlTags, $tag),
192 9
            true
193
        );
194
195 9
        return $hasAllTags;
196
    }
197
}
198