Completed
Pull Request — master (#5)
by Vincent
07:04
created

RepositoryQueryFactory::disableCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 7
c 1
b 0
f 0
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Bdf\Prime\Repository;
4
5
use Bdf\Prime\Cache\CacheInterface;
6
use Bdf\Prime\Connection\ConnectionInterface;
0 ignored issues
show
introduced by
Unused use statement
Loading history...
7
use Bdf\Prime\Exception\QueryException;
8
use Bdf\Prime\Mapper\Metadata;
0 ignored issues
show
introduced by
Unused use statement
Loading history...
9
use Bdf\Prime\Query\CommandInterface;
10
use Bdf\Prime\Query\Compiler\Preprocessor\OrmPreprocessor;
11
use Bdf\Prime\Query\Contract\Cachable;
12
use Bdf\Prime\Query\Contract\Query\KeyValueQueryInterface;
13
use Bdf\Prime\Query\QueryInterface;
0 ignored issues
show
introduced by
Unused use statement
Loading history...
14
use Bdf\Prime\Query\QueryRepositoryExtension;
15
use Bdf\Prime\Query\ReadCommandInterface;
16
17
/**
18
 * Factory for repository queries
19
 */
20
class RepositoryQueryFactory
21
{
22
    /**
23
     * @var RepositoryInterface
24
     */
25
    private $repository;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before first member var; 0 found
Loading history...
26
27
    /**
28
     * @var ConnectionInterface
29
     */
30
    private $connection;
31
32
    /**
33
     * @var Metadata
34
     */
35
    private $metadata;
36
37
    /**
38
     * @var callable[]
39
     */
40
    private $queries;
41
42
    /**
43
     * Query result cache
44
     *
45
     * @var CacheInterface
46
     */
47
    private $resultCache;
48
49
    /**
50
     * Check if the repository can support optimised KeyValue query
51
     * 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...
52
     *
53
     * @var bool
0 ignored issues
show
Bug introduced by
Expected "boolean" but found "bool" for @var tag in member variable comment
Loading history...
54
     */
55
    private $supportsKeyValue;
56
57
    //===============//
0 ignored issues
show
Coding Style introduced by
No space found before comment text; expected "// ===============//" but found "//===============//"
Loading history...
58
    // Optimisations //
59
    //===============//
0 ignored issues
show
Coding Style introduced by
No space found before comment text; expected "// ===============//" but found "//===============//"
Loading history...
60
61
    /**
62
     * @var KeyValueQueryInterface
63
     */
64
    private $findByIdQuery;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before member var; 5 found
Loading history...
65
66
    /**
67
     * @var KeyValueQueryInterface
68
     */
69
    private $countKeyValueQuery;
70
71
    /**
72
     * Save extension instance for optimisation
73
     *
74
     * @var QueryRepositoryExtension
75
     */
76
    private $extension;
77
78
79
    /**
80
     * RepositoryQueryFactory constructor.
81
     *
82
     * @param RepositoryInterface $repository
83
     * @param CacheInterface $resultCache
84
     */
85 137
    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...
86
    {
87 137
        $this->repository = $repository;
88 137
        $this->resultCache = $resultCache;
89
90 137
        $this->supportsKeyValue = empty($repository->constraints());
91
92 137
        $this->connection = $repository->connection();
93 137
        $this->queries = $repository->mapper()->queries();
94 137
        $this->metadata = $repository->metadata();
95 137
    }
96
97
    /**
98
     * Get query builder
99
     *
100
     * @return QueryInterface
101
     */
102 394
    public function builder()
103
    {
104 394
        return $this->configure($this->connection->builder(new OrmPreprocessor($this->repository)));
105
    }
106
107
    /**
108
     * Make a query
109
     *
110
     * @param string $query The query name or class name to make
111
     *
112
     * @return CommandInterface
113
     */
114 142
    public function make($query)
115
    {
116 142
        return $this->configure($this->connection->make($query, new OrmPreprocessor($this->repository)));
117
    }
118
119
    /**
120
     * Find entity by its primary key
121
     *
122
     * <code>
123
     * $queries->findById(2);
124
     * $queries->findById(['key1' => 1, 'key2' => 5]);
125
     * </code>
126
     *
127
     * @param array|string $id The entity PK. Use an array for composite PK
128
     *
129
     * @return mixed The entity or null if not found
130
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
131 35
    public function findById($id)
132
    {
133
        // Create a new query if cache is disabled
134 35
        if (!$this->supportsKeyValue) {
135 1
            $query = $this->builder();
136
        } else {
137 34
            if (!$this->findByIdQuery) {
138 20
                $this->findByIdQuery = $this->keyValue();
139
            }
140
141 34
            $query = $this->findByIdQuery;
142
        }
143
144 35
        if (is_array($id)) {
145 30
            if (!$this->isPrimaryKeyFilter($id)) {
146 2
                throw new QueryException('Only primary keys must be passed to findById()');
147
            }
148
149 29
            $query->where($id);
150
        } else {
151 6
            list($identifierName) = $this->metadata->primary['attributes'];
152 6
            $query->where($identifierName, $id);
153
        }
154
155 34
        return $query->first();
156
    }
157
158
    /**
159
     * Create a query for perform simple key / value search on the current repository
160
     *
161
     * /!\ Key value query cannot perform join queries (condition on relation is not allowed)
162
     *     And can perform only equality comparison
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
163
     *
164
     * <code>
165
     * // Search by name
166
     * $queries->keyValue('name', 'myName')->first();
167
     *
168
     * // Get an empty key value query
169
     * $queries->keyValue()->where(...);
170
     *
171
     * // With criteria
172
     * $queries->keyValue(['name' => 'John', 'customer.id' => 5])->all();
173
     * </code>
174
     *
175
     * @param string|array|null $attribute The search attribute, or criteria
176
     * @param mixed $value The search value
177
     *
178
     * @return KeyValueQueryInterface|null The query, or null if not supported
179
     */
180 102
    public function keyValue($attribute = null, $value = null)
181
    {
182 102
        if (!$this->supportsKeyValue) {
183 11
            return null;
184
        }
185
186 93
        $query = $this->make(KeyValueQueryInterface::class);
187
188 93
        if ($attribute) {
189 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

189
            $query->/** @scrutinizer ignore-call */ 
190
                    where($attribute, $value);
Loading history...
190
        }
191
192 93
        return $query;
193
    }
194
195
    /**
196
     * Count rows on the current table with simple key / value search
197
     *
198
     * /!\ Key value query cannot perform join queries (condition on relation is not allowed)
199
     *     And can perform only equality comparison
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
200
     *
201
     * <code>
202
     * // Count entities with myName as name value
203
     * $queries->countKeyValue('name', 'myName');
204
     *
205
     * // With criteria
206
     * $queries->countKeyValue(['name' => 'John', 'customer.id' => 5]);
207
     * </code>
208
     *
209
     * @param string|array|null $attribute The search attribute, or criteria
210
     * @param mixed $value The search value
211
     *
212
     * @return int
0 ignored issues
show
Coding Style introduced by
Expected "integer" but found "int" for function return type
Loading history...
213
     */
214 47
    public function countKeyValue($attribute = null, $value = null)
215
    {
216 47
        if (!$this->supportsKeyValue) {
217 8
            $query = $this->builder();
218
        } else {
219 45
            if (!$this->countKeyValueQuery) {
220 26
                $this->countKeyValueQuery = $this->keyValue();
221
            }
222
223 45
            $query = $this->countKeyValueQuery;
224
        }
225
226 47
        if ($attribute) {
227 46
            $query->where($attribute, $value);
228
        }
229
230 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

230
        return $query->/** @scrutinizer ignore-call */ count();
Loading history...
231
    }
232
233
    /**
234
     * Create a query selecting all entities
235
     *
236
     * This method will configure query like :
237
     * SELECT * FROM entity WHERE pk IN (entity1.pk, entity2.pk, ...)
238
     *
239
     * /!\ All entities MUST have a valid primary key !
240
     *
241
     * <code>
242
     * // True if modifications occurs on database
243
     * $hasChanges = ($entities != $queries->entities($entities)->all());
244
     *
245
     * // Delete all entities
246
     * $queries->entities($entities)->delete();
247
     * </code>
248
     *
249
     * @param object[] $entities Array of entities to select
250
     *
251
     * @return QueryInterface
252
     */
253 11
    public function entities(array $entities)
254
    {
255 11
        $query = $this->repository->queries()->builder();
256
257 11
        if ($this->repository->mapper()->metadata()->isCompositePrimaryKey()) {
258 3
            foreach ($entities as $entity) {
259 3
                $query->orWhere($this->repository->mapper()->primaryCriteria($entity));
260
            }
261
        } else {
262 8
            $attribute = $this->repository->mapper()->metadata()->primary['attributes'][0];
263 8
            $keys = [];
264
265 8
            foreach ($entities as $entity) {
266 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

266
                /** @scrutinizer ignore-call */ 
267
                $keys[] = $this->repository->extractOne($entity, $attribute);
Loading history...
267
            }
268
269 8
            $query->where($attribute, 'in', $keys);
270
        }
271
272 11
        return $query;
273
    }
274
275
    /**
276
     * Delegates call to corresponding query
277
     *
278
     * @param string $name
279
     * @param string $arguments
280
     *
281
     * @return mixed
282
     */
283 109
    public function __call($name, $arguments)
284
    {
285 109
        if (isset($this->queries[$name])) {
286 1
            return $this->queries[$name]($this->repository, ...$arguments);
287
        }
288
289 108
        return $this->builder()->$name(...$arguments);
290
    }
291
292
    /**
293
     * Configure the query for the current repository
294
     *
295
     * @param CommandInterface $query
296
     *
297
     * @return CommandInterface
298
     */
299 451
    private function configure(CommandInterface $query)
0 ignored issues
show
Coding Style introduced by
Private method name "RepositoryQueryFactory::configure" must be prefixed with an underscore
Loading history...
300
    {
301 451
        if ($this->metadata->useQuoteIdentifier) {
302 5
            $query->useQuoteIdentifier();
303
        }
304
305 451
        $query->setCustomFilters($this->repository->mapper()->filters());
306 451
        $query->from($this->metadata->table);
307
308 451
        if ($query instanceof ReadCommandInterface) {
309 435
            $query->setCollectionFactory($this->repository->collectionFactory());
310 435
            $this->extension()->apply($query);
311
        }
312
313 451
        if ($query instanceof Cachable) {
314 451
            $query->setCache($this->resultCache);
315
        }
316
317 451
        return $query;
318
    }
319
320
    /**
321
     * Optimise query extension creation
322
     *
323
     * @return QueryRepositoryExtension
324
     */
325 435
    private function extension()
0 ignored issues
show
Coding Style introduced by
Private method name "RepositoryQueryFactory::extension" must be prefixed with an underscore
Loading history...
326
    {
327 435
        if (!$this->extension) {
328 105
            $this->extension = new QueryRepositoryExtension($this->repository);
329
        }
330
331 435
        return clone $this->extension;
332
    }
333
334
    /**
335
     * Check if the given filter exactly match with primary key attributes
336
     *
337
     * @param array $filter
338
     *
339
     * @return bool
0 ignored issues
show
Coding Style introduced by
Expected "boolean" but found "bool" for function return type
Loading history...
340
     */
341 30
    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...
342
    {
343 30
        $pk = $this->metadata->primary['attributes'];
344
345 30
        if (count($filter) !== count($pk)) {
346 1
            return false;
347
        }
348
349 30
        foreach ($pk as $key) {
350 30
            if (!isset($filter[$key])) {
351 30
                return false;
352
            }
353
        }
354
355 29
        return true;
356
    }
357
}
358