Completed
Push — master ( b7ef6a...58a002 )
by Kirill
03:12
created

Query::merge()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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\Processor\ProcessorInterface;
18
use RDS\Hydrogen\Query\AliasProvider;
19
use RDS\Hydrogen\Query\ExecutionsProvider;
20
use RDS\Hydrogen\Query\GroupByProvider;
21
use RDS\Hydrogen\Query\LimitAndOffsetProvider;
22
use RDS\Hydrogen\Query\ModeProvider;
23
use RDS\Hydrogen\Query\OrderProvider;
24
use RDS\Hydrogen\Query\RelationProvider;
25
use RDS\Hydrogen\Query\RepositoryProvider;
26
use RDS\Hydrogen\Query\SelectProvider;
27
use RDS\Hydrogen\Query\WhereProvider;
28
29
/**
30
 * Class Query
31
 */
32
class Query implements \IteratorAggregate
33
{
34
    use Macroable {
35
        Macroable::__call as __macroableCall;
36
        Macroable::__callStatic as __macroableCallStatic;
37
    }
38
39
    use ModeProvider;
40
    use AliasProvider;
41
    use WhereProvider;
42
    use OrderProvider;
43
    use SelectProvider;
44
    use GroupByProvider;
45
    use RelationProvider;
46
    use RepositoryProvider;
47
    use ExecutionsProvider;
48
    use LimitAndOffsetProvider;
49
50
    /**
51
     * @var bool
52
     */
53
    private static $booted = false;
54
55
    /**
56
     * @var CriterionInterface[]|\SplDoublyLinkedList
57
     */
58
    protected $criteria;
59
60
    /**
61
     * @var array|ObjectRepository[]
62
     */
63
    protected $scopes = [];
64
65
    /**
66
     * Query constructor.
67
     * @param ObjectRepository|null $repository
68
     */
69 67
    public function __construct(ObjectRepository $repository = null)
70
    {
71 67
        $this->criteria = new \SplDoublyLinkedList();
72
73 67
        if ($repository) {
74 14
            $this->from($repository);
75
        }
76 67
    }
77
78
    /**
79
     * @param string $stmt
80
     * @return string
81
     */
82
    public static function raw(string $stmt): string
83
    {
84
        return \sprintf("RAW('%s')", \addcslashes($stmt, "'"));
85
    }
86
87
    /**
88
     * @param string $method
89
     * @param array $parameters
90
     * @return mixed
91
     */
92
    public static function __callStatic(string $method, array $parameters = [])
93
    {
94
        $instance = new static();
95
96
        return $instance->$method(...$parameters);
97
    }
98
99
    /**
100
     * @param string $criterion
101
     * @return bool
102
     */
103 20
    public function has(string $criterion): bool
104
    {
105 20
        foreach ($this->criteria as $haystack) {
106 18
            if (\get_class($haystack) === $criterion) {
107 18
                return true;
108
            }
109
        }
110
111 20
        return false;
112
    }
113
114
    /**
115
     * @param string $name
116
     * @return null
117
     */
118 19
    public function __get(string $name)
119
    {
120 19
        if (\method_exists($this, $name)) {
121 19
            return $this->$name();
122
        }
123
124
        return null;
125
    }
126
127
    /**
128
     * @return string
129
     */
130
    public function getClassName(): string
131
    {
132
        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...
133
    }
134
135
    /**
136
     * @return EntityManagerInterface
137
     */
138
    public function getEntityManager(): EntityManagerInterface
139
    {
140
        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...
141
    }
142
143
    /**
144
     * @return ClassMetadata
145
     */
146
    public function getMetadata(): ClassMetadata
147
    {
148
        return $this->getEntityManager()->getClassMetadata($this->getClassName());
149
    }
150
151
    /**
152
     * @param string $name
153
     * @return string
154
     */
155
    public function column(string $name): string
156
    {
157
        $name = \addcslashes($name, "'");
158
        $table = $this->getMetadata()->getTableName();
159
160
        return \sprintf("FIELD('%s', '%s', '%s')", $table, $this->getAlias(), $name);
161
    }
162
163
    /**
164
     * @param string $method
165
     * @param array $parameters
166
     * @return mixed|$this|Query
167
     */
168 4
    public function __call(string $method, array $parameters = [])
169
    {
170 4
        foreach ($this->scopes as $scope) {
171 4
            if (\method_exists($scope, $method)) {
172
                /** @var Query $query */
173 4
                $query = \is_object($scope)
174 4
                    ? clone $scope->$method(...$parameters)
175 4
                    : clone $scope::$method(...$parameters);
176
177 4
                return $this->merge($query);
178
            }
179
        }
180
181
        return $this->__macroableCall($method, $parameters);
182
    }
183
184
    /**
185
     * Returns a list of selection criteria.
186
     *
187
     * @return \Generator|CriterionInterface[]
188
     */
189 67
    public function getCriteria(): \Generator
190
    {
191 67
        yield from $this->criteria;
192 35
    }
193
194
    /**
195
     * Creates a new query (alias to the constructor).
196
     *
197
     * @param CriterionInterface $criterion
198
     * @return Query|$this
199
     */
200 65
    public function add(CriterionInterface $criterion): self
201
    {
202 65
        if (! $criterion->isAttached()) {
203
            $criterion->attach($this);
204
        }
205
206 65
        $this->criteria->push($criterion);
207
208 65
        return $this;
209
    }
210
211
    /**
212
     * Creates a new query using the current set of scopes.
213
     *
214
     * @return Query
215
     */
216 5
    public function create(): Query
217
    {
218 5
        $query = static::new()->scope(...$this->getScopes());
219
220 5
        if ($this->repository) {
221 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...
222
        }
223
224 3
        return $query;
225
    }
226
227
    /**
228
     * Adds the specified set of scopes (method groups) to the query.
229
     *
230
     * @param object|string ...$scopes
231
     * @return Query|$this
232
     */
233 33
    public function scope(...$scopes): self
234
    {
235 33
        $this->scopes = \array_merge($this->scopes, $scopes);
236
237 33
        return $this;
238
    }
239
240
    /**
241
     * Creates a new query (alias to the constructor).
242
     *
243
     * @param ObjectRepository|null $repository
244
     * @return Query
245
     */
246 67
    public static function new(ObjectRepository $repository = null): Query
247
    {
248 67
        return new static($repository);
249
    }
250
251
    /**
252
     * Returns a set of scopes for the specified query.
253
     *
254
     * @return array|ObjectRepository[]
255
     */
256 5
    public function getScopes(): array
257
    {
258 5
        return $this->scopes;
259
    }
260
261
    /**
262
     * Copies a set of Criteria from the child query to the parent.
263
     *
264
     * @param Query $query
265
     * @return Query
266
     */
267 4
    public function merge(Query $query): Query
268
    {
269 4
        foreach ($query->getCriteria() as $criterion) {
270 4
            $criterion->attach($this);
271
        }
272
273 4
        return $this->attach($query);
274
    }
275
276
    /**
277
     * @param Query $query
278
     * @return Query
279
     */
280 4
    public function attach(Query $query): Query
281
    {
282 4
        foreach ($query->getCriteria() as $criterion) {
283 4
            $this->add($criterion);
284
        }
285
286 4
        return $this;
287
    }
288
289
    /**
290
     * @return void
291
     */
292 4
    public function __clone()
293
    {
294 4
        $reflection = new \ReflectionClass($this);
295
296 4
        foreach ($reflection->getProperties() as $property) {
297 4
            $property->setAccessible(true);
298 4
            $value = $property->getValue($this);
299
300 4
            if (\is_object($value)) {
301 4
                $property->setValue($this, clone $value);
302
            }
303
        }
304 4
    }
305
306
    /**
307
     * @return \Generator
308
     */
309
    public function getIterator(): \Generator
310
    {
311
        foreach ($this->get() as $result) {
312
            yield $result;
313
        }
314
    }
315
316
    /**
317
     * @return bool
318
     */
319
    public function isEmpty(): bool
320
    {
321
        return $this->criteria->count() === 0;
322
    }
323
324
    /**
325
     * @return void
326
     */
327 30
    private function bootIfNotBooted(): void
328
    {
329 30
        if (self::$booted === false) {
330 1
            self::$booted = true;
331
332 1
            $bootstrap = new Bootstrap();
333 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...
334
        }
335 30
    }
336
337
    /**
338
     * @return string
339
     */
340
    public function dump(): string
341
    {
342
        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...
343
    }
344
}
345