Issues (3)

Security Analysis    not enabled

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/RepositoryTrait.php (2 issues)

Labels
Severity

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
/*
4
 * doctrine-base-repositories (https://github.com/juliangut/doctrine-base-repositories).
5
 * Doctrine2 utility repositories.
6
 *
7
 * @license MIT
8
 * @link https://github.com/juliangut/doctrine-base-repositories
9
 * @author Julián Gutiérrez <[email protected]>
10
 */
11
12
declare(strict_types=1);
13
14
namespace Jgut\Doctrine\Repository;
15
16
use Doctrine\Common\Util\Inflector;
17
18
/**
19
 * Repository trait.
20
 *
21
 * @method mixed find($id, $lockMode = null, $lockVersion = null)
22
 * @method mixed findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
23
 * @method mixed findOneBy(array $criteria, array $orderBy = null)
24
 * @method mixed findAll()
25
 */
26
trait RepositoryTrait
27
{
28
    /**
29
     * Supported magic methods.
30
     *
31
     * @var array
32
     */
33
    protected static $supportedMethods = [
34
        'findBy',
35
        'findOneBy',
36
        'findPaginatedBy',
37
        'removeBy',
38
        'removeOneBy',
39
        'countBy',
40
    ];
41
42
    /**
43
     * Methods that support exception throwing on fail.
44
     *
45
     * @var array
46
     */
47
    protected static $falibleMethods = [
48
        'findBy',
49
        'findOneBy',
50
        'findPaginatedBy',
51
    ];
52
53
    /**
54
     * Auto flush changes.
55
     *
56
     * @var bool
57
     */
58
    protected $autoFlush = false;
59
60
    /**
61
     * New object factory.
62
     *
63
     * @var callable
64
     */
65
    protected $objectFactory;
66
67
    /**
68
     * Get automatic manager flushing.
69
     *
70
     * @return bool
71
     */
72
    public function isAutoFlush(): bool
73
    {
74
        return $this->autoFlush;
75
    }
76
77
    /**
78
     * Set automatic manager flushing.
79
     *
80
     * @param bool $autoFlush
81
     */
82
    public function setAutoFlush(bool $autoFlush = true)
83
    {
84
        $this->autoFlush = $autoFlush;
85
    }
86
87
    /**
88
     * Manager flush.
89
     */
90
    public function flush()
91
    {
92
        $this->getManager()->flush();
93
    }
94
95
    /**
96
     * Find elements or throw an exception if none found.
97
     *
98
     * @param array      $criteria
99
     * @param array|null $orderBy
100
     * @param int|null   $limit
101
     * @param int|null   $offset
102
     *
103
     * @throws FindException
104
     *
105
     * @return object[]
106
     */
107
    public function findByOrFail(array $criteria, array $orderBy = null, int $limit = null, int $offset = null): array
108
    {
109
        $objects = $this->findBy($criteria, $orderBy, $limit, $offset);
0 ignored issues
show
It seems like $orderBy defined by parameter $orderBy on line 107 can also be of type null; however, Jgut\Doctrine\Repository\RepositoryTrait::findBy() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
110
111
        if (\count($objects) === 0) {
112
            throw new FindException('FindBy did not return any results');
113
        }
114
115
        return $objects;
116
    }
117
118
    /**
119
     * Find elements or throw an exception if none found.
120
     *
121
     * @param array $criteria
122
     *
123
     * @throws FindException
124
     *
125
     * @return object
126
     */
127
    public function findOneByOrFail(array $criteria)
128
    {
129
        $object = $this->findOneBy($criteria);
130
131
        if ($object === null) {
132
            throw new FindException('FindOneBy did not return any results');
133
        }
134
135
        return $object;
136
    }
137
138
    /**
139
     * Find one object by a set of criteria or create a new one.
140
     *
141
     * @param array $criteria
142
     *
143
     * @throws \RuntimeException
144
     *
145
     * @return object
146
     */
147
    public function findOneByOrGetNew(array $criteria)
148
    {
149
        $object = $this->findOneBy($criteria);
150
151
        if ($object === null) {
152
            $object = $this->getNew();
153
        }
154
155
        return $object;
156
    }
157
158
    /**
159
     * Get a new managed object instance.
160
     *
161
     * @throws \RuntimeException
162
     *
163
     * @return object
164
     */
165
    public function getNew()
166
    {
167
        $object = \call_user_func($this->getObjectFactory());
168
169
        if (!$this->canBeManaged($object)) {
170
            throw new \RuntimeException(
171
                \sprintf(
172
                    'Object factory must return an instance of %s. "%s" returned',
173
                    $this->getClassName(),
174
                    \is_object($object) ? \get_class($object) : \gettype($object)
175
                )
176
            );
177
        }
178
179
        return $object;
180
    }
181
182
    /**
183
     * Get object factory.
184
     *
185
     * @return callable
186
     */
187
    private function getObjectFactory(): callable
188
    {
189
        if ($this->objectFactory === null) {
190
            $className = $this->getClassName();
191
192
            $this->objectFactory = function () use ($className) {
193
                return new $className();
194
            };
195
        }
196
197
        return $this->objectFactory;
198
    }
199
200
    /**
201
     * Set object factory.
202
     *
203
     * @param callable $objectFactory
204
     */
205
    public function setObjectFactory(callable $objectFactory)
206
    {
207
        $this->objectFactory = $objectFactory;
208
    }
209
210
    /**
211
     * Add objects.
212
     *
213
     * @param object|iterable $objects
214
     * @param bool            $flush
215
     *
216
     * @throws \InvalidArgumentException
217
     */
218
    public function add($objects, bool $flush = false)
219
    {
220
        $this->runManagerAction('persist', $objects, $flush);
221
    }
222
223
    /**
224
     * Remove all objects.
225
     *
226
     * @param bool $flush
227
     */
228
    public function removeAll(bool $flush = false)
229
    {
230
        $this->runManagerAction('remove', $this->findAll(), $flush);
231
    }
232
233
    /**
234
     * Remove object filtered by a set of criteria.
235
     *
236
     * @param array $criteria
237
     * @param bool  $flush
238
     */
239
    public function removeBy(array $criteria, bool $flush = false)
240
    {
241
        $this->runManagerAction('remove', $this->findBy($criteria), $flush);
242
    }
243
244
    /**
245
     * Remove first object filtered by a set of criteria.
246
     *
247
     * @param array $criteria
248
     * @param bool  $flush
249
     */
250
    public function removeOneBy(array $criteria, bool $flush = false)
251
    {
252
        $this->runManagerAction('remove', $this->findOneBy($criteria), $flush);
253
    }
254
255
    /**
256
     * Remove objects.
257
     *
258
     * @param object|iterable|string|int $objects
259
     * @param bool                       $flush
260
     *
261
     * @throws \InvalidArgumentException
262
     */
263
    public function remove($objects, bool $flush = false)
264
    {
265
        if (!\is_object($objects) && !is_iterable($objects)) {
266
            $objects = $this->find($objects);
267
        }
268
269
        $this->runManagerAction('remove', $objects, $flush);
270
    }
271
272
    /**
273
     * Refresh objects.
274
     *
275
     * @param object|iterable $objects
276
     *
277
     * @throws \InvalidArgumentException
278
     */
279
    public function refresh($objects)
280
    {
281
        $backupAutoFlush = $this->autoFlush;
282
283
        $this->autoFlush = false;
284
        $this->runManagerAction('refresh', $objects, false);
285
286
        $this->autoFlush = $backupAutoFlush;
287
    }
288
289
    /**
290
     * Detach objects.
291
     *
292
     * @param object|iterable $objects
293
     *
294
     * @throws \InvalidArgumentException
295
     */
296
    public function detach($objects)
297
    {
298
        $backupAutoFlush = $this->autoFlush;
299
300
        $this->autoFlush = false;
301
        $this->runManagerAction('detach', $objects, false);
302
303
        $this->autoFlush = $backupAutoFlush;
304
    }
305
306
    /**
307
     * Get all objects count.
308
     *
309
     * @return int
310
     */
311
    public function countAll(): int
312
    {
313
        return $this->countBy([]);
314
    }
315
316
    /**
317
     * Get object count filtered by a set of criteria.
318
     *
319
     * @param mixed $criteria
320
     *
321
     * @return int
322
     */
323
    abstract public function countBy($criteria): int;
324
325
    /**
326
     * Adds support for magic methods.
327
     *
328
     * @param string $method
329
     * @param array  $arguments
330
     *
331
     * @throws \BadMethodCallException
332
     *
333
     * @return mixed
334
     */
335
    public function __call($method, $arguments)
336
    {
337
        if (\count($arguments) === 0) {
338
            throw new \BadMethodCallException(\sprintf(
339
                'You need to call %s::%s with a parameter',
340
                $this->getClassName(),
341
                $method
342
            ));
343
        }
344
345
        $baseMethod = $this->getSupportedMethod($method);
346
347
        if (\in_array($baseMethod, static::$falibleMethods, true) && \preg_match('/OrFail$/', $method)) {
348
            $field = \substr($method, \strlen($baseMethod), -6);
349
            $method = $baseMethod . 'OrFail';
350
        } elseif ($baseMethod === 'findOneBy' && \preg_match('/OrGetNew$/', $method)) {
351
            $field = \substr($method, \strlen($baseMethod), -8);
352
            $method = 'findOneByOrGetNew';
353
        } else {
354
            $field = \substr($method, \strlen($baseMethod));
355
            $method = $baseMethod;
356
        }
357
358
        return $this->callSupportedMethod($method, Inflector::camelize($field), $arguments);
359
    }
360
361
    /**
362
     * Get supported magic method.
363
     *
364
     * @param string $method
365
     *
366
     * @throws \BadMethodCallException
367
     *
368
     * @return string
369
     */
370
    private function getSupportedMethod(string $method): string
371
    {
372
        foreach (static::$supportedMethods as $supportedMethod) {
373
            if (\strpos($method, $supportedMethod) === 0) {
374
                return $supportedMethod;
375
            }
376
        }
377
378
        throw new \BadMethodCallException(\sprintf(
379
            'Undefined method "%s". Method call must start with one of "%s"!',
380
            $method,
381
            \implode('", "', static::$supportedMethods)
382
        ));
383
    }
384
385
    /**
386
     * Internal method call.
387
     *
388
     * @param string $method
389
     * @param string $fieldName
390
     * @param array  $arguments
391
     *
392
     * @throws \BadMethodCallException
393
     *
394
     * @return mixed
395
     */
396
    protected function callSupportedMethod(string $method, string $fieldName, array $arguments)
397
    {
398
        $classMetadata = $this->getClassMetadata();
399
400
        if (!$classMetadata->hasField($fieldName) && !$classMetadata->hasAssociation($fieldName)) {
401
            throw new \BadMethodCallException(\sprintf(
402
                'Invalid call to %s::%s. Field "%s" does not exist',
403
                $this->getClassName(),
404
                $method,
405
                $fieldName
406
            ));
407
        }
408
409
        // @codeCoverageIgnoreStart
410
        $parameters = \array_merge(
411
            [$fieldName => $arguments[0]],
412
            \array_slice($arguments, 1)
413
        );
414
415
        return \call_user_func_array([$this, $method], $parameters);
416
        // @codeCoverageIgnoreEnd
417
    }
418
419
    /**
420
     * Run manager action.
421
     *
422
     * @param string          $action
423
     * @param object|iterable $objects
424
     * @param bool            $flush
425
     *
426
     * @throws \InvalidArgumentException
427
     */
428
    protected function runManagerAction(string $action, $objects, bool $flush)
429
    {
430
        $manager = $this->getManager();
431
432
        if (!is_iterable($objects)) {
433
            $objects = \array_filter([$objects]);
434
        }
435
436
        foreach ($objects as $object) {
437
            if (!$this->canBeManaged($object)) {
438
                throw new \InvalidArgumentException(
439
                    \sprintf(
440
                        'Managed object must be a %s. "%s" given',
441
                        $this->getClassName(),
442
                        \is_object($object) ? \get_class($object) : \gettype($object)
443
                    )
444
                );
445
            }
446
447
            $manager->$action($object);
448
        }
449
450
        // @codeCoverageIgnoreStart
451
        if ($objects instanceof \Traversable) {
452
            $objects = \iterator_to_array($objects);
453
        }
454
        // @codeCoverageIgnoreEnd
455
456
        $this->flushObjects($objects, $flush);
0 ignored issues
show
It seems like $objects defined by parameter $objects on line 428 can also be of type object; however, Jgut\Doctrine\Repository...ryTrait::flushObjects() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
457
    }
458
459
    /**
460
     * Flush managed objects.
461
     *
462
     * @param array $objects
463
     * @param bool  $flush
464
     */
465
    protected function flushObjects(array $objects, bool $flush)
466
    {
467
        if ($flush || $this->autoFlush) {
468
            $this->getManager()->flush($objects);
469
        }
470
    }
471
472
    /**
473
     * Check if the object is of the proper type.
474
     *
475
     * @param object $object
476
     *
477
     * @return bool
478
     */
479
    protected function canBeManaged($object): bool
480
    {
481
        $managedClass = $this->getClassName();
482
483
        return $object instanceof $managedClass;
484
    }
485
486
    /**
487
     * Returns the fully qualified class name of the objects managed by the repository.
488
     *
489
     * @return string
490
     */
491
    abstract public function getClassName(): string;
492
493
    /**
494
     * Get object manager.
495
     *
496
     * @return \Doctrine\Common\Persistence\ObjectManager
497
     */
498
    abstract protected function getManager();
499
500
    /**
501
     * Get class metadata.
502
     *
503
     * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata
504
     */
505
    abstract protected function getClassMetadata();
506
}
507