Completed
Push — master ( eb17ea...a61a7d )
by Alejandro
26s queued 11s
created

ShortUrl::resolveDomain()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

189
        if ($meta->hasValidSince() && ! $meta->getValidSince()->eq(/** @scrutinizer ignore-type */ $this->validSince)) {
Loading history...
190
            return false;
191
        }
192 9
        if ($meta->hasValidUntil() && ! $meta->getValidUntil()->eq($this->validUntil)) {
193
            return false;
194
        }
195
196 9
        $shortUrlTags = invoke($this->getTags(), '__toString');
197 9
        $hasAllTags = count($shortUrlTags) === count($tags) && array_reduce(
198 9
            $tags,
199
            function (bool $hasAllTags, string $tag) use ($shortUrlTags) {
200 3
                return $hasAllTags && contains($shortUrlTags, $tag);
201 9
            },
202 9
            true
203
        );
204
205 9
        return $hasAllTags;
206
    }
207
}
208