Passed
Pull Request — master (#1138)
by Tarmo
07:31
created

BaseRepository::addCallback()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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