Issues (39)

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/EntityManager.php (2 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
namespace ORM;
4
5
use ORM\Dbal\Column;
6
use ORM\Dbal\Dbal;
7
use ORM\Dbal\Other;
8
use ORM\Dbal\Table;
9
use ORM\Exception\IncompletePrimaryKey;
10
use ORM\Exception\InvalidConfiguration;
11
use ORM\Exception\NoConnection;
12
use ORM\Exception\NoEntity;
13
use PDO;
14
use ReflectionClass;
15
16
/**
17
 * The EntityManager that manages the instances of Entities.
18
 *
19
 * @package ORM
20
 * @author  Thomas Flori <[email protected]>
21
 */
22
class EntityManager
23
{
24
    const OPT_CONNECTION = 'connection';
25
    const OPT_TABLE_NAME_TEMPLATE = 'tableNameTemplate';
26
    const OPT_NAMING_SCHEME_TABLE = 'namingSchemeTable';
27
    const OPT_NAMING_SCHEME_COLUMN = 'namingSchemeColumn';
28
    const OPT_NAMING_SCHEME_METHODS = 'namingSchemeMethods';
29
    const OPT_NAMING_SCHEME_ATTRIBUTE = 'namingSchemeAttribute';
30
    const OPT_QUOTING_CHARACTER = 'quotingChar';
31
    const OPT_IDENTIFIER_DIVIDER = 'identifierDivider';
32
    const OPT_BOOLEAN_TRUE = 'true';
33
    const OPT_BOOLEAN_FALSE = 'false';
34
    const OPT_DBAL_CLASS = 'dbalClass';
35
36
    /** @deprecated */
37
    const OPT_MYSQL_BOOLEAN_TRUE = 'mysqlTrue';
38
    /** @deprecated */
39
    const OPT_MYSQL_BOOLEAN_FALSE = 'mysqlFalse';
40
    /** @deprecated */
41
    const OPT_SQLITE_BOOLEAN_TRUE = 'sqliteTrue';
42
    /** @deprecated */
43
    const OPT_SQLITE_BOOLEAN_FALSE = 'sqliteFalse';
44
    /** @deprecated */
45
    const OPT_PGSQL_BOOLEAN_TRUE = 'pgsqlTrue';
46
    /** @deprecated */
47
    const OPT_PGSQL_BOOLEAN_FALSE = 'pgsqlFalse';
48
49
    /** Connection to database
50
     * @var PDO|callable|DbConfig */
51
    protected $connection;
52
53
    /** The Database Abstraction Layer
54
     * @var Dbal */
55
    protected $dbal;
56
57
    /** The Namer instance
58
     * @var Namer */
59
    protected $namer;
60
61
    /** The Entity map
62
     * @var Entity[][] */
63
    protected $map = [];
64
65
    /** The options set for this instance
66
     * @var array */
67
    protected $options = [];
68
69
    /** Already fetched column descriptions
70
     * @var Table[]|Column[][] */
71
    protected $descriptions = [];
72
73
    /** Classes forcing bulk insert
74
     * @var BulkInsert[] */
75
    protected $bulkInserts = [];
76
77
    /** Mapping for EntityManager instances
78
     * @var EntityManager[string]|EntityManager[string][string] */
79
    protected static $emMapping = [
80
        'byClass'     => [],
81
        'byNameSpace' => [],
82
        'byParent'    => [],
83
        'last'        => null,
84
    ];
85 755
86
    /**
87 755
     * Constructor
88 11
     *
89
     * @param array $options Options for the new EntityManager
90
     */
91 755
    public function __construct($options = [])
92 755
    {
93
        foreach ($options as $option => $value) {
94
            $this->setOption($option, $value);
95
        }
96
97
        self::$emMapping['last'] = $this;
98
    }
99
100
    /**
101
     * Get an instance of the EntityManager.
102
     *
103
     * If no class is given it gets $class from backtrace.
104
     *
105 312
     * It first gets tries the EntityManager for the Namespace of $class, then for the parents of $class. If no
106
     * EntityManager is found it returns the last created EntityManager (null if no EntityManager got created).
107 312
     *
108 42
     * @param string $class
109 42
     * @return EntityManager
110 1
     */
111
    public static function getInstance($class = null)
112 41
    {
113
        if (empty($class)) {
114
            $trace = debug_backtrace();
115 311
            if (empty($trace[1]['class'])) {
116 311
                return self::$emMapping['last'];
117 309
            }
118
            $class = $trace[1]['class'];
119
        }
120 2
121
        if (!isset(self::$emMapping['byClass'][$class])) {
122
            if (!($em = self::getInstanceByParent($class)) && !($em = self::getInstanceByNameSpace($class))) {
123 2
                return self::$emMapping['last'];
124
            }
125
126
            self::$emMapping['byClass'][$class] = $em;
127
        }
128
129
        return self::$emMapping['byClass'][$class];
130
    }
131
132 310
    /**
133
     * Get the instance by NameSpace mapping
134 310
     *
135 2
     * @param $class
136 2
     * @return EntityManager
137
     */
138
    private static function getInstanceByNameSpace($class)
139
    {
140 309
        foreach (self::$emMapping['byNameSpace'] as $nameSpace => $em) {
141
            if (strpos($class, $nameSpace) === 0) {
142
                return $em;
143
            }
144
        }
145
146
        return null;
147
    }
148
149 311
    /**
150
     * Get the instance by Parent class mapping
151
     *
152 311
     * @param $class
153 309
     * @return EntityManager
154
     */
155
    private static function getInstanceByParent($class)
156 2
    {
157 2
        // we don't need a reflection when we don't have mapping byParent
158 2
        if (empty(self::$emMapping['byParent'])) {
159 2
            return null;
160
        }
161
162
        $reflection = new ReflectionClass($class);
163 1
        foreach (self::$emMapping['byParent'] as $parentClass => $em) {
164
            if ($reflection->isSubclassOf($parentClass)) {
165
                return $em;
166
            }
167
        }
168
169
        return null;
170
    }
171
172 2
    /**
173
     * Define $this EntityManager as the default EntityManager for $nameSpace
174 2
     *
175 2
     * @param $nameSpace
176
     * @return static
177
     */
178
    public function defineForNamespace($nameSpace)
179
    {
180
        self::$emMapping['byNameSpace'][$nameSpace] = $this;
181
        return $this;
182
    }
183
184 2
    /**
185
     * Define $this EntityManager as the default EntityManager for subClasses of $class
186 2
     *
187 2
     * @param $class
188
     * @return static
189
     */
190
    public function defineForParent($class)
191
    {
192
        self::$emMapping['byParent'][$class] = $this;
193
        return $this;
194
    }
195
196
    /**
197 15
     * Set $option to $value
198
     *
199
     * @param string $option One of OPT_* constants
200 15
     * @param mixed $value
201 1
     * @return static
202 1
     */
203
    public function setOption($option, $value)
204 14
    {
205 13
        switch ($option) {
206 12
            case self::OPT_CONNECTION:
207 3
                $this->setConnection($value);
208 3
                break;
209
210 11
            case self::OPT_SQLITE_BOOLEAN_TRUE:
211 10
            case self::OPT_MYSQL_BOOLEAN_TRUE:
212 9
            case self::OPT_PGSQL_BOOLEAN_TRUE:
213 3
                $option = self::OPT_BOOLEAN_TRUE;
214 3
                break;
215
216
            case self::OPT_SQLITE_BOOLEAN_FALSE:
217 15
            case self::OPT_MYSQL_BOOLEAN_FALSE:
218 15
            case self::OPT_PGSQL_BOOLEAN_FALSE:
219
                $option = self::OPT_BOOLEAN_FALSE;
220
                break;
221
        }
222
223
        $this->options[$option] = $value;
224
        return $this;
225
    }
226
227 10
    /**
228
     * Get $option
229 10
     *
230
     * @param $option
231
     * @return mixed
232
     */
233
    public function getOption($option)
234
    {
235
        return isset($this->options[$option]) ? $this->options[$option] : null;
236
    }
237
238
    /**
239
     * Add connection after instantiation
240
     *
241
     * The connection can be an array of parameters for DbConfig::__construct(), a callable function that returns a PDO
242
     * instance, an instance of DbConfig or a PDO instance itself.
243 36
     *
244
     * When it is not a PDO instance the connection get established on first use.
245 36
     *
246 6
     * @param mixed $connection A configuration for (or a) PDO instance
247
     * @throws InvalidConfiguration
248 30
     */
249 27
    public function setConnection($connection)
250 27
    {
251 3
        if (is_callable($connection) || $connection instanceof DbConfig) {
252 2
            $this->connection = $connection;
253 2
        } else {
254
            if ($connection instanceof PDO) {
255 1
                $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
256 1
                $this->connection = $connection;
257
            } elseif (is_array($connection)) {
258
                $this->connection = new DbConfig(...$connection);
0 ignored issues
show
The call to DbConfig::__construct() misses a required argument $name.

This check looks for function calls that miss required arguments.

Loading history...
$connection is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
259
            } else {
260 35
                throw new InvalidConfiguration(
261
                    'Connection must be callable, DbConfig, PDO or an array of parameters for DbConfig::__constructor'
262
                );
263
            }
264
        }
265
    }
266
267
    /**
268 20
     * Get the pdo connection.
269
     *
270 20
     * @return PDO
271 1
     * @throws NoConnection
272
     * @throws NoConnection
273
     */
274 19
    public function getConnection()
275 7
    {
276
        if (!$this->connection) {
277 4
            throw new NoConnection('No database connection');
278 4
        }
279 4
280 4
        if (!$this->connection instanceof PDO) {
281 4
            if ($this->connection instanceof DbConfig) {
282 4
                /** @var DbConfig $dbConfig */
283
                $dbConfig         = $this->connection;
284
                $this->connection = new PDO(
285 3
                    $dbConfig->getDsn(),
286 3
                    $dbConfig->user,
287 1
                    $dbConfig->pass,
288
                    $dbConfig->attributes
289 2
                );
290
            } else {
291 6
                $pdo = call_user_func($this->connection);
292
                if (!$pdo instanceof PDO) {
293
                    throw new NoConnection('Getter does not return PDO instance');
294 18
                }
295
                $this->connection = $pdo;
296
            }
297
            $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
298
        }
299
300
        return $this->connection;
301
    }
302 18
303
    /**
304 18
     * Get the Datbase Abstraction Layer
305 18
     *
306 18
     * @return Dbal
307 18
     */
308 18
    public function getDbal()
309
    {
310 18
        if (!$this->dbal) {
311 2
            $connectionType = $this->getConnection()->getAttribute(PDO::ATTR_DRIVER_NAME);
312
            $options        = &$this->options;
313
            $dbalClass      = isset($options[self::OPT_DBAL_CLASS]) ?
314 18
                $options[self::OPT_DBAL_CLASS] : __NAMESPACE__ . '\\Dbal\\' . ucfirst($connectionType);
315
316
            if (!class_exists($dbalClass)) {
317 18
                $dbalClass = Other::class;
318
            }
319
320
            $this->dbal = new $dbalClass($this, $options);
321
        }
322
323
        return $this->dbal;
324
    }
325
326
    /**
327
     * Get the Namer instance
328
     *
329
     * @return Namer
330
     * @codeCoverageIgnore trivial code...
331
     */
332
    public function getNamer()
333
    {
334
        if (!$this->namer) {
335
            $this->namer = new Namer($this->options);
336
        }
337
338
        return $this->namer;
339
    }
340
341
    /**
342
     * Synchronizing $entity with database
343
     *
344
     * If $reset is true it also calls reset() on $entity.
345
     *
346
     * @param Entity $entity
347
     * @param bool $reset Reset entities current data
348 14
     * @return bool
349
     */
350 14
    public function sync(Entity $entity, $reset = false)
351
    {
352
        $this->map($entity, true);
353 10
354 10
        /** @var EntityFetcher $fetcher */
355 10
        $fetcher = $this->fetch(get_class($entity));
356
        foreach ($entity->getPrimaryKey() as $attribute => $value) {
357
            $fetcher->where($attribute, $value);
358 10
        }
359 7
360 5
        $result = $this->getConnection()->query($fetcher->getQuery());
361 5
        if ($originalData = $result->fetch(PDO::FETCH_ASSOC)) {
362 2
            $entity->setOriginalData($originalData);
363
            if ($reset) {
364 5
                $entity->reset();
365
            }
366 2
            return true;
367
        }
368
        return false;
369
    }
370
371
    /**
372
     * Insert $entity in database
373
     *
374
     * Returns boolean if it is not auto incremented or the value of auto incremented column otherwise.
375
     *
376
     * @param Entity $entity
377
     * @param bool $useAutoIncrement
378
     * @return bool
379 12
     * @internal
380
     */
381 12
    public function insert(Entity $entity, $useAutoIncrement = true)
382
    {
383
        if (isset($this->bulkInserts[get_class($entity)])) {
384
            $this->bulkInserts[get_class($entity)]->add($entity);
385
            return true;
386
        }
387
388
        return $useAutoIncrement && $entity::isAutoIncremented() ?
389
            $this->getDbal()->insertAndSyncWithAutoInc($entity) :
390
            $this->getDbal()->insertAndSync($entity);
391 6
    }
392
393 6
    /**
394
     * Force $class to use bulk insert.
395
     *
396
     * At the end you should call finish bulk insert otherwise you may loose data.
397
     *
398
     * @param string $class
399
     * @param int $limit Maximum number of rows per insert
400
     * @return BulkInsert
401
     */
402
    public function useBulkInserts($class, $limit = 20)
403
    {
404 6
        if (!isset($this->bulkInserts[$class])) {
405
            $this->bulkInserts[$class] = new BulkInsert($this->getDbal(), $class, $limit);
406 6
        }
407 4
        return $this->bulkInserts[$class];
408 4
    }
409
410
    /**
411
     * Finish the bulk insert for $class.
412
     *
413
     * Returns an array of entities added.
414
     *
415
     * @param $class
416
     * @return Entity[]
417
     */
418
    public function finishBulkInserts($class)
419
    {
420
        $bulkInsert = $this->bulkInserts[$class];
421
        unset($this->bulkInserts[$class]);
422
        return $bulkInsert->finish();
423
    }
424
425
    /**
426 41
     * Update $entity in database
427
     *
428 41
     * @param Entity $entity
429 41
     * @return bool
430
     * @internal
431 35
     */
432 35
    public function update(Entity $entity)
433
    {
434
        return $this->getDbal()->update($entity);
435 35
    }
436
437
    /**
438
     * Delete $entity from database
439
     *
440
     * This method does not delete from the map - you can still receive the entity via fetch.
441
     *
442
     * @param Entity $entity
443
     * @return bool
444
     */
445
    public function delete(Entity $entity)
446
    {
447
        $this->getDbal()->delete($entity);
448
        $entity->setOriginalData([]);
449
        return true;
450
    }
451
452
    /**
453 67
     * Map $entity in the entity map
454
     *
455 67
     * Returns the given entity or an entity that previously got mapped. This is useful to work in every function with
456 67
     * the same object.
457 1
     *
458
     * ```php
459
     * $user = $enitityManager->map(new User(['id' => 42]));
460 66
     * ```
461 52
     *
462
     * @param Entity $entity
463
     * @param bool $update Update the entity map
464 15
     * @param string $class Overwrite the class
465 13
     * @return Entity
466
     */
467
    public function map(Entity $entity, $update = false, $class = null)
468 15
    {
469 15
        $class = $class ?: get_class($entity);
470 1
        $key   = static::buildChecksum($entity->getPrimaryKey());
471 1
472
        if ($update || !isset($this->map[$class][$key])) {
473
            $this->map[$class][$key] = $entity;
474
        }
475 14
476
        return $this->map[$class][$key];
477 14
    }
478 13
479
    /**
480
     * Fetch one or more entities
481 1
     *
482 1
     * With $primaryKey it tries to find this primary key in the entity map (carefully: mostly the database returns a
483 1
     * string and we do not convert them). If there is no entity in the entity map it tries to fetch the entity from
484
     * the database. The return value is then null (not found) or the entity.
485
     *
486 1
     * Without $primaryKey it creates an entityFetcher and returns this.
487
     *
488
     * @param string $class The entity class you want to fetch
489
     * @param mixed $primaryKey The primary key of the entity you want to fetch
490
     * @return Entity|EntityFetcher
491
     * @throws IncompletePrimaryKey
492
     * @throws NoEntity
493
     */
494
    public function fetch($class, $primaryKey = null)
495
    {
496
        $reflection = new ReflectionClass($class);
497
        if (!$reflection->isSubclassOf(Entity::class)) {
498
            throw new NoEntity($class . ' is not a subclass of Entity');
499
        }
500
        /** @var string|Entity $class */
501
502
        if ($primaryKey === null) {
503
            return new EntityFetcher($this, $class);
504
        }
505
506
        $primaryKey = $this::buildPrimaryKey($class, (array)$primaryKey);
507
        $checksum = $this::buildChecksum($primaryKey);
508
509
        if (isset($this->map[$class][$checksum])) {
510
            return $this->map[$class][$checksum];
511
        }
512
513
        $fetcher = new EntityFetcher($this, $class);
514
        foreach ($primaryKey as $attribute => $value) {
515
            $fetcher->where($attribute, $value);
516
        }
517
518
        return $fetcher->one();
519 2
    }
520
521 2
    /**
522 2
     * Returns $value formatted to use in a sql statement.
523
     *
524 2
     * @param  mixed $value The variable that should be returned in SQL syntax
525
     * @return string
526
     * @codeCoverageIgnore This is just a proxy
527
     */
528
    public function escapeValue($value)
529
    {
530
        return $this->getDbal()->escapeValue($value);
531
    }
532
533
    /**
534
     * Returns $identifier quoted for use in a sql statement
535
     *
536
     * @param string $identifier Identifier to quote
537
     * @return string
538
     * @codeCoverageIgnore This is just a proxy
539
     */
540
    public function escapeIdentifier($identifier)
541
    {
542
        return $this->getDbal()->escapeIdentifier($identifier);
543
    }
544
545
    /**
546
     * Returns an array of columns from $table.
547
     *
548
     * @param string $table
549
     * @return Column[]|Table
550
     */
551
    public function describe($table)
552
    {
553
        if (!isset($this->descriptions[$table])) {
554
            $this->descriptions[$table] = $this->getDbal()->describe($table);
555
        }
556
        return $this->descriptions[$table];
557
    }
558
559
    /**
560
     * Build a checksum from $primaryKey
561
     *
562
     * @param array $primaryKey
563
     * @return string
564
     */
565
    protected static function buildChecksum(array $primaryKey)
566
    {
567
        return md5(serialize($primaryKey));
568
    }
569
570
    /**
571
     * Builds the primary key with column names as keys
572
     *
573
     * @param string|Entity $class
574
     * @param array $primaryKey
575
     * @return array
576
     * @throws IncompletePrimaryKey
577
     */
578
    protected static function buildPrimaryKey($class, array $primaryKey)
579
    {
580
        $primaryKeyVars = $class::getPrimaryKeyVars();
581
        if (count($primaryKeyVars) !== count($primaryKey)) {
582
            throw new IncompletePrimaryKey(
583
                'Primary key consist of [' . implode(',', $primaryKeyVars) . '] only ' . count($primaryKey) . ' given'
584
            );
585
        }
586
587
        return array_combine($primaryKeyVars, $primaryKey);
588
    }
589
}
590