BaseRepository   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 19
eloc 59
dl 0
loc 188
ccs 56
cts 56
cp 1
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A processCallbacks() 0 9 2
A addInnerJoin() 0 7 2
A getSearchColumns() 0 3 1
A getEntityName() 0 3 1
A addLeftJoin() 0 7 2
A addJoinToQuery() 0 8 2
A processJoins() 0 9 2
A save() 0 12 2
A addCallback() 0 11 2
A processQueryBuilder() 0 11 1
A remove() 0 12 2
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Repository/BaseRepository.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\Repository;
10
11
use App\Entity\Interfaces\EntityInterface;
12
use App\Repository\Interfaces\BaseRepositoryInterface;
13
use App\Repository\Traits\RepositoryMethodsTrait;
14
use App\Repository\Traits\RepositoryWrappersTrait;
15
use Doctrine\ORM\EntityManager;
16
use Doctrine\ORM\QueryBuilder;
17
use Doctrine\Persistence\ManagerRegistry;
18
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
19
use function array_map;
20
use function array_unshift;
21
use function implode;
22
use function in_array;
23
use function serialize;
24
use function sha1;
25
use function spl_object_hash;
26
27
/**
28
 * Class BaseRepository
29
 *
30
 * @package App\Repository
31
 * @author TLe, Tarmo Leppänen <[email protected]>
32
 */
33
#[AutoconfigureTag('app.rest.repository')]
34
#[AutoconfigureTag('app.stopwatch')]
35
abstract class BaseRepository implements BaseRepositoryInterface
36
{
37
    use RepositoryMethodsTrait;
38
    use RepositoryWrappersTrait;
39
40
    private const INNER_JOIN = 'innerJoin';
41
    private const LEFT_JOIN = 'leftJoin';
42
43
    /**
44
     * @psalm-var class-string
45
     */
46
    protected static string $entityName;
47
48
    /**
49
     * @var array<int, string>
50
     */
51
    protected static array $searchColumns = [];
52
    protected static EntityManager $entityManager;
53
54
    protected ManagerRegistry $managerRegistry;
55
56
    /**
57
     * Joins that need to attach to queries, this is needed for to prevent duplicate joins on those.
58
     *
59
     * @var array<string, array<array<int, scalar>>>
60
     */
61
    private static array $joins = [
62
        self::INNER_JOIN => [],
63
        self::LEFT_JOIN => [],
64
    ];
65
66
    /**
67
     * @var array<string, array<int, string>>
68
     */
69
    private static array $processedJoins = [
70
        self::INNER_JOIN => [],
71
        self::LEFT_JOIN => [],
72
    ];
73
74
    /**
75
     * @var array<int, array{0: callable, 1: array<mixed>}>
76
     */
77
    private static array $callbacks = [];
78
79
    /**
80
     * @var array<int, string>
81
     */
82
    private static array $processedCallbacks = [];
83
84
    /**
85
     * @psalm-return class-string
86
     */
87 705
    public function getEntityName(): string
88
    {
89 705
        return static::$entityName;
90
    }
91
92 148
    public function getSearchColumns(): array
93
    {
94 148
        return static::$searchColumns;
95
    }
96
97 546
    public function save(EntityInterface $entity, ?bool $flush = null): self
98
    {
99 546
        $flush ??= true;
100
101
        // Persist on database
102 546
        $this->getEntityManager()->persist($entity);
103
104 546
        if ($flush) {
105 546
            $this->getEntityManager()->flush();
106
        }
107
108 544
        return $this;
109
    }
110
111 13
    public function remove(EntityInterface $entity, ?bool $flush = null): self
112
    {
113 13
        $flush ??= true;
114
115
        // Remove from database
116 13
        $this->getEntityManager()->remove($entity);
117
118 13
        if ($flush) {
119 13
            $this->getEntityManager()->flush();
120
        }
121
122 13
        return $this;
123
    }
124
125 148
    public function processQueryBuilder(QueryBuilder $queryBuilder): void
126
    {
127
        // Reset processed joins and callbacks
128 148
        self::$processedJoins = [
129 148
            self::INNER_JOIN => [],
130 148
            self::LEFT_JOIN => [],
131 148
        ];
132 148
        self::$processedCallbacks = [];
133
134 148
        $this->processJoins($queryBuilder);
135 148
        $this->processCallbacks($queryBuilder);
136
    }
137
138 6
    public function addLeftJoin(array $parameters): self
139
    {
140 6
        if ($parameters !== []) {
141 4
            $this->addJoinToQuery(self::LEFT_JOIN, $parameters);
142
        }
143
144 6
        return $this;
145
    }
146
147 6
    public function addInnerJoin(array $parameters): self
148
    {
149 6
        if ($parameters !== []) {
150 4
            $this->addJoinToQuery(self::INNER_JOIN, $parameters);
151
        }
152
153 6
        return $this;
154
    }
155
156 2
    public function addCallback(callable $callable, ?array $args = null): self
157
    {
158 2
        $args ??= [];
159 2
        $hash = sha1(serialize([spl_object_hash((object)$callable), ...$args]));
160
161 2
        if (!in_array($hash, self::$processedCallbacks, true)) {
162 2
            self::$callbacks[] = [$callable, $args];
163 2
            self::$processedCallbacks[] = $hash;
164
        }
165
166 2
        return $this;
167
    }
168
169
    /**
170
     * Process defined joins for current QueryBuilder instance.
171
     */
172 148
    protected function processJoins(QueryBuilder $queryBuilder): void
173
    {
174 148
        foreach (self::$joins as $joinType => $joins) {
175 148
            array_map(
176 148
                static fn (array $joinParameters): QueryBuilder => $queryBuilder->{$joinType}(...$joinParameters),
177 148
                $joins,
178 148
            );
179
180 148
            self::$joins[$joinType] = [];
181
        }
182
    }
183
184
    /**
185
     * Process defined callbacks for current QueryBuilder instance.
186
     */
187 148
    protected function processCallbacks(QueryBuilder $queryBuilder): void
188
    {
189 148
        foreach (self::$callbacks as [$callback, $args]) {
190 2
            array_unshift($args, $queryBuilder);
191
192 2
            $callback(...$args);
193
        }
194
195 148
        self::$callbacks = [];
196
    }
197
198
    /**
199
     * Method to add defined join(s) to current QueryBuilder query. This will
200
     * keep track of attached join(s) so any of those are not added multiple
201
     * times to QueryBuilder.
202
     *
203
     * @note processJoins() method must be called for joins to actually be
204
     *       added to QueryBuilder. processQueryBuilder() method calls this
205
     *       method automatically.
206
     *
207
     * @see QueryBuilder::leftJoin()
208
     * @see QueryBuilder::innerJoin()
209
     *
210
     * @param string $type Join type; leftJoin, innerJoin or join
211
     * @param array<int, scalar> $parameters Query builder join parameters
212
     */
213 8
    private function addJoinToQuery(string $type, array $parameters): void
214
    {
215 8
        $comparison = implode('|', $parameters);
216
217 8
        if (!in_array($comparison, self::$processedJoins[$type], true)) {
218 8
            self::$joins[$type][] = $parameters;
219
220 8
            self::$processedJoins[$type][] = $comparison;
221
        }
222
    }
223
}
224