Passed
Push — develop ( f651b0...33190c )
by Alejandro
09:03
created

ShortUrl::toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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