Issues (124)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Query.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
 * A base class for all queries, contains the execution context
30
 * and a set of methods for adding criteria to this context.
31
 *
32
 * To add new methods during runtime, you can use the
33
 * `Query::macro(..)` method.
34
 */
35
class Query implements \IteratorAggregate
36
{
37
    use Macroable {
38
        Macroable::__call as __macroableCall;
39
        Macroable::__callStatic as __macroableCallStatic;
40
    }
41
42
    use ModeProvider;
43
    use AliasProvider;
44
    use WhereProvider;
45
    use OrderProvider;
46
    use SelectProvider;
47
    use GroupByProvider;
48
    use RelationProvider;
49
    use RepositoryProvider;
50
    use ExecutionsProvider;
51
    use LimitAndOffsetProvider;
52
53
    /**
54
     * Contains the status of the download. Before any request,
55
     * you need to make sure that all the runtime is loaded.
56
     *
57
     * It is this perennial one that indicates if at least one
58
     * query has already been created in order to load the
59
     * necessary functions.
60
     *
61
     * @var bool
62
     */
63
    private static $booted = false;
64
65
    /**
66
     * A set of query criteria in a given execution context.
67
     *
68
     * @var CriterionInterface[]
69
     */
70
    protected $criteria = [];
71
72
    /**
73
     * A set of scopes (classes and objects) that have access to be
74
     * able to create a query from a set of methods defined
75
     * in the specified scopes.
76
     *
77
     * @var array|ObjectRepository[]
78
     */
79
    protected $scopes = [];
80
81
    /**
82
     * @param ObjectRepository|null $repository
83
     */
84 69
    public function __construct(ObjectRepository $repository = null)
85
    {
86 69
        if ($repository !== null) {
87 14
            $this->from($repository);
88
        }
89 69
    }
90
91
    /**
92
     * Method for creating native DB queries or query parts.
93
     *
94
     * @param string $stmt
95
     * @return string
96
     */
97
    public static function raw(string $stmt): string
98
    {
99
        return \sprintf("RAW('%s')", \addcslashes($stmt, "'"));
100
    }
101
102
    /**
103
     * The method checks for the presence of the required criterion inside the query.
104
     *
105
     * TODO Add callable argument support (like filter).
106
     *
107
     * @param string $criterion
108
     * @return bool
109
     */
110 20
    public function has(string $criterion): bool
111
    {
112 20
        foreach ($this->criteria as $haystack) {
113 18
            if (\get_class($haystack) === $criterion) {
114 18
                return true;
115
            }
116
        }
117
118 20
        return false;
119
    }
120
121
    /**
122
     * Provides the ability to directly access methods without specifying parentheses.
123
     *
124
     * TODO 1) Add High Order Messaging for methods like `->field->where(23)` instead `->where('field', 23)`
125
     * TODO 2) Allow inner access `->embedded->field->where(23)` instead `->where('embedded.field', 23)`
126
     *
127
     * @param string $name
128
     * @return null
129
     */
130 19
    public function __get(string $name)
131
    {
132 19
        if (\method_exists($this, $name)) {
133 19
            return $this->$name();
134
        }
135
136
        return null;
137
    }
138
139
    /**
140
     * Creates the ability to directly access the table's column.
141
     *
142
     * @param string $name
143
     * @return string
144
     */
145
    public function column(string $name): string
146
    {
147
        $name = \addcslashes($name, "'");
148
        $table = $this->getMetadata()->getTableName();
149
150
        return \sprintf("FIELD('%s', '%s', '%s')", $table, $this->getAlias(), $name);
151
    }
152
153
    /**
154
     * @internal For internal use only
155
     * @return ClassMetadata
156
     */
157
    public function getMetadata(): ClassMetadata
158
    {
159
        return $this->getEntityManager()->getClassMetadata($this->getClassName());
160
    }
161
162
    /**
163
     * @internal For internal use only
164
     * @return EntityManagerInterface
165
     */
166
    public function getEntityManager(): EntityManagerInterface
167
    {
168
        return $this->getRepository()->getEntityManager();
0 ignored issues
show
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...
169
    }
170
171
    /**
172
     * @internal For internal use only
173
     * @return string
174
     */
175
    public function getClassName(): string
176
    {
177
        return $this->getRepository()->getClassName();
0 ignored issues
show
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...
178
    }
179
180
    /**
181
     * @param string $method
182
     * @param array $parameters
183
     * @return mixed|$this|Query
184
     */
185 8
    public function __call(string $method, array $parameters)
186
    {
187 8
        if ($result = $this->callScopes($method, $parameters)) {
188 8
            return $result;
189
        }
190
191
        return $this->__macroableCall($method, $parameters);
192
    }
193
194
    /**
195
     * @param string $method
196
     * @param array $parameters
197
     * @return null|Query|mixed
198
     */
199 8
    private function callScopes(string $method, array $parameters = [])
200
    {
201 8
        foreach ($this->scopes as $scope) {
202 8
            if (\method_exists($scope, $method)) {
203
                /** @var Query $query */
204 8
                $query = \is_object($scope) ? $scope->$method(...$parameters) : $scope::$method(...$parameters);
205
206 8
                if ($query instanceof self) {
207 4
                    return $this->merge($query->clone());
208
                }
209
210 8
                return $query;
211
            }
212
        }
213
214
        return null;
215
    }
216
217
    /**
218
     * Copies a set of Criteria from the child query to the parent.
219
     *
220
     * @param Query $query
221
     * @return Query
222
     */
223 4
    public function merge(Query $query): Query
224
    {
225 4
        foreach ($query->getCriteria() as $criterion) {
226 4
            $criterion->attach($this);
227
        }
228
229 4
        return $this->attach($query);
230
    }
231
232
    /**
233
     * Returns a list of selection criteria.
234
     *
235
     * @return \Generator|CriterionInterface[]
236
     */
237 67
    public function getCriteria(): \Generator
238
    {
239 67
        yield from $this->criteria;
240 35
    }
241
242
    /**
243
     * @param Query $query
244
     * @return Query
245
     */
246 4
    public function attach(Query $query): Query
247
    {
248 4
        foreach ($query->getCriteria() as $criterion) {
249 4
            $this->add($criterion);
250
        }
251
252 4
        return $this;
253
    }
254
255
    /**
256
     * Creates a new query (alias to the constructor).
257
     *
258
     * @param CriterionInterface $criterion
259
     * @return Query|$this
260
     */
261 65
    public function add(CriterionInterface $criterion): self
262
    {
263 65
        if (! $criterion->isAttached()) {
264
            $criterion->attach($this);
265
        }
266
267 65
        $this->criteria[] = $criterion;
268
269 65
        return $this;
270
    }
271
272
    /**
273
     * @return Query
274
     */
275 4
    public function clone(): Query
276
    {
277 4
        $clone = $this->create();
278
279 4
        foreach ($this->criteria as $criterion) {
280 4
            $criterion = clone $criterion;
281
282 4
            if ($criterion->isAttachedTo($this)) {
283 4
                $criterion->attach($clone);
284
            }
285
286 4
            $clone->add($criterion);
287
        }
288
289 4
        return $clone;
290
    }
291
292
    /**
293
     * Creates a new query using the current set of scopes.
294
     *
295
     * @return Query
296
     */
297 9
    public function create(): Query
298
    {
299 9
        $query = static::new()->scope(...$this->getScopes());
300
301 9
        if ($this->repository) {
302 2
            return $query->from($this->repository);
0 ignored issues
show
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...
303
        }
304
305 7
        return $query;
306
    }
307
308
    /**
309
     * Adds the specified set of scopes (method groups) to the query.
310
     *
311
     * @param object|string ...$scopes
312
     * @return Query|$this
313
     */
314 37
    public function scope(...$scopes): self
315
    {
316 37
        $this->scopes = \array_merge($this->scopes, $scopes);
317
318 37
        return $this;
319
    }
320
321
    /**
322
     * Creates a new query (alias to the constructor).
323
     *
324
     * @param ObjectRepository|null $repository
325
     * @return Query
326
     */
327 69
    public static function new(ObjectRepository $repository = null): Query
328
    {
329 69
        return new static($repository);
330
    }
331
332
    /**
333
     * Returns a set of scopes for the specified query.
334
     *
335
     * @return array|ObjectRepository[]
336
     */
337 9
    public function getScopes(): array
338
    {
339 9
        return $this->scopes;
340
    }
341
342
    /**
343
     * @return void
344
     * @throws \LogicException
345
     */
346
    public function __clone()
347
    {
348
        $error = '%s not allowed. Use %s::clone() instead';
349
350
        throw new \LogicException(\sprintf($error, __METHOD__, __CLASS__));
351
    }
352
353
    /**
354
     * @param string|\Closure $filter
355
     * @return Query
356
     */
357
    public function except($filter): Query
358
    {
359 View Code Duplication
        if (\is_string($filter) && ! \is_callable($filter)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360
            return $this->only(function (CriterionInterface $criterion) use ($filter): bool {
361
                return ! $criterion instanceof $filter;
362
            });
363
        }
364
365
        return $this->only(function (CriterionInterface $criterion) use ($filter): bool {
366
            return ! $filter($criterion);
367
        });
368
    }
369
370
    /**
371
     * @param string|\Closure $filter
372
     * @return Query
373
     */
374
    public function only($filter): Query
375
    {
376
        $filter = $this->createFilter($filter);
377
        $copy = $this->clone();
378
        $criteria = [];
379
380
        foreach ($copy->getCriteria() as $criterion) {
381
            if ($filter($criterion)) {
382
                $criteria[] = $criterion;
383
            }
384
        }
385
386
        $copy->criteria = $criteria;
387
388
        return $copy;
389
    }
390
391
    /**
392
     * @param string|callable $filter
393
     * @return callable
394
     */
395
    private function createFilter($filter): callable
396
    {
397
        \assert(\is_string($filter) || \is_callable($filter));
398
399 View Code Duplication
        if (\is_string($filter) && ! \is_callable($filter)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
            $typeOf = $filter;
401
402
            return function (CriterionInterface $criterion) use ($typeOf): bool {
403
                return $criterion instanceof $typeOf;
404
            };
405
        }
406
407
        return $filter;
408
    }
409
410
    /**
411
     * @return \Generator
412
     */
413
    public function getIterator(): \Generator
414
    {
415
        foreach ($this->get() as $result) {
416
            yield $result;
417
        }
418
    }
419
420
    /**
421
     * @return bool
422
     */
423
    public function isEmpty(): bool
424
    {
425
        return \count($this->criteria) === 0;
426
    }
427
428
    /**
429
     * @return string
430
     */
431
    public function dump(): string
432
    {
433
        return $this->getRepository()->getProcessor()->dump($this);
0 ignored issues
show
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...
434
    }
435
436
    /**
437
     * @return void
438
     */
439 30
    private function bootIfNotBooted(): void
440
    {
441 30
        if (self::$booted === false) {
442 1
            self::$booted = true;
443
444 1
            $bootstrap = new Bootstrap();
445 1
            $bootstrap->register($this->getRepository()->getEntityManager());
0 ignored issues
show
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...
446
        }
447 30
    }
448
}
449