Completed
Push — develop ( a74e1d...e4f01e )
by Alejandro
28s queued 14s
created

UrlShortener   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 96
Duplicated Lines 0 %

Test Coverage

Coverage 97.78%

Importance

Changes 0
Metric Value
wmc 15
eloc 46
c 0
b 0
f 0
dl 0
loc 96
rs 10
ccs 44
cts 45
cp 0.9778

4 Methods

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