Completed
Push — develop ( f71bd8...4fb2c6 )
by Alejandro
17s queued 14s
created

UrlShortener::shortCodeToUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
crap 2.0185
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\Core\Service;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use Psr\Http\Message\UriInterface;
9
use Shlinkio\Shlink\Core\Domain\Resolver\PersistenceDomainResolver;
10
use Shlinkio\Shlink\Core\Entity\ShortUrl;
11
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
12
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
13
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
14
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
15
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
16
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
17
use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
18
use Throwable;
19
20
use function array_reduce;
21
22
class UrlShortener implements UrlShortenerInterface
23
{
24
    use TagManagerTrait;
25
26
    private EntityManagerInterface $em;
27
    private UrlShortenerOptions $options;
28
    private UrlValidatorInterface $urlValidator;
29
30 14
    public function __construct(
31
        UrlValidatorInterface $urlValidator,
32
        EntityManagerInterface $em,
33
        UrlShortenerOptions $options
34
    ) {
35 14
        $this->urlValidator = $urlValidator;
36 14
        $this->em = $em;
37 14
        $this->options = $options;
38
    }
39
40
    /**
41
     * @param string[] $tags
42
     * @throws NonUniqueSlugException
43
     * @throws InvalidUrlException
44
     * @throws Throwable
45
     */
46 14
    public function urlToShortCode(UriInterface $url, array $tags, ShortUrlMeta $meta): ShortUrl
47
    {
48 14
        $url = (string) $url;
49
50
        // First, check if a short URL exists for all provided params
51 14
        $existingShortUrl = $this->findExistingShortUrlIfExists($url, $tags, $meta);
52 14
        if ($existingShortUrl !== null) {
53 9
            return $existingShortUrl;
54
        }
55
56
        // If the URL validation is enabled, check that the URL actually exists
57 5
        if ($this->options->isUrlValidationEnabled()) {
58 1
            $this->urlValidator->validateUrl($url);
59
        }
60
61 5
        $this->em->beginTransaction();
62 5
        $shortUrl = new ShortUrl($url, $meta, new PersistenceDomainResolver($this->em));
63 5
        $shortUrl->setTags($this->tagNamesToEntities($this->em, $tags));
64
65
        try {
66 5
            $this->verifyShortCodeUniqueness($meta, $shortUrl);
67 4
            $this->em->persist($shortUrl);
68 4
            $this->em->flush();
69 3
            $this->em->commit();
70 2
        } catch (Throwable $e) {
71 2
            if ($this->em->getConnection()->isTransactionActive()) {
72 1
                $this->em->rollback();
73 1
                $this->em->close();
74
            }
75
76 2
            throw $e;
77
        }
78
79 3
        return $shortUrl;
80
    }
81
82 14
    private function findExistingShortUrlIfExists(string $url, array $tags, ShortUrlMeta $meta): ?ShortUrl
83
    {
84 14
        if (! $meta->findIfExists()) {
85 5
            return null;
86
        }
87
88 9
        $criteria = ['longUrl' => $url];
89 9
        if ($meta->hasCustomSlug()) {
90 1
            $criteria['shortCode'] = $meta->getCustomSlug();
91
        }
92
        /** @var ShortUrl[] $shortUrls */
93 9
        $shortUrls = $this->em->getRepository(ShortUrl::class)->findBy($criteria);
94 9
        if (empty($shortUrls)) {
95
            return null;
96
        }
97
98
        // Iterate short URLs until one that matches is found, or return null otherwise
99
        return array_reduce($shortUrls, function (?ShortUrl $found, ShortUrl $shortUrl) use ($tags, $meta) {
100 9
            if ($found !== null) {
101 1
                return $found;
102
            }
103
104 9
            return $shortUrl->matchesCriteria($meta, $tags) ? $shortUrl : null;
105 9
        });
106
    }
107
108 5
    private function verifyShortCodeUniqueness(ShortUrlMeta $meta, ShortUrl $shortUrlToBeCreated): void
109
    {
110 5
        $shortCode = $shortUrlToBeCreated->getShortCode();
111 5
        $domain = $meta->getDomain();
112
113
        /** @var ShortUrlRepository $repo */
114 5
        $repo = $this->em->getRepository(ShortUrl::class);
115 5
        $otherShortUrlsExist = $repo->shortCodeIsInUse($shortCode, $domain);
116
117 5
        if ($otherShortUrlsExist && $meta->hasCustomSlug()) {
118 1
            throw NonUniqueSlugException::fromSlug($shortCode, $domain);
119
        }
120
121 4
        if ($otherShortUrlsExist) {
122 1
            $shortUrlToBeCreated->regenerateShortCode();
123 1
            $this->verifyShortCodeUniqueness($meta, $shortUrlToBeCreated);
124
        }
125
    }
126
}
127