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

PublishableExtension::isDraftRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 2
nc 2
nop 1
crap 6
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\EntityRepository;
20
use Doctrine\ORM\QueryBuilder;
21
use Doctrine\Persistence\ManagerRegistry;
22
use Silverback\ApiComponentBundle\Annotation\Publishable;
23
use Silverback\ApiComponentBundle\Publishable\PublishableHelper;
24
use Symfony\Component\HttpFoundation\RequestStack;
25
26
/**
27
 * @author Vincent Chalamon <[email protected]>
28
 */
29
final class PublishableExtension implements QueryItemExtensionInterface, ContextAwareQueryCollectionExtensionInterface
30
{
31
    private PublishableHelper $publishableHelper;
32
    private RequestStack $requestStack;
33
    private ManagerRegistry $registry;
34
    private ?Publishable $configuration;
35
36
    public function __construct(PublishableHelper $publishableHelper, RequestStack $requestStack, ManagerRegistry $registry)
37
    {
38
        $this->publishableHelper = $publishableHelper;
39
        $this->requestStack = $requestStack;
40
        $this->registry = $registry;
41
    }
42
43
    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void
44
    {
45
        $configuration = $this->getConfiguration($resourceClass);
46
        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...
47
            return;
48
        }
49
50
        if (!$this->isDraftRequest($context)) {
51
            // User has no access to draft object
52
            $this->updateQueryBuilderForUnauthorizedUsers($queryBuilder, $configuration);
53
54
            return;
55
        }
56
57
        // Reset queryBuilder to prevent an invalid DQL
58
        $queryBuilder->where('1 = 1');
59
        $alias = $queryBuilder->getRootAliases()[0];
60
61
        // (o.publishedResource = :id OR o.id = :id) ORDER BY o.publishedResource IS NULL LIMIT 1
62
        foreach ($identifiers as $identifier) {
63
            $queryBuilder->andWhere(
64
                $queryBuilder->expr()->orX(
65
                    $queryBuilder->expr()->eq("$alias.$configuration->associationName", ":id_$identifier"),
66
                    $queryBuilder->expr()->eq("$alias.$identifier", ":id_$identifier"),
67
                )
68
            )->setParameter("id_$identifier", $identifier);
69
        }
70
71
        $queryBuilder->expr()->asc($queryBuilder->expr()->isNull("$alias.$configuration->associationName"));
72
        $queryBuilder->setMaxResults(1);
73
    }
74
75
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []): void
76
    {
77
        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...
78
            return;
79
        }
80
81
        $configuration = $this->getConfiguration($resourceClass);
82
        if (!$this->isDraftRequest($context)) {
83
            // User has no access to draft object
84
            $this->updateQueryBuilderForUnauthorizedUsers($queryBuilder, $configuration);
85
86
            return;
87
        }
88
89
        $alias = $queryBuilder->getRootAliases()[0];
90
        $identifiers = $this->registry->getManagerForClass($resourceClass)->getClassMetadata($resourceClass)->getIdentifier();
91
        $dql = $this->getDQL($configuration, $resourceClass);
92
93
        // o.id NOT IN (SELECT p.publishedResource FROM {table} t WHERE t.publishedResource IS NOT NULL)
94
        foreach ($identifiers as $identifier) {
95
            $queryBuilder->andWhere($queryBuilder->expr()->notIn("$alias.$identifier", $dql));
96
        }
97
    }
98
99
    private function getDQL(Publishable $configuration, string $resourceClass): string
100
    {
101
        /** @var EntityRepository $repository */
102
        $repository = $this->registry->getManagerForClass($resourceClass)->getRepository($resourceClass);
103
        $queryBuilder = $repository->createQueryBuilder('o');
104
105
        return $queryBuilder
106
            ->select("o.$configuration->associationName")
107
            ->where($queryBuilder->expr()->isNotNull("o.$configuration->associationName"))
108
            ->getDQL();
109
    }
110
111
    private function isDraftRequest(array $context): bool
112
    {
113
        return $this->publishableHelper->isGranted() && false === ($context['filters']['published'] ?? false);
114
    }
115
116
    private function updateQueryBuilderForUnauthorizedUsers(QueryBuilder $queryBuilder, Publishable $configuration): void
117
    {
118
        $alias = $queryBuilder->getRootAliases()[0];
119
        $queryBuilder
120
            ->andWhere("$alias.$configuration->fieldName IS NOT NULL")
121
            ->andWhere("$alias.$configuration->fieldName <= :currentTime")
122
            ->setParameter('currentTime', new \DateTimeImmutable());
123
    }
124
125
    private function getConfiguration(string $resourceClass): ?Publishable
126
    {
127
        if (!$this->configuration && ($this->publishableHelper->isPublishable($resourceClass))) {
128
            $this->configuration = $this->publishableHelper->getConfiguration($resourceClass);
129
        }
130
131
        return $this->configuration;
132
    }
133
}
134