• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemsdk / docker-symfony-api / #172

23 Aug 2026 11:37AM UTC coverage: 59.508% (+0.08%) from 59.433%
#172

push

DKravtsov
Symfony 8.1, MySQL 9.7, Nginx 1.31, Redis 8.10, updated composer dependencies, refactoring.

22 of 30 new or added lines in 10 files covered. (73.33%)

2 existing lines in 2 files now uncovered.

1646 of 2766 relevant lines covered (59.51%)

25.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.57
/src/General/Infrastructure/Repository/BaseRepository.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\General\Infrastructure\Repository;
6

7
use App\General\Domain\Entity\Interfaces\EntityInterface;
8
use App\General\Domain\Repository\Interfaces\BaseRepositoryInterface;
9
use App\General\Infrastructure\Repository\Traits\RepositoryMethodsTrait;
10
use App\General\Infrastructure\Repository\Traits\RepositoryWrappersTrait;
11
use Doctrine\ORM\EntityManager;
12
use Doctrine\ORM\QueryBuilder;
13
use Doctrine\Persistence\ManagerRegistry;
14
use Override;
15
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
16

17
use function array_map;
18
use function array_unshift;
19
use function implode;
20
use function in_array;
21
use function serialize;
22
use function sha1;
23
use function spl_object_hash;
24

25
/**
26
 * @package App\General
27
 */
28
#[AutoconfigureTag('app.rest.repository')]
29
#[AutoconfigureTag('app.stopwatch')]
30
abstract class BaseRepository implements BaseRepositoryInterface
31
{
32
    use RepositoryMethodsTrait;
33
    use RepositoryWrappersTrait;
34

35
    private const string INNER_JOIN = 'innerJoin';
36
    private const string LEFT_JOIN = 'leftJoin';
37

38
    /**
39
     * @psalm-var class-string
40
     */
41
    protected static string $entityName;
42

43
    /**
44
     * @var array<int, string>
45
     */
46
    protected static array $searchColumns = [];
47
    protected static EntityManager $entityManager;
48
    protected ManagerRegistry $managerRegistry;
49

50
    /**
51
     * Joins that need to attach to queries, this is needed for to prevent duplicate joins on those.
52
     *
53
     * @var array<string, array<array<int, scalar>>>
54
     */
55
    private static array $joins = [
56
        self::INNER_JOIN => [],
57
        self::LEFT_JOIN => [],
58
    ];
59

60
    /**
61
     * @var array<string, array<int, string>>
62
     */
63
    private static array $processedJoins = [
64
        self::INNER_JOIN => [],
65
        self::LEFT_JOIN => [],
66
    ];
67

68
    /**
69
     * @var array<int, array{0: callable, 1: array<mixed>}>
70
     */
71
    private static array $callbacks = [];
72

73
    /**
74
     * @var array<int, string>
75
     */
76
    private static array $processedCallbacks = [];
77

78
    /**
79
     * {@inheritDoc}
80
     *
81
     * @psalm-return class-string
82
     */
83
    #[Override]
84
    public function getEntityName(): string
85
    {
86
        return static::$entityName;
175✔
87
    }
88

89
    /**
90
     * {@inheritDoc}
91
     */
92
    #[Override]
93
    public function getSearchColumns(): array
94
    {
95
        return static::$searchColumns;
79✔
96
    }
97

98
    /**
99
     * {@inheritDoc}
100
     */
101
    #[Override]
102
    public function save(EntityInterface $entity, ?bool $flush = null, ?string $entityManagerName = null): self
103
    {
104
        $flush ??= true;
172✔
105
        // Persist on database
106
        $this->getEntityManager($entityManagerName)->persist($entity);
172✔
107

108
        if ($flush) {
172✔
109
            $this->getEntityManager($entityManagerName)->flush();
172✔
110
        }
111

112
        return $this;
172✔
113
    }
114

115
    /**
116
     * {@inheritDoc}
117
     */
118
    #[Override]
119
    public function remove(EntityInterface $entity, ?bool $flush = null, ?string $entityManagerName = null): self
120
    {
121
        $flush ??= true;
4✔
122
        // Remove from database
123
        $this->getEntityManager($entityManagerName)->remove($entity);
4✔
124

125
        if ($flush) {
4✔
126
            $this->getEntityManager($entityManagerName)->flush();
4✔
127
        }
128

129
        return $this;
4✔
130
    }
131

132
    /**
133
     * {@inheritDoc}
134
     */
135
    #[Override]
136
    public function processQueryBuilder(QueryBuilder $queryBuilder): void
137
    {
138
        // Reset processed joins and callbacks
139
        self::$processedJoins = [
79✔
140
            self::INNER_JOIN => [],
79✔
141
            self::LEFT_JOIN => [],
79✔
142
        ];
79✔
143
        self::$processedCallbacks = [];
79✔
144
        $this->processJoins($queryBuilder);
79✔
145
        $this->processCallbacks($queryBuilder);
79✔
146
    }
147

148
    /**
149
     * {@inheritDoc}
150
     */
151
    #[Override]
152
    public function addLeftJoin(array $parameters): self
153
    {
154
        if ($parameters !== []) {
×
155
            $this->addJoinToQuery(self::LEFT_JOIN, $parameters);
×
156
        }
157

158
        return $this;
×
159
    }
160

161
    /**
162
     * {@inheritDoc}
163
     */
164
    #[Override]
165
    public function addInnerJoin(array $parameters): self
166
    {
167
        if ($parameters !== []) {
×
168
            $this->addJoinToQuery(self::INNER_JOIN, $parameters);
×
169
        }
170

171
        return $this;
×
172
    }
173

174
    /**
175
     * {@inheritDoc}
176
     */
177
    #[Override]
178
    public function addCallback(callable $callable, ?array $args = null): self
179
    {
180
        $args ??= [];
×
181
        $hash = sha1(serialize([spl_object_hash((object)$callable), ...$args]));
×
182

183
        if (!in_array($hash, self::$processedCallbacks, true)) {
×
184
            self::$callbacks[] = [$callable, $args];
×
185
            self::$processedCallbacks[] = $hash;
×
186
        }
187

188
        return $this;
×
189
    }
190

191
    /**
192
     * Process defined joins for current QueryBuilder instance.
193
     */
194
    protected function processJoins(QueryBuilder $queryBuilder): void
195
    {
196
        foreach (self::$joins as $joinType => $joins) {
79✔
197
            array_map(
79✔
198
                static function (array $joinParameters) use ($queryBuilder, $joinType): QueryBuilder {
79✔
NEW
199
                    return $queryBuilder->{$joinType}(...$joinParameters);
×
200
                },
79✔
201
                $joins,
79✔
202
            );
79✔
203

204
            self::$joins[$joinType] = [];
79✔
205
        }
206
    }
207

208
    /**
209
     * Process defined callbacks for current QueryBuilder instance.
210
     */
211
    protected function processCallbacks(QueryBuilder $queryBuilder): void
212
    {
213
        foreach (self::$callbacks as [$callback, $args]) {
79✔
214
            array_unshift($args, $queryBuilder);
×
215
            $callback(...$args);
×
216
        }
217

218
        self::$callbacks = [];
79✔
219
    }
220

221
    /**
222
     * Method to add defined join(s) to current QueryBuilder query. This will keep track of attached join(s) so any of
223
     * those are not added multiple times to QueryBuilder.
224
     *
225
     * @note processJoins() method must be called for joins to actually be added to QueryBuilder. processQueryBuilder()
226
     *       method calls this method automatically.
227
     *
228
     * @see QueryBuilder::leftJoin()
229
     * @see QueryBuilder::innerJoin()
230
     *
231
     * @param string $type Join type; leftJoin, innerJoin or join
232
     * @param array<int, scalar> $parameters Query builder join parameters
233
     */
234
    private function addJoinToQuery(string $type, array $parameters): void
235
    {
236
        $comparison = implode('|', $parameters);
×
237

238
        if (!in_array($comparison, self::$processedJoins[$type], true)) {
×
239
            self::$joins[$type][] = $parameters;
×
240

241
            self::$processedJoins[$type][] = $comparison;
×
242
        }
243
    }
244
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc