This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Starlit Db. |
||
4 | * |
||
5 | * @copyright Copyright (c) 2016 Starweb AB |
||
6 | * @license BSD 3-Clause |
||
7 | */ |
||
8 | |||
9 | namespace Starlit\Db; |
||
10 | |||
11 | use Starlit\Db\Exception; |
||
12 | |||
13 | /** |
||
14 | * A database entity service is intended to handle database operations for existing |
||
15 | * entity objects, i.e. load, save and load secondary objects etc. |
||
16 | * |
||
17 | * @author Andreas Nilsson <http://github.com/jandreasn> |
||
18 | */ |
||
19 | class BasicDbEntityService |
||
20 | { |
||
21 | /** |
||
22 | * The database adapter/connection/handler. |
||
23 | * |
||
24 | * @var Db |
||
25 | */ |
||
26 | protected $db; |
||
27 | |||
28 | /** |
||
29 | * Constructor. |
||
30 | * |
||
31 | * @param Db $db |
||
32 | */ |
||
33 | 15 | public function __construct(Db $db) |
|
34 | { |
||
35 | 15 | $this->db = $db; |
|
36 | 15 | } |
|
37 | |||
38 | /** |
||
39 | * Load object's values from database table. |
||
40 | * |
||
41 | * @param AbstractDbEntity $dbEntity |
||
42 | * @throws Exception\EntityNotFoundException |
||
43 | */ |
||
44 | 3 | public function load(AbstractDbEntity $dbEntity) |
|
45 | { |
||
46 | // Check that the primary key is set |
||
47 | 3 | if (!$dbEntity->getPrimaryDbValue()) { |
|
48 | 1 | throw new \InvalidArgumentException( |
|
49 | 1 | 'Database entity can not be loaded because primary value is not set' |
|
50 | ); |
||
51 | } |
||
52 | |||
53 | // Fetch ad from db |
||
54 | 2 | $row = $this->db->fetchRow( |
|
55 | ' |
||
56 | SELECT * |
||
57 | 2 | FROM `' . $dbEntity->getDbTableName() . '` |
|
58 | 2 | WHERE ' . $this->getPrimaryKeyWhereSql($dbEntity) . ' |
|
59 | ', |
||
60 | 2 | $this->getPrimaryKeyWhereParameters($dbEntity) |
|
61 | ); |
||
62 | |||
63 | 2 | if (!$row) { |
|
64 | 1 | throw new Exception\EntityNotFoundException("Db entity[{$dbEntity->getPrimaryDbValue()}] does not exist"); |
|
65 | } |
||
66 | |||
67 | 1 | $dbEntity->setDbDataFromRow($row); |
|
68 | 1 | } |
|
69 | |||
70 | /** |
||
71 | * Save entity to database table. |
||
72 | * |
||
73 | * @param AbstractDbEntity $dbEntity |
||
74 | * @return bool |
||
75 | */ |
||
76 | 10 | public function save(AbstractDbEntity $dbEntity) |
|
77 | { |
||
78 | 10 | if ($dbEntity->shouldBeDeletedFromDbOnSave()) { |
|
79 | // Only delete if previously saved to db |
||
80 | 2 | if ($dbEntity->getPrimaryDbValue()) { |
|
81 | 1 | $this->delete($dbEntity); |
|
82 | } |
||
83 | |||
84 | 2 | return false; |
|
85 | } |
||
86 | |||
87 | 8 | if ($dbEntity->shouldInsertOnDbSave()) { |
|
88 | // Note that database data always contains all properties, |
||
89 | // with defaults for non set properties |
||
90 | 5 | $dataToSave = $dbEntity->getDbData(); |
|
91 | } else { |
||
92 | 3 | if ($dbEntity->hasModifiedDbProperties()) { |
|
93 | 2 | $dataToSave = $dbEntity->getModifiedDbData(); |
|
94 | } else { |
||
95 | // Return if no value has been modified and it's not an insert |
||
96 | // (we always want to insert if no id exist, since some child objects might |
||
97 | // depend on the this primary id being available) |
||
98 | 1 | return false; |
|
99 | } |
||
100 | } |
||
101 | |||
102 | // Check data |
||
103 | 7 | $sqlData = []; |
|
104 | 7 | foreach ($dataToSave as $propertyName => $value) { |
|
105 | 7 | if (!empty($value) && is_scalar($value) |
|
106 | 7 | && $dbEntity->getDbPropertyMaxLength($propertyName) |
|
107 | 7 | && mb_strlen($value) > $dbEntity->getDbPropertyMaxLength($propertyName) |
|
108 | ) { |
||
109 | 1 | throw new \RuntimeException( |
|
110 | 1 | "Database field \"{$propertyName}\" exceeds field max length (value: \"{$value}\")" |
|
111 | ); |
||
112 | 7 | } elseif (empty($value) && $dbEntity->getDbPropertyNonEmpty($propertyName)) { |
|
113 | 1 | throw new \RuntimeException("Database field \"{$propertyName}\" is empty and required"); |
|
114 | 7 | } elseif (((is_scalar($value) && ((string) $value) === '') || (!is_scalar($value) && empty($value))) |
|
115 | 7 | && $dbEntity->getDbPropertyRequired($propertyName) |
|
116 | ) { |
||
117 | 2 | throw new \RuntimeException("Database field \"{$propertyName}\" is required to be set"); |
|
118 | } |
||
119 | |||
120 | // Set data keys db field format |
||
121 | 7 | $fieldName = $dbEntity->getDbFieldName($propertyName); |
|
122 | 7 | $sqlData[$fieldName] = $value; |
|
123 | } |
||
124 | |||
125 | |||
126 | // Insert new database row |
||
127 | 3 | if ($dbEntity->shouldInsertOnDbSave()) { |
|
128 | 1 | $this->db->insert( |
|
129 | 1 | $dbEntity->getDbTableName(), |
|
130 | 1 | $sqlData |
|
131 | ); |
||
132 | |||
133 | // Update entity with auto increment value |
||
134 | 1 | if (!is_array($dbEntity->getPrimaryDbPropertyKey()) |
|
135 | 1 | && !empty($lastInsertId = $this->db->getLastInsertId()) |
|
136 | ) { |
||
137 | $dbEntity->setPrimaryDbValue($lastInsertId); |
||
138 | } else { |
||
139 | // For entities with for example string primary keys, we update primary value |
||
140 | // with value/values from db data, indicating that the entity is now |
||
141 | // in database/loaded. |
||
142 | 1 | $dbEntity->updatePrimaryDbValueFromDbData(); |
|
143 | } |
||
144 | // Update existing database row |
||
145 | } else { |
||
146 | 2 | $this->db->update( |
|
147 | 2 | $dbEntity->getDbTableName(), |
|
148 | 2 | $sqlData, |
|
149 | 2 | $this->getPrimaryKeyWhereSql($dbEntity), |
|
150 | 2 | $this->getPrimaryKeyWhereParameters($dbEntity) |
|
151 | ); |
||
152 | } |
||
153 | |||
154 | 3 | $dbEntity->clearModifiedDbProperties(); |
|
155 | 3 | $dbEntity->setForceDbInsertOnSave(false); |
|
156 | |||
157 | 3 | return true; |
|
158 | } |
||
159 | |||
160 | /** |
||
161 | * Delete entity's corresponding database row. |
||
162 | * |
||
163 | * @param AbstractDbEntity $dbEntity |
||
164 | */ |
||
165 | 3 | public function delete(AbstractDbEntity $dbEntity) |
|
166 | { |
||
167 | 3 | if (!$dbEntity->getPrimaryDbValue()) { |
|
168 | 1 | throw new \InvalidArgumentException('Primary database value not set'); |
|
169 | } |
||
170 | |||
171 | 2 | $this->db->exec( |
|
172 | 2 | 'DELETE FROM `' . $dbEntity->getDbTableName() . '` WHERE ' . $this->getPrimaryKeyWhereSql($dbEntity), |
|
173 | 2 | $this->getPrimaryKeyWhereParameters($dbEntity) |
|
174 | ); |
||
175 | |||
176 | 2 | $dbEntity->setDeleted(); |
|
177 | 2 | } |
|
178 | |||
179 | /** |
||
180 | * @param AbstractDbEntity $dbEntity |
||
181 | * @return string |
||
182 | */ |
||
183 | 6 | protected function getPrimaryKeyWhereSql(AbstractDbEntity $dbEntity) |
|
184 | { |
||
185 | 6 | if (is_array($dbEntity->getPrimaryDbPropertyKey())) { |
|
186 | 1 | $whereStrings = []; |
|
187 | 1 | foreach ($dbEntity->getPrimaryDbFieldKey() as $primaryKeyPart) { |
|
0 ignored issues
–
show
|
|||
188 | 1 | $whereStrings[] = '`' . $primaryKeyPart . '` = ?'; |
|
189 | } |
||
190 | 1 | $whereSql = implode(' AND ', $whereStrings); |
|
191 | } else { |
||
192 | 5 | $whereSql = '`' . $dbEntity->getPrimaryDbFieldKey() . '` = ?'; |
|
193 | } |
||
194 | |||
195 | 6 | return $whereSql; |
|
196 | } |
||
197 | |||
198 | /** |
||
199 | * @param AbstractDbEntity $dbEntity |
||
200 | * @return array |
||
201 | */ |
||
202 | 6 | protected function getPrimaryKeyWhereParameters(AbstractDbEntity $dbEntity) |
|
203 | { |
||
204 | 6 | if (is_array($dbEntity->getPrimaryDbPropertyKey())) { |
|
205 | 1 | $whereParameters = $dbEntity->getPrimaryDbValue(); |
|
206 | } else { |
||
207 | 5 | $whereParameters = [$dbEntity->getPrimaryDbValue()]; |
|
208 | } |
||
209 | |||
210 | 6 | return $whereParameters; |
|
211 | } |
||
212 | } |
||
213 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.