Completed
Push — master ( 8c2434...6692d3 )
by Sébastien
14:40
created

ShardingKeyValueQuery::cacheNamespace()   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 1
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 3
c 1
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Bdf\Prime\Sharding\Query;
4
5
use Bdf\Prime\Connection\ConnectionInterface;
6
use Bdf\Prime\Query\AbstractReadCommand;
7
use Bdf\Prime\Query\Compiler\Preprocessor\DefaultPreprocessor;
8
use Bdf\Prime\Query\Compiler\Preprocessor\PreprocessorInterface;
9
use Bdf\Prime\Query\Contract\Query\KeyValueQueryInterface;
10
use Bdf\Prime\Query\Extension\CachableTrait;
11
use Bdf\Prime\Query\Extension\ExecutableTrait;
12
use Bdf\Prime\Sharding\Extension\ShardPicker;
13
use Bdf\Prime\Sharding\ShardingConnection;
14
15
/**
16
 * Handle simple key/value query on sharding connection
17
 * If the distribution key is found on the filters, the corresponding sharding query is used
18
 * In other case, all shards will be queried on
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...
19
 *
20
 * @property ShardingConnection $connection
0 ignored issues
show
Coding Style Documentation introduced by
@property tag is not allowed in class comment
Loading history...
21
 */
22
class ShardingKeyValueQuery extends AbstractReadCommand implements KeyValueQueryInterface
23
{
24
    use CachableTrait;
25
    use ExecutableTrait;
26
    use ShardPicker;
27
28
    /**
29
     * @var KeyValueQueryInterface[]
30
     */
31
    private $queries = [];
32
33
34
    /**
35
     * ShardingKeyValueQuery constructor.
36
     *
37
     * @param ShardingConnection $connection
38
     * @param PreprocessorInterface $preprocessor
39
     */
40 35
    public function __construct(ShardingConnection $connection, PreprocessorInterface $preprocessor = null)
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line before function; 2 found
Loading history...
41
    {
42 35
        parent::__construct($connection, $preprocessor ?: new DefaultPreprocessor());
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
43
44 35
        $this->statements = [
45
            'where'     => [],
46
            'table'     => null,
47
            'columns'   => [],
48
            'aggregate' => null,
49
            'limit'     => null,
50
            'values'    => [
51
                'data'  => [],
52
                'types' => []
0 ignored issues
show
introduced by
A comma should follow the last multiline array item. Found: ]
Loading history...
53
            ]
0 ignored issues
show
introduced by
A comma should follow the last multiline array item. Found: ]
Loading history...
54
        ];
55 35
    }
56
57
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $connection should have a doc-comment as per coding-style.
Loading history...
58
     * {@inheritdoc}
59
     */
60 35
    public function on(ConnectionInterface $connection)
61
    {
62 35
        $this->connection = $connection;
0 ignored issues
show
Documentation Bug introduced by
$connection is of type Bdf\Prime\Connection\ConnectionInterface, but the property $connection was declared to be of type Bdf\Prime\Sharding\ShardingConnection. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
63 35
        $this->queries    = [];
64
65 35
        return $this;
66
    }
67
68
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $columns should have a doc-comment as per coding-style.
Loading history...
69
     * {@inheritdoc}
70
     */
71 1
    public function project($columns = null)
72
    {
73 1
        $this->statements['columns'] = (array) $columns;
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
74
75 1
        return $this;
76
    }
77
78
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $columns should have a doc-comment as per coding-style.
Loading history...
79
     * {@inheritdoc}
80
     */
81
    public function select($columns = null)
82
    {
83
        return $this->select($columns);
84
    }
85
86
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $columns should have a doc-comment as per coding-style.
Loading history...
87
     * {@inheritdoc}
88
     */
89
    public function addSelect($columns)
90
    {
91
        $this->statements['columns'] = array_merge($this->statements['columns'], $columns);
92
93
        return $this;
94
    }
95
96
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $from should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $alias should have a doc-comment as per coding-style.
Loading history...
97
     * {@inheritdoc}
98
     */
99 34
    public function from($from, $alias = null)
0 ignored issues
show
Coding Style introduced by
The method parameter $alias is never used
Loading history...
100
    {
101 34
        $this->statements['table'] = $from;
102
103 34
        return $this;
104
    }
105
106
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $field should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $value should have a doc-comment as per coding-style.
Loading history...
107
     * {@inheritdoc}
108
     */
109 27
    public function where($field, $value = null)
110
    {
111 27
        if (is_array($field)) {
112 13
            $this->statements['where'] = $field + $this->statements['where'];
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
113
        } else {
114 14
            $this->statements['where'][$field] = $value;
115
        }
116
117 27
        return $this;
118
    }
119
120
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $values should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $types should have a doc-comment as per coding-style.
Loading history...
121
     * {@inheritdoc}
122
     */
123 9
    public function values(array $values = [], array $types = [])
124
    {
125 9
        $this->statements['values'] = [
126 9
            'data'  => $values,
127 9
            'types' => $types,
128
        ];
129
130 9
        return $this;
131
    }
132
133
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $limit should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $offset should have a doc-comment as per coding-style.
Loading history...
134
     * {@inheritdoc}
135
     *
136
     * @internal Use internally for optimise "first" query. The offset parameter is not used
137
     */
138 12
    public function limit($limit, $offset = null)
0 ignored issues
show
Unused Code introduced by
The parameter $offset is not used and could be removed. ( Ignorable by Annotation )

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

138
    public function limit($limit, /** @scrutinizer ignore-unused */ $offset = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
The method parameter $offset is never used
Loading history...
139
    {
140 12
        $this->statements['limit'] = $limit;
141
142 12
        return $this;
143
    }
144
145
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
146
     * {@inheritdoc}
147
     */
148 7
    public function count($column = null)
149
    {
150 7
        return (int) array_sum($this->aggregate(__FUNCTION__, $column));
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
151
    }
152
153
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
154
     * {@inheritdoc}
155
     */
156
    public function avg($column = null)
157
    {
158
        $results = $this->aggregate(__FUNCTION__, $column);
159
160
        return (float) array_sum($results) / count($results);
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
Coding Style introduced by
Operation must be bracketed
Loading history...
161
    }
162
163
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
164
     * {@inheritdoc}
165
     */
166 1
    public function min($column = null)
167
    {
168 1
        return (float) min($this->aggregate(__FUNCTION__, $column));
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
169
    }
170
171
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
172
     * {@inheritdoc}
173
     */
174 1
    public function max($column = null)
175
    {
176 1
        return (float) max($this->aggregate(__FUNCTION__, $column));
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
177
    }
178
179
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
180
     * {@inheritdoc}
181
     */
182 1
    public function sum($column = null)
183
    {
184 1
        return (float) array_sum($this->aggregate(__FUNCTION__, $column));
0 ignored issues
show
Coding Style introduced by
A cast statement should not be followed as per the coding-style.
Loading history...
185
    }
186
187
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $function should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $column should have a doc-comment as per coding-style.
Loading history...
188
     * {@inheritdoc}
189
     *
190
     * @return array
191
     */
192 7
    public function aggregate($function, $column = null)
193
    {
194 7
        $results = [];
195
196 7
        foreach ($this->selectQueries() as $query) {
197 7
            $results[] = $query->aggregate($function, $column);
198
        }
199
200 7
        return $results;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $results returns the type array|string[] which is incompatible with the return type mandated by Bdf\Prime\Query\Contract\Aggregatable::aggregate() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
201
    }
202
203
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $columns should have a doc-comment as per coding-style.
Loading history...
204
     * {@inheritdoc}
205
     *
206
     * @todo execute cached with closure
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
207
     */
208 19
    public function execute($columns = null)
209
    {
210 19
        $results = [];
211 19
        $limit = $this->statements['limit'];
212
213 19
        foreach ($this->selectQueries() as $query) {
214 19
            $results = array_merge($results, $query->execute($columns));
215
216 19
            if ($limit) {
217 12
                $count = count($results);
218
219 12
                if ($count == $limit) {
220 9
                    return $results;
221
                }
222
223 4
                if ($count > $limit) {
224 11
                    return array_slice($results, 0, $limit);
225
                }
226
            }
227
        }
228
229 10
        return $results;
230
    }
231
232
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $values should have a doc-comment as per coding-style.
Loading history...
233
     * {@inheritdoc}
234
     */
235 12
    public function update($values = null)
236
    {
237 12
        $count = 0;
238
239 12
        foreach ($this->selectQueries() as $query) {
240 12
            $count += $query->update($values);
241
        }
242
243 12
        if ($count > 0) {
244 9
            $this->clearCacheOnWrite();
245
        }
246
247 12
        return $count;
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253 5
    public function delete()
254
    {
255 5
        $count = 0;
256
257 5
        foreach ($this->selectQueries() as $query) {
258 5
            $count += $query->delete();
259
        }
260
261 5
        if ($count > 0) {
262 4
            $this->clearCacheOnWrite();
263
        }
264
265 5
        return $count;
266
    }
267
268
    /**
269
     * Select the queriesvto use
270
     *
271
     * @return KeyValueQueryInterface[]
272
     *
273
     * @throws \Doctrine\DBAL\Sharding\ShardingException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
274
     */
275 34
    private function selectQueries(): iterable
0 ignored issues
show
Coding Style introduced by
Private method name "ShardingKeyValueQuery::selectQueries" must be prefixed with an underscore
Loading history...
276
    {
277 34
        foreach ($this->getShardIds() as $shardId) {
278 34
            yield $this->getQueryByShard($shardId);
0 ignored issues
show
Bug Best Practice introduced by
The expression yield $this->getQueryByShard($shardId) returns the type Generator which is incompatible with the documented return type Bdf\Prime\Query\Contract...eyValueQueryInterface[].
Loading history...
279
        }
280 30
    }
281
282
    /**
283
     * Get the targeted shard IDs
284
     *
285
     * @return string[]
286
     */
287 34
    private function getShardIds(): array
0 ignored issues
show
Coding Style introduced by
Private method name "ShardingKeyValueQuery::getShardIds" must be prefixed with an underscore
Loading history...
288
    {
289 34
        if ($this->shardId !== null) {
290 1
            return [$this->shardId];
291
        }
292
293 33
        $distributionKey = $this->connection->getDistributionKey();
294
295 33
        if (isset($this->statements['where'][$distributionKey])) {
296 23
            return [$this->connection->getShardChoser()->pick($this->statements['where'][$distributionKey], $this->connection)];
297
        }
298
299 10
        return $this->connection->getShardIds();
300
    }
301
302
    /**
303
     * Get and configure a query for the given shard
304
     *
305
     * @param string $shardId The shard id
306
     *
307
     * @return KeyValueQueryInterface
308
     *
309
     * @throws \Doctrine\DBAL\Sharding\ShardingException
0 ignored issues
show
introduced by
Comment missing for @throws tag in function comment
Loading history...
310
     */
311 34
    private function getQueryByShard($shardId)
0 ignored issues
show
Coding Style introduced by
Private method name "ShardingKeyValueQuery::getQueryByShard" must be prefixed with an underscore
Loading history...
312
    {
313
        /** @var KeyValueQueryInterface $query */
0 ignored issues
show
Coding Style introduced by
Inline doc block comments are not allowed; use "/* Comment */" or "// Comment" instead
Loading history...
Coding Style introduced by
Block comments must be started with /*
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
314 34
        if (isset($this->queries[$shardId])) {
315 9
            $query = $this->queries[$shardId];
316
        } else {
317 34
            $this->queries[$shardId] = $query = $this->connection->getShardConnection($shardId)->make(KeyValueQueryInterface::class, $this->preprocessor());
0 ignored issues
show
Coding Style introduced by
Assignments must be the first block of code on a line
Loading history...
318 34
            $query->setExtension($this->extension);
0 ignored issues
show
Bug introduced by
The method setExtension() 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\ReadCommandInterface. ( Ignorable by Annotation )

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

318
            $query->/** @scrutinizer ignore-call */ 
319
                    setExtension($this->extension);
Loading history...
319
        }
320
321 34
        $query->from($this->statements['table']);
322
323 34
        if (!empty($this->statements['limit'])) {
324 12
            $query->limit($this->statements['limit']);
0 ignored issues
show
Bug introduced by
The method limit() does not exist on Bdf\Prime\Query\Contract...\KeyValueQueryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\Contract...\KeyValueQueryInterface. ( Ignorable by Annotation )

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

324
            $query->/** @scrutinizer ignore-call */ 
325
                    limit($this->statements['limit']);
Loading history...
Bug introduced by
The method limit() 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\AbstractReadCommand or Bdf\Prime\Sharding\Query\ShardingKeyValueQuery or Bdf\Prime\Query\Custom\KeyValue\KeyValueQuery or Bdf\Prime\Query\AbstractQuery or Bdf\Prime\Query\SqlQueryInterface. ( Ignorable by Annotation )

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

324
            $query->/** @scrutinizer ignore-call */ 
325
                    limit($this->statements['limit']);
Loading history...
325
        }
326
327 34
        if (!empty($this->statements['columns'])) {
328 1
            $query->project($this->statements['columns']);
0 ignored issues
show
Bug introduced by
The method project() 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

328
            $query->/** @scrutinizer ignore-call */ 
329
                    project($this->statements['columns']);
Loading history...
329
        }
330
331 34
        if (!empty($this->statements['where'])) {
332 27
            $query->where($this->statements['where']);
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

332
            $query->/** @scrutinizer ignore-call */ 
333
                    where($this->statements['where']);
Loading history...
333
        }
334
335 34
        if (!empty($this->statements['values']['data'])) {
336 9
            $query->values($this->statements['values']['data'], $this->statements['values']['types']);
0 ignored issues
show
Bug introduced by
The method values() does not exist on Bdf\Prime\Query\CommandInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Bdf\Prime\Query\ReadCommandInterface or Bdf\Prime\Query\QueryInterface or Bdf\Prime\Query\SqlQueryInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

336
            $query->/** @scrutinizer ignore-call */ 
337
                    values($this->statements['values']['data'], $this->statements['values']['types']);
Loading history...
337
        }
338
339 34
        return $query;
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345 2
    protected function cacheNamespace()
346
    {
347 2
        return $this->connection->getName().':'.$this->statements['table'];
348
    }
349
}
350