Passed
Push — main ( 16cc9f...4d0cff )
by Daniel
09:49 queued 04:29
created

MercureAuthorization::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 10
ccs 0
cts 1
cp 0
crap 2
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Silverback\ApiComponentsBundle\Mercure;
4
5
use ApiPlatform\Exception\OperationNotFoundException;
6
use ApiPlatform\Metadata\HttpOperation;
7
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
8
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
9
use Silverback\ApiComponentsBundle\Annotation\Publishable;
10
use Silverback\ApiComponentsBundle\Helper\Publishable\PublishableStatusChecker;
11
use Symfony\Component\HttpFoundation\Cookie;
12
use Symfony\Component\HttpFoundation\RequestStack;
13
use Symfony\Component\Mercure\Authorization;
14
use Symfony\Component\Routing\RequestContext;
15
16
class MercureAuthorization
17
{
18
    public function __construct(
19
        private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory,
20
        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
21
        private readonly PublishableStatusChecker $publishableStatusChecker,
22
        private readonly RequestContext $requestContext,
23
        private readonly Authorization $mercureAuthorization,
24
        private readonly RequestStack $requestStack,
25
        private readonly string $cookieSameSite = Cookie::SAMESITE_STRICT,
26
        private readonly ?string $hubName = null
27
    ) {
28
    }
29
30
    public function getAuthorizationCookie(): Cookie
31
    {
32
        $subscribeTopics = $this->getSubscribeTopics();
33
        // Todo: await merge of https://github.com/symfony/mercure/pull/93 to remove ability to publish any updates and set to  null
34
        // May also be able to await a mercure bundle update to set the cookie samesite in mercure configs
35
        $cookie = $this->mercureAuthorization->createCookie($this->requestStack->getCurrentRequest(), $subscribeTopics, [], [], $this->hubName);
0 ignored issues
show
Bug introduced by
It seems like $this->requestStack->getCurrentRequest() can also be of type null; however, parameter $request of Symfony\Component\Mercur...ization::createCookie() does only seem to accept Symfony\Component\HttpFoundation\Request, 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

35
        $cookie = $this->mercureAuthorization->createCookie(/** @scrutinizer ignore-type */ $this->requestStack->getCurrentRequest(), $subscribeTopics, [], [], $this->hubName);
Loading history...
36
        return $cookie
37
            ->withSameSite($this->cookieSameSite)
38
            ->withExpires(time() + (10 * 365 * 24 * 60 * 60));
39
    }
40
41
    public function getClearAuthorizationCookie(): Cookie
42
    {
43
        return $this->getAuthorizationCookie()->withExpires(1);
44
    }
45
46
    public function getSubscribeTopics(): array
47
    {
48
        $subscribeIris = [];
49
        foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
50
            if ($resourceIris = $this->getSubscribeIrisForResource($resourceClass)) {
51
                $subscribeIris[] = $resourceIris;
52
            }
53
        }
54
        return array_merge([], ...$subscribeIris);
55
    }
56
57
    private function getSubscribeIrisForResource(string $resourceClass): ?array
58
    {
59
        $operation = $this->getMercureResourceOperation($resourceClass);
60
        if (!$operation) {
61
            return null;
62
        }
63
64
        $refl = new \ReflectionClass($operation->getClass());
65
        $isPublishable = \count($refl->getAttributes(Publishable::class));
66
67
        $uriTemplate = $this->buildAbsoluteUriTemplate() . $operation->getRoutePrefix() . $operation->getUriTemplate();
68
        $subscribeIris = [$uriTemplate];
69
70
        if (!$isPublishable) {
71
            return $subscribeIris;
72
        }
73
74
        // Note that `?draft=1` is also hard coded into the PublishableIriConverter, probably make this configurable somewhere
75
        if ($this->publishableStatusChecker->isGranted($operation->getClass())) {
76
            $subscribeIris[] = $uriTemplate . '?draft=1';
77
        }
78
79
        return $subscribeIris;
80
    }
81
82
    private function getMercureResourceOperation(string $resourceClass): ?HttpOperation
83
    {
84
        $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
85
86
        try {
87
            $operation = $resourceMetadataCollection->getOperation(forceCollection: false, httpOperation: true);
88
        } catch (OperationNotFoundException $e) {
89
            return null;
90
        }
91
92
        if (!$operation instanceof HttpOperation) {
93
            return null;
94
        }
95
96
        $mercure = $operation->getMercure();
97
98
        if (!$mercure) {
99
            return null;
100
        }
101
102
        return $operation;
103
    }
104
105
    /**
106
     * Mercure subscribe iris should be absolute
107
     * this code can also be found in Symfony's URL Generator
108
     * but as we work without a symfony route here (and we would not want to do this as its not spec-compliant)
109
     * we do it by hand.
110
     */
111
    private function buildAbsoluteUriTemplate(): string
112
    {
113
        $scheme = $this->requestContext->getScheme();
114
        $host = $this->requestContext->getHost();
115
        $port = $this->requestContext->isSecure() ? $this->requestContext->getHttpsPort() : $this->requestContext->getHttpPort();
116
117
        if (80 !== $port || 443 !== $port) {
118
            return sprintf('%s://%s:%d', $scheme, $host, $port);
119
        }
120
121
        return sprintf('%s://%s', $scheme, $host);
122
    }
123
}
124