Test Failed
Push — master ( 1c5165...b7ef6a )
by Kirill
03:37
created

Query   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 11

Test Coverage

Coverage 84.29%

Importance

Changes 0
Metric Value
wmc 30
lcom 2
cbo 11
dl 0
loc 246
ccs 59
cts 70
cp 0.8429
rs 10
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A bootIfNotBooted() 0 12 3
A scope() 0 6 1
A getCriteria() 0 4 1
A add() 0 6 1
A new() 0 4 1
A __get() 0 8 2
A __call() 0 19 5
A getScopes() 0 4 1
A attach() 0 8 2
A withAlias() 0 10 2
A create() 0 4 1
A merge() 0 9 2
A __clone() 0 13 3
A getIterator() 0 6 2
A isEmpty() 0 4 1
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 Illuminate\Support\Traits\Macroable;
15
use phpDocumentor\Reflection\Types\Self_;
16
use RDS\Hydrogen\Criteria\CriterionInterface;
17
use RDS\Hydrogen\Fn\RawFunction;
18
use RDS\Hydrogen\Query\AliasProvider;
19
use RDS\Hydrogen\Query\GroupByProvider;
20
use RDS\Hydrogen\Query\LimitAndOffsetProvider;
21
use RDS\Hydrogen\Query\OrderProvider;
22
use RDS\Hydrogen\Query\RelationProvider;
23
use RDS\Hydrogen\Query\RepositoryProvider;
24
use RDS\Hydrogen\Query\ExecutionsProvider;
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
    }
36
    use AliasProvider;
37
    use WhereProvider;
38
    use OrderProvider;
39
    use SelectProvider;
40
    use GroupByProvider;
41
    use RelationProvider;
42
    use RepositoryProvider;
43
    use ExecutionsProvider;
44
    use LimitAndOffsetProvider;
45
46
    /**
47
     * @var bool
48
     */
49
    private static $booted = false;
50
51
    /**
52
     * @var CriterionInterface[]|\SplObjectStorage
53
     */
54
    protected $criteria;
55
56
    /**
57
     * @var array|ObjectRepository[]
58
     */
59
    protected $scopes = [];
60
61
    /**
62
     * Query constructor.
63
     * @param ObjectRepository|null $repository
64
     */
65 67
    public function __construct(ObjectRepository $repository = null)
66
    {
67 67
        $this->criteria = new \SplObjectStorage();
68
69 67
        if ($repository) {
70 14
            $this->from($repository);
71
        }
72 67
    }
73
74
    /**
75
     * @return void
76
     */
77 30
    private function bootIfNotBooted(): void
78
    {
79 30
        if (self::$booted === false) {
80 1
            self::$booted = true;
81
82 1
            $config = $this->getRepository()->getEntityManager()->getConfiguration();
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...
83
84 1
            if (! $config->getCustomStringFunction('RAW')) {
85 1
                $config->addCustomStringFunction('RAW', RawFunction::class);
86
            }
87
        }
88 30
    }
89
90
    /**
91
     * Adds the specified set of scopes (method groups) to the query.
92
     *
93
     * @param object|string ...$scopes
94
     * @return Query|$this
95
     */
96 33
    public function scope(...$scopes): self
97
    {
98 33
        $this->scopes = \array_merge($this->scopes, $scopes);
99
100 33
        return $this;
101
    }
102
103
    /**
104
     * Returns a list of selection criteria.
105
     *
106
     * @return \Generator|CriterionInterface[]
107
     */
108 67
    public function getCriteria(): \Generator
109
    {
110 67
        yield from $this->criteria;
111 35
    }
112
113
    /**
114
     * Creates a new query (alias to the constructor).
115
     *
116
     * @param CriterionInterface $criterion
117
     * @return Query|$this
118
     */
119 63
    public function add(CriterionInterface $criterion): self
120
    {
121 63
        $this->criteria->attach($criterion->withQuery($this));
122
123 63
        return $this;
124
    }
125
126
    /**
127
     * Creates a new query (alias to the constructor).
128
     *
129
     * @param ObjectRepository|null $repository
130
     * @return Query
131
     */
132 67
    public static function new(ObjectRepository $repository = null): Query
133
    {
134 67
        return new static($repository);
135
    }
136
137
    /**
138
     * @param string $name
139
     * @return null
140
     */
141 18
    public function __get(string $name)
142
    {
143 18
        if (\method_exists($this, $name)) {
144 18
            return $this->$name();
145
        }
146
147
        return null;
148
    }
149
150
    /**
151
     * @param string $method
152
     * @param array $parameters
153
     * @return mixed|$this|Query
154
     */
155 4
    public function __call(string $method, array $parameters = [])
156
    {
157 4
        foreach ($this->scopes as $scope) {
158 4
            if (\method_exists($scope, $method)) {
159
                /** @var Query $query */
160 4
                $query = \is_object($scope)
161 4
                    ? clone $scope->$method(...$parameters)
162 4
                    : clone $scope::$method(...$parameters);
163
164 4
                foreach ($query->getCriteria() as $criterion) {
165 4
                    $this->add($criterion);
166
                }
167
168 4
                return $this;
169
            }
170
        }
171
172
        return $this->__macroableCall($method, $parameters);
173
    }
174
175
    /**
176
     * Returns a set of scopes for the specified query.
177
     *
178
     * @return array|ObjectRepository[]
179
     */
180 7
    public function getScopes(): array
181
    {
182 7
        return $this->scopes;
183
    }
184
185
    /**
186
     * Attaches a child query to the parent without affecting
187
     * the set of criteria (selections).
188
     *
189
     * @param Query $query
190
     * @return Query
191
     */
192 5
    public function attach(Query $query): Query
193
    {
194 5
        $this->repository
195 2
            ? $query->from($this->getRepository())
0 ignored issues
show
Bug introduced by
It seems like $this->getRepository() targeting RDS\Hydrogen\Query\Repos...ovider::getRepository() 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?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
196 3
            : $query->alias = $this->getAlias();
197
198 5
        return $query;
199
    }
200
201
    /**
202
     * @param string $alias
203
     * @return Query|$this|self
204
     */
205 2
    public function withAlias(string $alias): Query
206
    {
207 2
        $this->alias = $alias;
208
209 2
        foreach ($this->criteria as $criterion) {
210
            $criterion->withAlias($alias);
211
        }
212
213 2
        return $this;
214
    }
215
216
    /**
217
     * Creates a new query using the current set of scopes.
218
     *
219
     * @return Query
220
     */
221 7
    public function create(): Query
222
    {
223 7
        return static::new()->scope(...$this->getScopes());
224
    }
225
226
    /**
227
     * Copies a set of Criteria from the child query to the parent.
228
     *
229
     * @param Query $query
230
     * @return Query
231
     */
232 2
    public function merge(Query $query): Query
233
    {
234 2
        foreach ($query->getCriteria() as $criterion) {
235
            $criterion->withAlias($query->getAlias());
236
            $this->add($criterion);
237
        }
238
239 2
        return $this;
240
    }
241
242
    /**
243
     * @return void
244
     */
245 14
    public function __clone()
246
    {
247 14
        $reflection = new \ReflectionClass($this);
248
249 14
        foreach ($reflection->getProperties() as $property) {
250 14
            $property->setAccessible(true);
251 14
            $value = $property->getValue($this);
252
253 14
            if (\is_object($value)) {
254 14
                $property->setValue($this, clone $value);
255
            }
256
        }
257 14
    }
258
259
    /**
260
     * @return \Generator
261
     */
262
    public function getIterator(): \Generator
263
    {
264
        foreach ($this->get() as $result) {
265
            yield $result;
266
        }
267
    }
268
269
    /**
270
     * @return bool
271
     */
272
    public function isEmpty(): bool
273
    {
274
        return $this->criteria->count() === 0;
275
    }
276
}
277