Passed
Pull Request — feature/publishable (#31)
by Vincent
06:01
created

PublishableExtension::applyToCollection()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 21
ccs 0
cts 12
cp 0
rs 9.9
cc 4
nc 4
nop 5
crap 20
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Component Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentBundle\Extension\Doctrine\ORM;
15
16
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\ContextAwareQueryCollectionExtensionInterface;
17
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
18
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
19
use Doctrine\ORM\QueryBuilder;
20
use Doctrine\Persistence\ManagerRegistry;
21
use Silverback\ApiComponentBundle\Annotation\Publishable;
22
use Silverback\ApiComponentBundle\Publishable\PublishableHelper;
23
use Symfony\Component\HttpFoundation\RequestStack;
24
25
/**
26
 * @author Vincent Chalamon <[email protected]>
27
 */
28
final class PublishableExtension implements QueryItemExtensionInterface, ContextAwareQueryCollectionExtensionInterface
29
{
30
    private PublishableHelper $publishableHelper;
31
    private RequestStack $requestStack;
32
    private ManagerRegistry $registry;
33
    private ?Publishable $configuration;
34
35
    public function __construct(PublishableHelper $publishableHelper, RequestStack $requestStack, ManagerRegistry $registry)
36
    {
37
        $this->publishableHelper = $publishableHelper;
38
        $this->requestStack = $requestStack;
39
        $this->registry = $registry;
40
    }
41
42
    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void
43
    {
44
        $configuration = $this->getConfiguration($resourceClass);
45
        if (!$configuration || !($request = $this->requestStack->getCurrentRequest())) {
0 ignored issues
show
Unused Code introduced by
The assignment to $request is dead and can be removed.
Loading history...
46
            return;
47
        }
48
49
        if (!$this->isDraftRequest($context)) {
50
            // User has no access to draft object
51
            $this->updateQueryBuilderForUnauthorizedUsers($queryBuilder, $configuration);
52
53
            return;
54
        }
55
56
        // Reset queryBuilder to prevent an invalid DQL
57
        $queryBuilder->where('1 = 1');
58
        $alias = $queryBuilder->getRootAliases()[0];
59
60
        // (o.publishedResource = :id OR o.id = :id) ORDER BY o.publishedResource IS NULL LIMIT 1
61
        foreach ($identifiers as $identifier) {
62
            $queryBuilder->andWhere(
63
                $queryBuilder->expr()->orX(
64
                    $queryBuilder->expr()->eq("$alias.$configuration->associationName", ":id_$identifier"),
65
                    $queryBuilder->expr()->eq("$alias.$identifier", ":id_$identifier"),
66
                )
67
            )->setParameter("id_$identifier", $identifier);
68
        }
69
70
        $queryBuilder->expr()->asc($queryBuilder->expr()->isNull("$alias.$configuration->associationName"));
71
        $queryBuilder->setMaxResults(1);
72
    }
73
74
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []): void
75
    {
76
        if (!$configuration = $this->getConfiguration($resourceClass)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $configuration is dead and can be removed.
Loading history...
77
            return;
78
        }
79
80
        $configuration = $this->getConfiguration($resourceClass);
81
        if (!$this->isDraftRequest($context)) {
82
            // User has no access to draft object
83
            $this->updateQueryBuilderForUnauthorizedUsers($queryBuilder, $configuration);
84
85
            return;
86
        }
87
88
        $alias = $queryBuilder->getRootAliases()[0];
89
        $identifiers = $this->registry->getManagerForClass($resourceClass)->getClassMetadata($resourceClass)->getIdentifier();
90
91
        // o.id NOT IN (SELECT p.publishedResource FROM {table} t WHERE t.publishedResource IS NOT NULL)
92
        foreach ($identifiers as $identifier) {
93
            $queryBuilder->andWhere(
94
                $queryBuilder->expr()->notIn("$alias.$identifier", $queryBuilder->select("$alias.$configuration->associationName")->getDQL())
95
            );
96
        }
97
    }
98
99
    private function isDraftRequest(array $context): bool
100
    {
101
        return $this->publishableHelper->isGranted() && false === ($context['filters']['published'] ?? false);
102
    }
103
104
    private function updateQueryBuilderForUnauthorizedUsers(QueryBuilder $queryBuilder, Publishable $configuration): void
105
    {
106
        $alias = $queryBuilder->getRootAliases()[0];
107
        $queryBuilder
108
            ->andWhere("$alias.$configuration->fieldName IS NOT NULL")
109
            ->andWhere("$alias.$configuration->fieldName <= :currentTime")
110
            ->setParameter('currentTime', new \DateTimeImmutable());
111
    }
112
113
    private function getConfiguration(string $resourceClass): ?Publishable
114
    {
115
        if (!$this->configuration && ($this->publishableHelper->isPublishable($resourceClass))) {
116
            $this->configuration = $this->publishableHelper->getConfiguration($resourceClass);
117
        }
118
119
        return $this->configuration;
120
    }
121
}
122