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

ShortUrl::matchesCriteria()   B

Complexity

Conditions 11
Paths 6

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 12.1908

Importance

Changes 0
Metric Value
cc 11
eloc 13
c 0
b 0
f 0
nc 6
nop 2
dl 0
loc 20
rs 7.3166
ccs 11
cts 14
cp 0.7856
crap 12.1908

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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