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/Entity.php (1 issue)

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\Error;
6
use ORM\Entity\EventHandlers;
7
use ORM\Entity\GeneratesPrimaryKeys;
8
use ORM\Entity\Naming;
9
use ORM\Entity\Relations;
10
use ORM\Entity\Validation;
11
use ORM\EntityManager as EM;
12
use ORM\Exception\IncompletePrimaryKey;
13
use ORM\Exception\UnknownColumn;
14
use ReflectionClass;
15
use Serializable;
16
17
/**
18
 * Definition of an entity
19
 *
20
 * The instance of an entity represents a row of the table and the statics variables and methods describe the database
21
 * table.
22
 *
23
 * This is the main part where your configuration efforts go. The following properties and methods are well documented
24
 * in the manual under [https://tflori.github.io/orm/entityDefinition.html](Entity Definition).
25
 *
26
 * @package ORM
27
 * @link    https://tflori.github.io/orm/entityDefinition.html Entity Definition
28
 * @author  Thomas Flori <[email protected]>
29
 */
30
abstract class Entity implements Serializable
31
{
32
    use Validation, Relations, Naming, EventHandlers;
33
34
    /** @deprecated Use Relation::OPT_CLASS instead */
35
    const OPT_RELATION_CLASS       = 'class';
36
    /** @deprecated Use Relation::OPT_CARDINALITY instead */
37
    const OPT_RELATION_CARDINALITY = 'cardinality';
38
    /** @deprecated Use Relation::OPT_REFERENCE instead */
39
    const OPT_RELATION_REFERENCE   = 'reference';
40
    /** @deprecated Use Relation::OPT_OPPONENT instead */
41
    const OPT_RELATION_OPPONENT    = 'opponent';
42
    /** @deprecated Use Relation::OPT_TABLE instead */
43
    const OPT_RELATION_TABLE       = 'table';
44
45
    /** The variable(s) used for primary key.
46
     * @var string[]|string */
47
    protected static $primaryKey = ['id'];
48
49
    /** Whether or not the primary key is auto incremented.
50
     * @var bool */
51
    protected static $autoIncrement = true;
52
53
    /** Additional attributes to show in toArray method
54
     * @var array  */
55
    protected static $includedAttributes = [];
56
57
    /** Attributes to hide for toArray method (overruled by $attributes parameter)
58
     * @var array  */
59
    protected static $excludedAttributes = [];
60
61
    /** The reflections of the classes.
62
     * @internal
63
     * @var ReflectionClass[] */
64
    protected static $reflections = [];
65
66
    /** The current data of a row.
67
     * @var mixed[] */
68
    protected $data = [];
69
70
    /** The original data of the row.
71
     * @var mixed[] */
72
    protected $originalData = [];
73
74
    /** The entity manager from which this entity got created
75
     * @var EM */
76
    protected $entityManager;
77
78
    /**
79
     * Constructor
80
     *
81
     * It calls ::onInit() after initializing $data and $originalData.
82
     *
83
     * @param mixed[] $data          The current data
84
     * @param EM      $entityManager The EntityManager that created this entity
85
     * @param bool    $fromDatabase  Whether or not the data comes from database
86
     */
87
    final public function __construct(array $data = [], EM $entityManager = null, $fromDatabase = false)
88
    {
89
        if ($fromDatabase) {
90
            $this->originalData = $data;
91
        }
92
        $this->data          = array_merge($this->data, $data);
93
        $this->entityManager = $entityManager ?: EM::getInstance(static::class);
94
        $this->onInit(!$fromDatabase);
95
    }
96
97
    /**
98
     * Create an entityFetcher for this entity
99
     *
100
     * @return EntityFetcher
101
     */
102
    public static function query()
103
    {
104
        return EM::getInstance(static::class)->fetch(static::class);
0 ignored issues
show
Bug Compatibility introduced by
The expression \ORM\EntityManager::getI...->fetch(static::class); of type ORM\EntityFetcher|ORM\Entity adds the type ORM\Entity to the return on line 104 which is incompatible with the return type documented by ORM\Entity::query of type ORM\EntityFetcher.
Loading history...
105
    }
106
107
    /**
108
     * Get the primary key vars
109
     *
110
     * The primary key can consist of multiple columns. You should configure the vars that are translated to these
111
     * columns.
112
     *
113
     * @return array
114 122
     */
115
    public static function getPrimaryKeyVars()
116 122
    {
117 14
        return !is_array(static::$primaryKey) ? [ static::$primaryKey ] : static::$primaryKey;
118
    }
119 122
120 122
    /**
121 122
     * Check if the table has a auto increment column
122 122
     *
123
     * @return bool
124
     */
125
    public static function isAutoIncremented()
126
    {
127
        return count(static::getPrimaryKeyVars()) > 1 ? false : static::$autoIncrement;
128
    }
129
130
    /**
131
     * @param EM $entityManager
132
     * @return static
133
     */
134
    public function setEntityManager(EM $entityManager)
135
    {
136
        $this->entityManager = $entityManager;
137 166
        return $this;
138
    }
139 166
140 6
    /**
141
     * @param string $attribute
142
     * @return mixed|null
143 164
     * @see self::getAttribute
144 164
     * @codeCoverageIgnore Alias for getAttribute
145
     */
146
    public function __get($attribute)
147
    {
148
        return $this->getAttribute($attribute);
149
    }
150
151
    /**
152
     * Get the value from $attribute
153
     *
154
     * If there is a custom getter this method get called instead.
155 72
     *
156
     * @param string $attribute The variable to get
157 72
     * @return mixed|null
158
     * @link https://tflori.github.io/orm/entities.html Working with entities
159
     */
160
    public function getAttribute($attribute)
161
    {
162
        $em     = EM::getInstance(static::class);
163
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($attribute), self::$namingSchemeMethods);
164
165
        if (method_exists($this, $getter) && is_callable([ $this, $getter ])) {
166
            return $this->$getter();
167
        } else {
168
            $col    = static::getColumnName($attribute);
169 154
            $result = isset($this->data[$col]) ? $this->data[$col] : null;
170
171 154
            if (!$result && isset(static::$relations[$attribute]) && isset($this->entityManager)) {
172 11
                return $this->getRelated($attribute);
173
            }
174
175 143
            return $result;
176 143
        }
177
    }
178
179
    /**
180
     * Check if a column is defined
181
     *
182
     * @param $attribute
183
     * @return bool
184 19
     */
185
    public function __isset($attribute)
186 19
    {
187
        $em     = EM::getInstance(static::class);
188
        $getter = $em->getNamer()->getMethodName('get' . ucfirst($attribute), self::$namingSchemeMethods);
189
190
        if (method_exists($this, $getter) && is_callable([ $this, $getter ])) {
191
            return $this->$getter() !== null;
192
        } else {
193 12
            $col = static::getColumnName($attribute);
194
            $isset = isset($this->data[$col]);
195 12
196 12
            if (!$isset && isset(static::$relations[$attribute])) {
197
                return !empty($this->getRelated($attribute));
198
            }
199
200
            return $isset;
201
        }
202
    }
203
204
    /**
205
     * @param string $attribute The variable to change
206
     * @param mixed $value The value to store
207
     * @see self::getAttribute
208
     * @codeCoverageIgnore Alias for getAttribute
209
     */
210 112
    public function __set($attribute, $value)
211
    {
212 112
        $this->setAttribute($attribute, $value);
213 112
    }
214
215 112
    /**
216 4
     * Set $attribute to $value
217
     *
218 108
     * Tries to call custom setter before it stores the data directly. If there is a setter the setter needs to store
219 108
     * data that should be updated in the database to $data. Do not store data in $originalData as it will not be
220
     * written and give wrong results for dirty checking.
221 108
     *
222 1
     * The onChange event is called after something got changed.
223
     *
224
     * The method throws an error when the validation fails (also when the column does not exist).
225 107
     *
226
     * @param string $attribute The variable to change
227
     * @param mixed $value The value to store
228
     * @return static
229
     * @link https://tflori.github.io/orm/entities.html Working with entities
230
     * @throws Error
231
     */
232
    public function setAttribute($attribute, $value)
233
    {
234
        $col = $this->getColumnName($attribute);
235 3
236
        $em     = EM::getInstance(static::class);
237 3
        $setter = $em->getNamer()->getMethodName('set' . ucfirst($attribute), self::$namingSchemeMethods);
238 3
239
        if (method_exists($this, $setter) && is_callable([ $this, $setter ])) {
240 3
            $oldValue   = $this->__get($attribute);
241 1
            $md5OldData = md5(serialize($this->data));
242
            $this->$setter($value);
243 2
            $changed = $md5OldData !== md5(serialize($this->data));
244 2
        } else {
245
            if (static::isValidatorEnabled()) {
246 2
                $error = static::validate($attribute, $value);
247 1
                if ($error instanceof Error) {
248
                    throw $error;
249
                }
250 1
            }
251
252
            $oldValue         = $this->__get($attribute);
253
            $changed          = (isset($this->data[$col]) ? $this->data[$col] : null) !== $value;
254
            $this->data[$col] = $value;
255
        }
256
257
        if ($changed) {
258
            $this->onChange($attribute, $oldValue, $this->__get($attribute));
259
        }
260
261
        return $this;
262
    }
263
264
    /**
265
     * Fill the entity with $data
266
     *
267
     * When $checkMissing is set to true it also proves that the absent columns are nullable.
268
     *
269
     * @param array $data
270 38
     * @param bool $ignoreUnknown
271
     * @param bool $checkMissing
272 38
     * @throws UnknownColumn
273
     * @throws Error
274 38
     */
275 38
    public function fill(array $data, $ignoreUnknown = false, $checkMissing = false)
276
    {
277 38
        foreach ($data as $attribute => $value) {
278 3
            try {
279 3
                $this->__set($attribute, $value);
280 3
            } catch (UnknownColumn $e) {
281 3
                if (!$ignoreUnknown) {
282
                    throw $e;
283 35
                }
284 35
            }
285
        }
286 1
287
        if ($checkMissing && is_array($errors = $this->isValid())) {
288
            throw $errors[0];
289 31
        }
290 31
    }
291 31
292
    /**
293
     * Resets the entity or $attribute to original data
294 34
     *
295 31
     * @param string $attribute Reset only this variable or all variables
296
     */
297 34
    public function reset($attribute = null)
298
    {
299
        if (!empty($attribute)) {
300
            $col = static::getColumnName($attribute);
301
            if (isset($this->originalData[$col])) {
302
                $this->data[$col] = $this->originalData[$col];
303
            } else {
304
                unset($this->data[$col]);
305
            }
306
            return;
307
        }
308
309
        $this->data = $this->originalData;
310 8
    }
311
312 8
    /**
313
     * Save the entity to EntityManager
314 7
     *
315 2
     * @return Entity
316 2
     * @throws IncompletePrimaryKey
317 7
     */
318
    public function save()
319
    {
320
        // @codeCoverageIgnoreStart
321
        if (func_num_args() === 1 && func_get_arg(0) instanceof EM) {
322 7
            trigger_error(
323 1
                'Passing EntityManager to save is deprecated. Use ->setEntityManager() to overwrite',
324
                E_USER_DEPRECATED
325 6
            );
326
        }
327
        // @codeCoverageIgnoreEnd
328
329
        $inserted = false;
330
        $updated  = false;
331
332
        try {
333 22
            // this may throw if the primary key is auto incremented but we using this to omit duplicated code
334
            if (!$this->entityManager->sync($this)) {
335 22
                $this->prePersist();
336 3
                $inserted = $this->entityManager->insert($this, false);
337 3
            } elseif ($this->isDirty()) {
338 2
                $this->preUpdate();
339
                $updated = $this->entityManager->update($this);
340 1
            }
341
        } catch (IncompletePrimaryKey $e) {
342 3
            if (static::isAutoIncremented()) {
343
                $this->prePersist();
344
                $inserted = $this->entityManager->insert($this);
345 19
            } elseif ($this instanceof GeneratesPrimaryKeys) {
346 19
                $this->generatePrimaryKey();
347
                $this->prePersist();
348
                $inserted = $this->entityManager->insert($this);
349
            } else {
350
                throw $e;
351
            }
352
        }
353
354
        $inserted && $this->postPersist();
355
        $updated && $this->postUpdate();
356
357
        return $this;
358
    }
359
360
    /**
361 16
     * Generates a primary key
362
     *
363
     * This method should only be executed from save method.
364
     * @codeCoverageIgnore no operations
365
     */
366
    protected function generatePrimaryKey()
367
    {
368
        // no operation by default
369
    }
370
371
    /**
372 16
     * Checks if entity or $attribute got changed
373 16
     *
374
     * @param string $attribute Check only this variable or all variables
375
     * @return bool
376
     */
377 16
    public function isDirty($attribute = null)
378 2
    {
379 2
        if (!empty($attribute)) {
380 4
            $col = static::getColumnName($attribute);
381 3
            return (isset($this->data[$col]) ? $this->data[$col] : null) !==
382 6
                   (isset($this->originalData[$col]) ? $this->originalData[$col] : null);
383
        }
384 10
385 10
        ksort($this->data);
386 8
        ksort($this->originalData);
387 8
388
        return serialize($this->data) !== serialize($this->originalData);
389 1
    }
390 1
391 1
    /**
392
     * Get the primary key
393 1
     *
394
     * @return array
395
     * @throws IncompletePrimaryKey
396
     */
397 14
    public function getPrimaryKey()
398 14
    {
399
        $primaryKey = [];
400 14
        foreach (static::getPrimaryKeyVars() as $attribute) {
401
            $value = $this->$attribute;
402
            if ($value === null) {
403
                throw new IncompletePrimaryKey('Incomplete primary key - missing ' . $attribute);
404
            }
405
            $primaryKey[$attribute] = $value;
406
        }
407
        return $primaryKey;
408
    }
409
410
    /**
411
     * Get current data
412
     *
413
     * @return array
414
     * @internal
415
     */
416
    public function getData()
417
    {
418
        return $this->data;
419
    }
420
421 20
    /**
422
     * Set new original data
423 20
     *
424 4
     * @param array $data
425 4
     * @internal
426 4
     */
427
    public function setOriginalData(array $data)
428
    {
429 17
        $this->originalData = $data;
430 17
    }
431
432 17
    /**
433
     * Get an array of the entity
434
     *
435
     * @param array $attributes
436
     * @param bool $includeRelations
437
     * @return array
438
     */
439
    public function toArray(array $attributes = [], $includeRelations = true)
440
    {
441
        if (empty($attributes)) {
442
            $attributes = array_keys(static::$columnAliases);
443
            $attributes = array_merge($attributes, array_map([$this, 'getAttributeName'], array_keys($this->data)));
444
            $attributes = array_merge($attributes, static::$includedAttributes);
445
            $attributes = array_diff($attributes, static::$excludedAttributes);
446
        }
447
448
        $values = array_map(function ($attribute) {
449
            return $this->getAttribute($attribute);
450
        }, $attributes);
451
452
        $result = (array)array_combine($attributes, $values);
453
454
        if ($includeRelations) {
455
            foreach ($this->relatedObjects as $relation => $relatedObject) {
456
                if (is_array($relatedObject)) {
457
                    $result[$relation] = array_map(function (Entity $relatedObject) {
458
                        return $relatedObject->toArray();
459
                    }, $relatedObject);
460
                } elseif ($relatedObject instanceof Entity) {
461
                    $result[$relation] = $relatedObject->toArray();
462
                }
463
            }
464
        }
465
466
        return $result;
467
    }
468
469
    /**
470
     * String representation of data
471
     *
472
     * @link http://php.net/manual/en/serializable.serialize.php
473
     * @return string
474
     */
475
    public function serialize()
476
    {
477
        return serialize([ $this->data, $this->relatedObjects ]);
478
    }
479
480
    /**
481
     * Constructs the object
482
     *
483
     * @link http://php.net/manual/en/serializable.unserialize.php
484
     * @param string $serialized The string representation of data
485
     */
486
    public function unserialize($serialized)
487
    {
488
        list($this->data, $this->relatedObjects) = unserialize($serialized);
489
        $this->entityManager = EM::getInstance(static::class);
490
        $this->onInit(false);
491
    }
492
}
493