PublishableExtension   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 54
dl 0
loc 110
ccs 0
cts 56
cp 0
rs 10
c 3
b 0
f 0
wmc 17

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfiguration() 0 7 2
A applyToCollection() 0 20 4
B applyToItem() 0 39 6
A isDraftRequest() 0 3 2
A __construct() 0 5 1
A updateQueryBuilderForUnauthorizedUsers() 0 7 1
A getDQL() 0 10 1
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components 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\ApiComponentsBundle\Doctrine\Extension\ORM;
15
16
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
17
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
18
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
19
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
20
use ApiPlatform\Metadata\Operation;
21
use Doctrine\ORM\EntityRepository;
22
use Doctrine\ORM\QueryBuilder;
23
use Doctrine\Persistence\ManagerRegistry;
24
use Silverback\ApiComponentsBundle\Annotation\Publishable;
25
use Silverback\ApiComponentsBundle\Helper\Publishable\PublishableStatusChecker;
26
use Symfony\Component\HttpFoundation\RequestStack;
27
28
/**
29
 * @author Vincent Chalamon <[email protected]>
30
 */
31
final class PublishableExtension implements QueryItemExtensionInterface, QueryCollectionExtensionInterface
32
{
33
    private PublishableStatusChecker $publishableStatusChecker;
34
    private RequestStack $requestStack;
35
    private ManagerRegistry $registry;
36
37
    public function __construct(PublishableStatusChecker $publishableStatusChecker, RequestStack $requestStack, ManagerRegistry $registry)
38
    {
39
        $this->publishableStatusChecker = $publishableStatusChecker;
40
        $this->requestStack = $requestStack;
41
        $this->registry = $registry;
42
    }
43
44
    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void
45
    {
46
        $configuration = $this->getConfiguration($resourceClass);
47
48
        if (!$configuration || !$this->requestStack->getCurrentRequest()) {
49
            return;
50
        }
51
52
        if (!$this->isDraftRequest($resourceClass, $context)) {
53
            // User has no access to draft object
54
            $this->updateQueryBuilderForUnauthorizedUsers($queryBuilder, $configuration);
55
56
            return;
57
        }
58
59
        $alias = $queryBuilder->getRootAliases()[0];
60
        $classMetadata = $this->registry->getManagerForClass($resourceClass)->getClassMetadata($resourceClass);
61
62
        // (o.publishedResource = :id OR o.id = :id) ORDER BY o.publishedResource IS NULL LIMIT 1
63
        $criteriaReset = false;
64
        $altQueryNameGenerator = new QueryNameGenerator();
65
        foreach ($identifiers as $identifier => $value) {
66
            $placeholder = $altQueryNameGenerator->generateParameterName($identifier);
67
68
            $predicates = $queryBuilder->expr()->orX($queryBuilder->expr()->eq("$alias.$configuration->associationName", ":$placeholder"), $queryBuilder->expr()->eq("$alias.$identifier", ":$placeholder"));
69
70
            // Reset queryBuilder to prevent an invalid DQL
71
            if (!$criteriaReset) {
72
                $queryBuilder->where($predicates);
73
                $criteriaReset = true;
74
            } else {
75
                $queryBuilder->andWhere($predicates);
76
            }
77
            $queryBuilder->setParameter($placeholder, $value, $classMetadata->getTypeOfField($identifier));
78
        }
79
80
        $queryBuilder->addSelect("CASE WHEN $alias.$configuration->associationName IS NULL THEN 1 ELSE 0 END AS HIDDEN assocNameSort");
81
        $queryBuilder->orderBy('assocNameSort', 'ASC');
82
        $queryBuilder->setMaxResults(1);
83
    }
84
85
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
86
    {
87
        if (!$configuration = $this->getConfiguration($resourceClass)) {
88
            return;
89
        }
90
91
        if (!$this->isDraftRequest($resourceClass, $context)) {
92
            // User has no access to draft object
93
            $this->updateQueryBuilderForUnauthorizedUsers($queryBuilder, $configuration);
94
95
            return;
96
        }
97
98
        $alias = $queryBuilder->getRootAliases()[0];
99
        $identifiers = $this->registry->getManagerForClass($resourceClass)->getClassMetadata($resourceClass)->getIdentifier();
100
        $dql = $this->getDQL($configuration, $resourceClass);
101
102
        // o.id NOT IN (SELECT p.publishedResource FROM {table} t WHERE t.publishedResource IS NOT NULL)
103
        foreach ($identifiers as $identifier) {
104
            $queryBuilder->andWhere($queryBuilder->expr()->notIn("$alias.$identifier", $dql));
105
        }
106
    }
107
108
    private function getDQL(Publishable $configuration, string $resourceClass): string
109
    {
110
        /** @var EntityRepository $repository */
111
        $repository = $this->registry->getManagerForClass($resourceClass)->getRepository($resourceClass);
112
        $queryBuilder = $repository->createQueryBuilder('o2');
113
114
        return $queryBuilder
115
            ->select("IDENTITY(o2.$configuration->associationName)")
116
            ->where($queryBuilder->expr()->isNotNull("o2.$configuration->associationName"))
117
            ->getDQL();
118
    }
119
120
    private function isDraftRequest(string $resourceClass, array $context): bool
121
    {
122
        return $this->publishableStatusChecker->isGranted($resourceClass) && false === ($context['filters']['published'] ?? false);
123
    }
124
125
    private function updateQueryBuilderForUnauthorizedUsers(QueryBuilder $queryBuilder, Publishable $configuration): void
126
    {
127
        $alias = $queryBuilder->getRootAliases()[0];
128
        $queryBuilder
129
            ->andWhere("$alias.$configuration->fieldName IS NOT NULL")
130
            ->andWhere("$alias.$configuration->fieldName <= :currentTime")
131
            ->setParameter('currentTime', new \DateTimeImmutable());
132
    }
133
134
    private function getConfiguration(string $resourceClass): ?Publishable
135
    {
136
        if (!$this->publishableStatusChecker->getAttributeReader()->isConfigured($resourceClass)) {
137
            return null;
138
        }
139
140
        return $this->publishableStatusChecker->getAttributeReader()->getConfiguration($resourceClass);
141
    }
142
}
143