Completed
Push — master ( 425c1b...dc66d0 )
by Kirill
08:02
created

Query   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 373
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 15

Test Coverage

Coverage 33.33%

Importance

Changes 0
Metric Value
wmc 48
lcom 2
cbo 15
dl 0
loc 373
ccs 37
cts 111
cp 0.3333
rs 8.5599
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A raw() 0 4 1
A __callStatic() 0 6 1
A has() 0 10 3
A __get() 0 8 2
A getClassName() 0 4 1
A getEntityManager() 0 4 1
A getMetadata() 0 4 1
A column() 0 7 1
A __call() 0 15 4
A getCriteria() 0 4 1
A add() 0 10 2
A create() 0 10 2
A scope() 0 6 1
A new() 0 4 1
A getScopes() 0 4 1
A merge() 0 8 2
A attach() 0 8 2
A __clone() 0 6 1
B only() 0 26 6
A except() 0 12 3
A clone() 0 16 3
A getIterator() 0 6 2
A isEmpty() 0 4 1
A bootIfNotBooted() 0 9 2
A dump() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Query, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of Hydrogen package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace RDS\Hydrogen;
11
12
use Doctrine\Common\Persistence\ObjectRepository;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\Mapping\ClassMetadata;
15
use Illuminate\Support\Traits\Macroable;
16
use RDS\Hydrogen\Criteria\CriterionInterface;
17
use RDS\Hydrogen\Query\AliasProvider;
18
use RDS\Hydrogen\Query\ExecutionsProvider;
19
use RDS\Hydrogen\Query\GroupByProvider;
20
use RDS\Hydrogen\Query\LimitAndOffsetProvider;
21
use RDS\Hydrogen\Query\ModeProvider;
22
use RDS\Hydrogen\Query\OrderProvider;
23
use RDS\Hydrogen\Query\RelationProvider;
24
use RDS\Hydrogen\Query\RepositoryProvider;
25
use RDS\Hydrogen\Query\SelectProvider;
26
use RDS\Hydrogen\Query\WhereProvider;
27
28
/**
29
 * Class Query
30
 */
31
class Query implements \IteratorAggregate
32
{
33
    use Macroable {
34
        Macroable::__call as __macroableCall;
35
        Macroable::__callStatic as __macroableCallStatic;
36
    }
37
38
    use ModeProvider;
39
    use AliasProvider;
40
    use WhereProvider;
41
    use OrderProvider;
42
    use SelectProvider;
43
    use GroupByProvider;
44
    use RelationProvider;
45
    use RepositoryProvider;
46
    use ExecutionsProvider;
47
    use LimitAndOffsetProvider;
48
49
    /**
50
     * @var bool
51
     */
52
    private static $booted = false;
53
54
    /**
55
     * @var CriterionInterface[]
56
     */
57
    protected $criteria = [];
58
59
    /**
60
     * @var array|ObjectRepository[]
61
     */
62
    protected $scopes = [];
63
64
    /**
65
     * Query constructor.
66
     * @param ObjectRepository|null $repository
67
     */
68 63
    public function __construct(ObjectRepository $repository = null)
69
    {
70 63
        if ($repository) {
71 10
            $this->from($repository);
72
        }
73 63
    }
74
75
    /**
76
     * @param string $stmt
77
     * @return string
78
     */
79
    public static function raw(string $stmt): string
80
    {
81
        return \sprintf("RAW('%s')", \addcslashes($stmt, "'"));
82
    }
83
84
    /**
85
     * @param string $method
86
     * @param array $parameters
87
     * @return mixed
88
     */
89
    public static function __callStatic(string $method, array $parameters = [])
90
    {
91
        $instance = new static();
92
93
        return $instance->$method(...$parameters);
94
    }
95
96
    /**
97
     * @param string $criterion
98
     * @return bool
99
     */
100 16
    public function has(string $criterion): bool
101
    {
102 16
        foreach ($this->criteria as $haystack) {
103 14
            if (\get_class($haystack) === $criterion) {
104 14
                return true;
105
            }
106
        }
107
108 16
        return false;
109
    }
110
111
    /**
112
     * @param string $name
113
     * @return null
114
     */
115 19
    public function __get(string $name)
116
    {
117 19
        if (\method_exists($this, $name)) {
118 19
            return $this->$name();
119
        }
120
121
        return null;
122
    }
123
124
    /**
125
     * @return string
126
     */
127
    public function getClassName(): string
128
    {
129
        return $this->getRepository()->getClassName();
0 ignored issues
show
Bug introduced by
The method getClassName does only exist in Doctrine\Common\Persistence\ObjectRepository, but not in RDS\Hydrogen\Hydrogen.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
130
    }
131
132
    /**
133
     * @return EntityManagerInterface
134
     */
135
    public function getEntityManager(): EntityManagerInterface
136
    {
137
        return $this->getRepository()->getEntityManager();
0 ignored issues
show
Bug introduced by
The method getEntityManager does only exist in RDS\Hydrogen\Hydrogen, but not in Doctrine\Common\Persistence\ObjectRepository.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
138
    }
139
140
    /**
141
     * @return ClassMetadata
142
     */
143
    public function getMetadata(): ClassMetadata
144
    {
145
        return $this->getEntityManager()->getClassMetadata($this->getClassName());
146
    }
147
148
    /**
149
     * @param string $name
150
     * @return string
151
     */
152
    public function column(string $name): string
153
    {
154
        $name = \addcslashes($name, "'");
155
        $table = $this->getMetadata()->getTableName();
156
157
        return \sprintf("FIELD('%s', '%s', '%s')", $table, $this->getAlias(), $name);
158
    }
159
160
    /**
161
     * @param string $method
162
     * @param array $parameters
163
     * @return mixed|$this|Query
164
     */
165
    public function __call(string $method, array $parameters = [])
166
    {
167
        foreach ($this->scopes as $scope) {
168
            if (\method_exists($scope, $method)) {
169
                /** @var Query $query */
170
                $query = \is_object($scope)
171
                    ? clone $scope->$method(...$parameters)
172
                    : clone $scope::$method(...$parameters);
173
174
                return $this->merge($query);
175
            }
176
        }
177
178
        return $this->__macroableCall($method, $parameters);
179
    }
180
181
    /**
182
     * Returns a list of selection criteria.
183
     *
184
     * @return \Generator|CriterionInterface[]
185
     */
186 63
    public function getCriteria(): \Generator
187
    {
188 63
        yield from $this->criteria;
189 31
    }
190
191
    /**
192
     * Creates a new query (alias to the constructor).
193
     *
194
     * @param CriterionInterface $criterion
195
     * @return Query|$this
196
     */
197 61
    public function add(CriterionInterface $criterion): self
198
    {
199 61
        if (! $criterion->isAttached()) {
200
            $criterion->attach($this);
201
        }
202
203 61
        $this->criteria[] = $criterion;
204
205 61
        return $this;
206
    }
207
208
    /**
209
     * Creates a new query using the current set of scopes.
210
     *
211
     * @return Query
212
     */
213 5
    public function create(): Query
214
    {
215 5
        $query = static::new()->scope(...$this->getScopes());
216
217 5
        if ($this->repository) {
218 2
            return $query->from($this->repository);
0 ignored issues
show
Bug introduced by
It seems like $this->repository can also be of type object<RDS\Hydrogen\Hydrogen>; however, RDS\Hydrogen\Query\RepositoryProvider::from() does only seem to accept object<Doctrine\Common\P...tence\ObjectRepository>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
219
        }
220
221 3
        return $query;
222
    }
223
224
    /**
225
     * Adds the specified set of scopes (method groups) to the query.
226
     *
227
     * @param object|string ...$scopes
228
     * @return Query|$this
229
     */
230 29
    public function scope(...$scopes): self
231
    {
232 29
        $this->scopes = \array_merge($this->scopes, $scopes);
233
234 29
        return $this;
235
    }
236
237
    /**
238
     * Creates a new query (alias to the constructor).
239
     *
240
     * @param ObjectRepository|null $repository
241
     * @return Query
242
     */
243 63
    public static function new(ObjectRepository $repository = null): Query
244
    {
245 63
        return new static($repository);
246
    }
247
248
    /**
249
     * Returns a set of scopes for the specified query.
250
     *
251
     * @return array|ObjectRepository[]
252
     */
253 5
    public function getScopes(): array
254
    {
255 5
        return $this->scopes;
256
    }
257
258
    /**
259
     * Copies a set of Criteria from the child query to the parent.
260
     *
261
     * @param Query $query
262
     * @return Query
263
     */
264
    public function merge(Query $query): Query
265
    {
266
        foreach ($query->getCriteria() as $criterion) {
267
            $criterion->attach($this);
268
        }
269
270
        return $this->attach($query);
271
    }
272
273
    /**
274
     * @param Query $query
275
     * @return Query
276
     */
277
    public function attach(Query $query): Query
278
    {
279
        foreach ($query->getCriteria() as $criterion) {
280
            $this->add($criterion);
281
        }
282
283
        return $this;
284
    }
285
286
    /**
287
     * @return void
288
     * @throws \LogicException
289
     */
290
    public function __clone()
291
    {
292
        $error = '%s not allowed. Use %s::clone() instead';
293
294
        throw new \LogicException(\sprintf($error, __METHOD__, __CLASS__));
295
    }
296
297
    /**
298
     * @param string|\Closure $filter
299
     * @return Query
300
     */
301
    public function only($filter): Query
302
    {
303
        \assert(\is_string($filter) || \is_callable($filter));
304
305
        if (\is_string($filter) && ! \is_callable($filter)) {
306
            $typeOf = $filter;
307
308
            $filter = function (CriterionInterface $criterion) use ($typeOf): bool {
309
                return $criterion instanceof $typeOf;
310
            };
311
        }
312
313
        $copy = $this->clone();
314
315
        $criteria = [];
316
317
        foreach ($copy->getCriteria() as $criterion) {
318
            if ($filter($criterion)) {
319
                $criteria[] = $criterion;
320
            }
321
        }
322
323
        $copy->criteria = $criteria;
324
325
        return $copy;
326
    }
327
328
    /**
329
     * @param string|\Closure $filter
330
     * @return Query
331
     */
332
    public function except($filter): Query
333
    {
334
        if (\is_string($filter) && ! \is_callable($filter)) {
335
            return $this->only(function (CriterionInterface $criterion) use ($filter): bool {
336
                return ! $criterion instanceof $filter;
337
            });
338
        }
339
340
        return $this->only(function (CriterionInterface $criterion) use ($filter): bool {
341
            return ! $filter($criterion);
342
        });
343
    }
344
345
    /**
346
     * @return Query
347
     */
348
    public function clone(): Query
349
    {
350
        $clone = $this->create();
351
352
        foreach ($this->criteria as $criterion) {
353
            $criterion = clone $criterion;
354
355
            if ($criterion->isAttachedTo($this)) {
356
                $criterion->attach($clone);
357
            }
358
359
            $clone->add($criterion);
360
        }
361
362
        return $clone;
363
    }
364
365
    /**
366
     * @return \Generator
367
     */
368
    public function getIterator(): \Generator
369
    {
370
        foreach ($this->get() as $result) {
371
            yield $result;
372
        }
373
    }
374
375
    /**
376
     * @return bool
377
     */
378
    public function isEmpty(): bool
379
    {
380
        return $this->criteria->count() === 0;
0 ignored issues
show
Bug introduced by
The method count cannot be called on $this->criteria (of type array<integer,object<RDS...ia\CriterionInterface>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
381
    }
382
383
    /**
384
     * @return void
385
     */
386 26
    private function bootIfNotBooted(): void
387
    {
388 26
        if (self::$booted === false) {
389 1
            self::$booted = true;
390
391 1
            $bootstrap = new Bootstrap();
392 1
            $bootstrap->register($this->getRepository()->getEntityManager());
0 ignored issues
show
Bug introduced by
The method getEntityManager does only exist in RDS\Hydrogen\Hydrogen, but not in Doctrine\Common\Persistence\ObjectRepository.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
393
        }
394 26
    }
395
396
    /**
397
     * @return string
398
     */
399
    public function dump(): string
400
    {
401
        return $this->getRepository()->getProcessor()->dump($this);
0 ignored issues
show
Bug introduced by
The method getProcessor does only exist in RDS\Hydrogen\Hydrogen, but not in Doctrine\Common\Persistence\ObjectRepository.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
402
    }
403
}
404