Passed
Pull Request — master (#695)
by Alejandro
05:41
created

ShortUrl::update()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

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

207
        if ($meta->hasValidSince() && ! $meta->getValidSince()->eq(/** @scrutinizer ignore-type */ $this->validSince)) {
Loading history...
208
            return false;
209
        }
210 9
        if ($meta->hasValidUntil() && ! $meta->getValidUntil()->eq($this->validUntil)) {
211
            return false;
212
        }
213
214 9
        $shortUrlTags = invoke($this->getTags(), '__toString');
215 9
        return count($shortUrlTags) === count($tags) && array_reduce(
216 9
            $tags,
217 9
            fn (bool $hasAllTags, string $tag) => $hasAllTags && contains($shortUrlTags, $tag),
218 9
            true,
219
        );
220
    }
221
}
222