Passed
Pull Request — master (#21)
by Vincent
07:14
created

RepositoryQueryFactory::fromAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Bdf\Prime\Repository;
4
5
use Bdf\Prime\Cache\CacheInterface;
6
use Bdf\Prime\Connection\ConnectionInterface;
7
use Bdf\Prime\Exception\PrimeException;
8
use Bdf\Prime\Exception\QueryException;
9
use Bdf\Prime\Mapper\Metadata;
10
use Bdf\Prime\Query\CommandInterface;
11
use Bdf\Prime\Query\Compiler\Preprocessor\OrmPreprocessor;
12
use Bdf\Prime\Query\Contract\Cachable;
13
use Bdf\Prime\Query\Contract\Query\KeyValueQueryInterface;
14
use Bdf\Prime\Query\Contract\ReadOperation;
15
use Bdf\Prime\Query\QueryInterface;
16
use Bdf\Prime\Query\QueryRepositoryExtension;
17
use Bdf\Prime\Query\ReadCommandInterface;
18
19
/**
20
 * Factory for repository queries
21
 */
22
class RepositoryQueryFactory
23
{
24
    /**
25
     * @var RepositoryInterface
26
     */
27
    private $repository;
28
29
    /**
30
     * @var ConnectionInterface
31
     */
32
    private $connection;
33
34
    /**
35
     * @var Metadata
36
     */
37
    private $metadata;
38
39
    /**
40
     * @var callable[]
41
     */
42
    private $queries;
43
44
    /**
45
     * Query result cache
46
     *
47
     * @var CacheInterface
48
     */
49
    private $resultCache;
50
51
    /**
52
     * Check if the repository can support optimised KeyValue query
53
     * If this value is false, keyValue() must returns null
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
54
     *
55
     * @var bool
0 ignored issues
show
Bug introduced by
Expected "boolean" but found "bool" for @var tag in member variable comment
Loading history...
56
     */
57
    private $supportsKeyValue;
58
59
    //===============//
0 ignored issues
show
Coding Style introduced by
No space found before comment text; expected "// ===============//" but found "//===============//"
Loading history...
60
    // Optimisations //
61
    //===============//
0 ignored issues
show
Coding Style introduced by
No space found before comment text; expected "// ===============//" but found "//===============//"
Loading history...
62
63
    /**
64
     * @var KeyValueQueryInterface
65
     */
66
    private $findByIdQuery;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before member var; 5 found
Loading history...
67
68
    /**
69
     * @var KeyValueQueryInterface
70
     */
71
    private $countKeyValueQuery;
72
73
    /**
74
     * Save extension instance for optimisation
75
     *
76
     * @var QueryRepositoryExtension
77
     */
78
    private $extension;
79
80
81
    /**
82
     * RepositoryQueryFactory constructor.
83
     *
84
     * @param RepositoryInterface $repository
85
     * @param CacheInterface|null $resultCache
86
     */
87 142
    public function __construct(RepositoryInterface $repository, ?CacheInterface $resultCache = null)
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line before function; 2 found
Loading history...
88
    {
89 142
        $this->repository = $repository;
90 142
        $this->resultCache = $resultCache;
91
92 142
        $this->supportsKeyValue = empty($repository->constraints());
93
94 142
        $this->connection = $repository->connection();
95 142
        $this->queries = $repository->mapper()->queries();
96 142
        $this->metadata = $repository->metadata();
97 142
    }
98
99
    /**
100
     * Get query builder
101
     *
102
     * @return QueryInterface
103
     */
104 398
    public function builder()
105
    {
106 398
        return $this->fromAlias();
107
    }
108
109
    /**
110
     * Get query builder with a defined table alias on FROM clause
111
     *
112
     * @param string|null $alias The FROM table alias
113
     *
114
     * @return QueryInterface
115
     *
116
     * @throws PrimeException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
117
     */
118 399
    public function fromAlias(?string $alias = null)
119
    {
120 399
        return $this->configure($this->connection->builder(new OrmPreprocessor($this->repository)), $alias);
121
    }
122
123
    /**
124
     * Make a query
125
     *
126
     * @param string $query The query name or class name to make
127
     *
128
     * @return CommandInterface
129
     */
130 144
    public function make($query)
131
    {
132 144
        return $this->configure($this->connection->make($query, new OrmPreprocessor($this->repository)));
133
    }
134
135
    /**
136
     * Find entity by its primary key
137
     *
138
     * <code>
139
     * $queries->findById(2);
140
     * $queries->findById(['key1' => 1, 'key2' => 5]);
141
     * </code>
142
     *
143
     * @param array|string $id The entity PK. Use an array for composite PK
144
     *
145
     * @return mixed The entity or null if not found
146
     * @throws PrimeException When query fail
147
     */
148
    #[ReadOperation]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
Coding Style introduced by
Perl-style comments are not allowed; use "// Comment" instead
Loading history...
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
149 36
    public function findById($id)
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
150
    {
151
        // Create a new query if cache is disabled
152 36
        if (!$this->supportsKeyValue) {
153 1
            $query = $this->builder();
154
        } else {
155 35
            if (!$this->findByIdQuery) {
156 21
                $this->findByIdQuery = $this->keyValue();
157
            }
158
159 35
            $query = $this->findByIdQuery;
160
        }
161
162 36
        if (is_array($id)) {
163 31
            if (!$this->isPrimaryKeyFilter($id)) {
164 2
                throw new QueryException('Only primary keys must be passed to findById()');
165
            }
166
167 30
            $query->where($id);
168
        } else {
169 6
            list($identifierName) = $this->metadata->primary['attributes'];
170 6
            $query->where($identifierName, $id);
171
        }
172
173 35
        return $query->first();
174
    }
175
176
    /**
177
     * Create a query for perform simple key / value search on the current repository
178
     *
179
     * /!\ Key value query cannot perform join queries (condition on relation is not allowed)
180
     *     And can perform only equality comparison
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
181
     *
182
     * <code>
183
     * // Search by name
184
     * $queries->keyValue('name', 'myName')->first();
185
     *
186
     * // Get an empty key value query
187
     * $queries->keyValue()->where(...);
188
     *
189
     * // With criteria
190
     * $queries->keyValue(['name' => 'John', 'customer.id' => 5])->all();
191
     * </code>
192
     *
193
     * @param string|array|null $attribute The search attribute, or criteria
194
     * @param mixed $value The search value
195
     *
196
     * @return KeyValueQueryInterface|null The query, or null if not supported
197
     */
198 103
    public function keyValue($attribute = null, $value = null)
199
    {
200 103
        if (!$this->supportsKeyValue) {
201 11
            return null;
202
        }
203
204 94
        $query = $this->make(KeyValueQueryInterface::class);
205
206 94
        if ($attribute) {
207 25
            $query->where($attribute, $value);
0 ignored issues
show
Bug introduced by
The method where() does not exist on Bdf\Prime\Query\CommandInterface. It seems like you code against a sub-type of Bdf\Prime\Query\CommandInterface such as Bdf\Prime\Query\Contract...\KeyValueQueryInterface or Bdf\Prime\Query\QueryInterface or Bdf\Prime\Query\AbstractReadCommand. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

207
            $query->/** @scrutinizer ignore-call */ 
208
                    where($attribute, $value);
Loading history...
208
        }
209
210 94
        return $query;
211
    }
212
213
    /**
214
     * Count rows on the current table with simple key / value search
215
     *
216
     * /!\ Key value query cannot perform join queries (condition on relation is not allowed)
217
     *     And can perform only equality comparison
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
218
     *
219
     * <code>
220
     * // Count entities with myName as name value
221
     * $queries->countKeyValue('name', 'myName');
222
     *
223
     * // With criteria
224
     * $queries->countKeyValue(['name' => 'John', 'customer.id' => 5]);
225
     * </code>
226
     *
227
     * @param string|array|null $attribute The search attribute, or criteria
228
     * @param mixed $value The search value
229
     *
230
     * @return int
231
     * @throws PrimeException When query fail
232
     */
233
    #[ReadOperation]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
Coding Style introduced by
Perl-style comments are not allowed; use "// Comment" instead
Loading history...
Coding Style introduced by
Perl-style comments are not allowed. Use "// Comment." or "/* comment */" instead.
Loading history...
234 47
    public function countKeyValue($attribute = null, $value = null)
0 ignored issues
show
Coding Style introduced by
You must use "/**" style comments for a function comment
Loading history...
235
    {
236 47
        if (!$this->supportsKeyValue) {
237 8
            $query = $this->builder();
238
        } else {
239 45
            if (!$this->countKeyValueQuery) {
240 26
                $this->countKeyValueQuery = $this->keyValue();
241
            }
242
243 45
            $query = $this->countKeyValueQuery;
244
        }
245
246 47
        if ($attribute) {
247 46
            $query->where($attribute, $value);
248
        }
249
250 47
        return $query->count();
0 ignored issues
show
Bug introduced by
The method count() does not exist on Bdf\Prime\Query\QueryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\QueryInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

250
        return $query->/** @scrutinizer ignore-call */ count();
Loading history...
251
    }
252
253
    /**
254
     * Create a query selecting all entities
255
     *
256
     * This method will configure query like :
257
     * SELECT * FROM entity WHERE pk IN (entity1.pk, entity2.pk, ...)
258
     *
259
     * /!\ All entities MUST have a valid primary key !
260
     *
261
     * <code>
262
     * // True if modifications occurs on database
263
     * $hasChanges = ($entities != $queries->entities($entities)->all());
264
     *
265
     * // Delete all entities
266
     * $queries->entities($entities)->delete();
267
     * </code>
268
     *
269
     * @param object[] $entities Array of entities to select
270
     *
271
     * @return QueryInterface
272
     */
273 11
    public function entities(array $entities)
274
    {
275 11
        $query = $this->repository->queries()->builder();
276
277 11
        if ($this->repository->mapper()->metadata()->isCompositePrimaryKey()) {
278 3
            foreach ($entities as $entity) {
279 3
                $query->orWhere($this->repository->mapper()->primaryCriteria($entity));
280
            }
281
        } else {
282 8
            $attribute = $this->repository->mapper()->metadata()->primary['attributes'][0];
283 8
            $keys = [];
284
285 8
            foreach ($entities as $entity) {
286 8
                $keys[] = $this->repository->extractOne($entity, $attribute);
0 ignored issues
show
Bug introduced by
The method extractOne() does not exist on Bdf\Prime\Repository\RepositoryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Repository\RepositoryInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

286
                /** @scrutinizer ignore-call */ 
287
                $keys[] = $this->repository->extractOne($entity, $attribute);
Loading history...
287
            }
288
289 8
            $query->where($attribute, 'in', $keys);
290
        }
291
292 11
        return $query;
293
    }
294
295
    /**
296
     * Delegates call to corresponding query
297
     *
298
     * @param string $name
299
     * @param string $arguments
300
     *
301
     * @return mixed
302
     */
303 109
    public function __call($name, $arguments)
304
    {
305 109
        if (isset($this->queries[$name])) {
306 1
            return $this->queries[$name]($this->repository, ...$arguments);
307
        }
308
309 108
        return $this->builder()->$name(...$arguments);
310
    }
311
312
    /**
313
     * Configure the query for the current repository
314
     *
315
     * @param CommandInterface $query
316
     * @param string|null $alias The FROM table alias
317
     *
318
     * @return CommandInterface
319
     */
320 456
    private function configure(CommandInterface $query, ?string $alias = null)
0 ignored issues
show
Coding Style introduced by
Private method name "RepositoryQueryFactory::configure" must be prefixed with an underscore
Loading history...
321
    {
322 456
        if ($this->metadata->useQuoteIdentifier) {
323 5
            $query->useQuoteIdentifier();
324
        }
325
326 456
        $query->setCustomFilters($this->repository->mapper()->filters());
327 456
        $query->from($this->metadata->table, $alias);
328
329 456
        if ($query instanceof ReadCommandInterface) {
330 440
            $query->setCollectionFactory($this->repository->collectionFactory());
331 440
            $this->extension()->apply($query);
332
        }
333
334 456
        if ($query instanceof Cachable) {
335 456
            $query->setCache($this->resultCache);
336
        }
337
338 456
        return $query;
339
    }
340
341
    /**
342
     * Optimise query extension creation
343
     *
344
     * @return QueryRepositoryExtension
345
     */
346 440
    private function extension()
0 ignored issues
show
Coding Style introduced by
Private method name "RepositoryQueryFactory::extension" must be prefixed with an underscore
Loading history...
347
    {
348 440
        if (!$this->extension) {
349 108
            $this->extension = new QueryRepositoryExtension($this->repository);
350
        }
351
352 440
        return clone $this->extension;
353
    }
354
355
    /**
356
     * Check if the given filter exactly match with primary key attributes
357
     *
358
     * @param array $filter
359
     *
360
     * @return bool
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
361
     */
362 31
    private function isPrimaryKeyFilter(array $filter): bool
0 ignored issues
show
Coding Style introduced by
Private method name "RepositoryQueryFactory::isPrimaryKeyFilter" must be prefixed with an underscore
Loading history...
363
    {
364 31
        $pk = $this->metadata->primary['attributes'];
365
366 31
        if (count($filter) !== count($pk)) {
367 1
            return false;
368
        }
369
370 31
        foreach ($pk as $key) {
371 31
            if (!isset($filter[$key])) {
372 31
                return false;
373
            }
374
        }
375
376 30
        return true;
377
    }
378
}
379