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 | namespace Fwlib\Db; |
||
3 | |||
4 | use Fwlib\Base\SingleInstanceTrait; |
||
5 | use Fwlib\Bridge\Adodb; |
||
6 | use Fwlib\Db\Exception\InvalidColumnException; |
||
7 | |||
8 | /** |
||
9 | * Code dictionary manager |
||
10 | * |
||
11 | * Eg: code-name table in db. |
||
12 | * |
||
13 | * |
||
14 | * The primary key can only contain ONE column, its used as key for $dict. |
||
15 | * Single primary key should fit most need, or your data are possibly not code |
||
16 | * dictionary. |
||
17 | * |
||
18 | * To support composite primary key, there can extend this class with a |
||
19 | * generateDictIndex() method, the dict data array will be generated from all |
||
20 | * primary key column value. In this scenario it is hard for get() and set() |
||
21 | * method to recognize array param is key of many rows or primary key array, |
||
22 | * so more complicated work to do, maybe not suit for code dictionary. |
||
23 | * |
||
24 | * |
||
25 | * There are 2 way to initialize a code dictionary: |
||
26 | * |
||
27 | * - Use set method for property and dict data |
||
28 | * - Inherit to a child class and set in property define |
||
29 | * |
||
30 | * These 2 way can mixed in use. If dict data is defined and not index by |
||
31 | * primary key, a method will be called in constructor to fix it. This method |
||
32 | * also change column value array to associate array index by column name, so |
||
33 | * the dict array define are as simple as param of set(). |
||
34 | * |
||
35 | * @copyright Copyright 2011-2015 Fwolf |
||
36 | * @license http://www.gnu.org/licenses/lgpl.html LGPL-3.0+ |
||
37 | */ |
||
38 | class CodeDictionary |
||
39 | { |
||
40 | use SingleInstanceTrait; |
||
41 | |||
42 | |||
43 | const COL_CODE = 'code'; |
||
44 | |||
45 | const COL_TITLE = 'title'; |
||
46 | |||
47 | |||
48 | /** |
||
49 | * Columns name, should not be empty |
||
50 | * |
||
51 | * @var array |
||
52 | */ |
||
53 | protected $columns = [self::COL_CODE, self::COL_TITLE]; |
||
54 | |||
55 | /** |
||
56 | * Dictionary data array |
||
57 | * |
||
58 | * @var array |
||
59 | */ |
||
60 | protected $dictionary = []; |
||
61 | |||
62 | /** |
||
63 | * Primary key column name |
||
64 | * |
||
65 | * Primary key column is used to get or search, MUST exist in $column. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | protected $primaryKey = self::COL_CODE; |
||
70 | |||
71 | /** |
||
72 | * Code table name in db |
||
73 | * |
||
74 | * If table name is empty, getSql() will return empty. |
||
75 | * |
||
76 | * @var string |
||
77 | */ |
||
78 | protected $table = 'code_dictionary'; |
||
79 | |||
80 | |||
81 | /** |
||
82 | * Constructor |
||
83 | */ |
||
84 | public function __construct() |
||
85 | { |
||
86 | /** |
||
87 | * Dictionary need fix if: |
||
88 | * |
||
89 | * - Defined with collection of array, without explicit index |
||
90 | * - Key-Value pair, and value is not array |
||
91 | */ |
||
92 | if (!empty($this->dictionary) && |
||
93 | (0 === key($this->dictionary) || |
||
94 | !is_array(current($this->dictionary))) |
||
95 | ) { |
||
96 | $this->fixDictionaryIndex(); |
||
97 | } |
||
98 | } |
||
99 | |||
100 | |||
101 | /** |
||
102 | * Fix dictionary array index |
||
103 | * |
||
104 | * Use primary key value as index of first dimension, and column name as |
||
105 | * index of second dimension(column value array). |
||
106 | */ |
||
107 | protected function fixDictionaryIndex() |
||
108 | { |
||
109 | $dictionary = $this->dictionary; |
||
110 | $this->dictionary = []; |
||
111 | |||
112 | $this->set($dictionary); |
||
113 | } |
||
114 | |||
115 | |||
116 | /** |
||
117 | * Get value for given key |
||
118 | * |
||
119 | * If $columns is array, will use directly without parseColumns(). |
||
120 | * |
||
121 | * Child class can simplify this method to improve speed by avoid parse |
||
122 | * columns, get columns data by index. |
||
123 | * |
||
124 | * @param int|string|array $key |
||
125 | * @param string|array $columns |
||
126 | * @return int|string|array |
||
127 | */ |
||
128 | public function get($key, $columns = '') |
||
129 | { |
||
130 | if (!isset($this->dictionary[$key])) { |
||
131 | return null; |
||
132 | } |
||
133 | |||
134 | $resultColumns = is_array($columns) ? $columns |
||
135 | : $this->parseColumns($columns); |
||
136 | |||
137 | $result = array_intersect_key( |
||
138 | $this->dictionary[$key], |
||
139 | array_fill_keys($resultColumns, null) |
||
140 | ); |
||
141 | |||
142 | // If only have 1 column |
||
143 | if (1 == count($result)) { |
||
144 | $result = array_shift($result); |
||
145 | } |
||
146 | |||
147 | return $result; |
||
148 | } |
||
149 | |||
150 | |||
151 | /** |
||
152 | * Getter of $dictionary |
||
153 | * |
||
154 | * @return array |
||
155 | */ |
||
156 | public function getAll() |
||
157 | { |
||
158 | return $this->dictionary; |
||
159 | } |
||
160 | |||
161 | |||
162 | /** |
||
163 | * @return array |
||
164 | */ |
||
165 | public function getColumns() |
||
166 | { |
||
167 | return $this->columns; |
||
168 | } |
||
169 | |||
170 | |||
171 | /** |
||
172 | * Get value for given keys |
||
173 | * |
||
174 | * @param array $keys |
||
175 | * @param string|array $columns |
||
176 | * @return array |
||
177 | */ |
||
178 | public function getMultiple(array $keys, $columns = '') |
||
179 | { |
||
180 | if (empty($keys)) { |
||
181 | return null; |
||
182 | } |
||
183 | |||
184 | $resultColumns = is_array($columns) ? $columns |
||
185 | : $this->parseColumns($columns); |
||
186 | |||
187 | $result = []; |
||
188 | foreach ($keys as $singleKey) { |
||
189 | $result[$singleKey] = $this->get($singleKey, $resultColumns); |
||
190 | } |
||
191 | |||
192 | return $result; |
||
193 | } |
||
194 | |||
195 | |||
196 | /** |
||
197 | * @return string |
||
198 | */ |
||
199 | public function getPrimaryKey() |
||
200 | { |
||
201 | return $this->primaryKey; |
||
202 | } |
||
203 | |||
204 | |||
205 | /** |
||
206 | * Get key-value map of a single column |
||
207 | * |
||
208 | * The key of result is same, single column value as result value. |
||
209 | * |
||
210 | * @param string $column |
||
211 | * @return array |
||
212 | * @throws InvalidColumnException |
||
213 | */ |
||
214 | public function getSingleColumn($column) |
||
215 | { |
||
216 | if (!in_array($column, $this->columns)) { |
||
217 | throw new InvalidColumnException( |
||
218 | "Invalid column '{$column}'" |
||
219 | ); |
||
220 | } |
||
221 | |||
222 | $result = []; |
||
223 | foreach ($this->dictionary as $key => $row) { |
||
224 | $result[$key] = $row[$column]; |
||
225 | } |
||
226 | |||
227 | return $result; |
||
228 | } |
||
229 | |||
230 | |||
231 | /** |
||
232 | * Get SQL for write dictionary data to db |
||
233 | * |
||
234 | * @param Adodb $dbConn |
||
235 | * @param boolean $withTruncate |
||
236 | * @return string |
||
237 | * @throws \Exception |
||
238 | */ |
||
239 | public function getSql(Adodb $dbConn, $withTruncate = true) |
||
240 | { |
||
241 | if (empty($this->table)) { |
||
242 | return ''; |
||
243 | } |
||
244 | |||
245 | if (!$dbConn->isConnected()) { |
||
246 | throw new \Exception('Database not connected'); |
||
247 | } |
||
248 | |||
249 | |||
250 | // Result sql |
||
251 | $sql = ''; |
||
252 | |||
253 | // Mysql set names |
||
254 | if ($dbConn->isDbMysql()) { |
||
255 | $profile = $dbConn->getProfile(); |
||
256 | $sql .= 'SET NAMES \'' |
||
257 | . str_replace('UTF-8', 'UTF8', strtoupper($profile['lang'])) |
||
258 | . '\'' . $dbConn->getSqlDelimiter(); |
||
259 | } |
||
260 | |||
261 | // Truncate part ? |
||
262 | if ($withTruncate) { |
||
263 | $sql .= $this->getSqlTruncate($dbConn); |
||
264 | } |
||
265 | |||
266 | // Begin transaction |
||
267 | $sql .= $dbConn->getSqlTransBegin(); |
||
268 | |||
269 | // Data |
||
270 | // INSERT INTO table (col1, col2) VALUES (val1, val2)[DELIMITER] |
||
271 | foreach ($this->dictionary as $row) { |
||
272 | $valueList = []; |
||
273 | foreach ($row as $key => $val) { |
||
274 | $valueList[] = $dbConn->quoteValue($this->table, $key, $val); |
||
275 | } |
||
276 | |||
277 | $sql .= 'INSERT INTO ' . $this->table |
||
278 | . ' (' . implode(', ', $this->columns) . ')' |
||
279 | . ' VALUES (' . implode(', ', $valueList) . ')' |
||
280 | . $dbConn->getSqlDelimiter(); |
||
281 | } |
||
282 | |||
283 | // End transaction |
||
284 | $sql .= $dbConn->getSqlTransCommit(); |
||
285 | |||
286 | return $sql; |
||
287 | } |
||
288 | |||
289 | |||
290 | /** |
||
291 | * Get SQL for write dictionary data to db, truncate part. |
||
292 | * |
||
293 | * @param object $dbConn Fwlib\Bridge\Adodb |
||
294 | * @return string |
||
295 | */ |
||
296 | public function getSqlTruncate($dbConn) |
||
297 | { |
||
298 | $sql = 'TRUNCATE TABLE ' . $this->table |
||
299 | . $dbConn->getSqlDelimiter(); |
||
300 | |||
301 | if (!$dbConn->isDbSybase()) { |
||
302 | $sql = $dbConn->getSqlTransBegin() . $sql . |
||
303 | $dbConn->getSqlTransCommit(); |
||
304 | } |
||
305 | |||
306 | return $sql; |
||
307 | } |
||
308 | |||
309 | |||
310 | /** |
||
311 | * @return string |
||
312 | */ |
||
313 | public function getTable() |
||
314 | { |
||
315 | return $this->table; |
||
316 | } |
||
317 | |||
318 | |||
319 | /** |
||
320 | * Parse columns you want to query |
||
321 | * |
||
322 | * If $column not assigned, assign as first col which is not primary key. |
||
323 | * |
||
324 | * Use '*' for all columns. |
||
325 | * |
||
326 | * @param string|array $column |
||
327 | * @return array |
||
328 | */ |
||
329 | protected function parseColumns($column = '') |
||
330 | { |
||
331 | if ('*' == $column) { |
||
332 | $result = $this->columns; |
||
333 | |||
334 | } elseif (empty($column)) { |
||
335 | // Assign first col not pk |
||
336 | $columnWithoutPk = array_diff( |
||
337 | $this->columns, |
||
338 | (array)$this->primaryKey |
||
339 | ); |
||
340 | $result = [array_shift($columnWithoutPk)]; |
||
341 | |||
342 | } else { |
||
343 | // Find valid columns |
||
344 | if (is_string($column)) { |
||
345 | $column = explode(',', $column); |
||
346 | array_walk($column, 'trim'); |
||
347 | } |
||
348 | $result = array_intersect($column, $this->columns); |
||
349 | } |
||
350 | |||
351 | return $result; |
||
352 | } |
||
353 | |||
354 | |||
355 | /** |
||
356 | * Search for data fit given condition |
||
357 | * |
||
358 | * $checkMethod is a function take $row as parameter and return boolean |
||
359 | * value, can be anonymous function or other callable. |
||
360 | * |
||
361 | * @param callable $checkMethod |
||
362 | * @param string|array $columns |
||
363 | * @return array |
||
364 | */ |
||
365 | public function search($checkMethod, $columns = '*') |
||
366 | { |
||
367 | if (empty($this->dictionary)) { |
||
368 | return []; |
||
369 | } |
||
370 | |||
371 | $resultColumns = is_array($columns) ? $columns |
||
372 | : $this->parseColumns($columns); |
||
373 | |||
374 | $results = []; |
||
375 | foreach ($this->dictionary as $index => $row) { |
||
376 | if ($checkMethod($row)) { |
||
377 | $results[$index] = $this->get($index, $resultColumns); |
||
378 | } |
||
379 | } |
||
380 | |||
381 | return $results; |
||
382 | } |
||
383 | |||
384 | |||
385 | /** |
||
386 | * Set dictionary value |
||
387 | * |
||
388 | * @param array $data 1 or 2-dim data array. |
||
389 | * @return CodeDictionary |
||
390 | * @throws \Exception |
||
391 | */ |
||
392 | public function set(array $data) |
||
393 | { |
||
394 | if (empty($data)) { |
||
395 | return $this; |
||
396 | } |
||
397 | |||
398 | if (empty($this->columns)) { |
||
399 | throw new \Exception('Dictionary column not defined'); |
||
400 | } |
||
401 | |||
402 | if (!in_array($this->primaryKey, $this->columns)) { |
||
403 | throw new \Exception( |
||
404 | 'Defined columns did not include primary key' |
||
405 | ); |
||
406 | } |
||
407 | |||
408 | // Convert 1-dim to 2-dim |
||
409 | if (!is_array(current($data))) { |
||
410 | $data = [$data]; |
||
411 | } |
||
412 | |||
413 | |||
414 | foreach ($data as &$row) { |
||
415 | try { |
||
416 | $columnValueArray = array_combine( |
||
417 | $this->columns, |
||
418 | $row |
||
419 | ); |
||
420 | } catch (\Exception $e) { |
||
421 | throw new \Exception( |
||
422 | 'Given data did not contain all columns' |
||
423 | ); |
||
424 | } |
||
425 | |||
426 | $primaryKeyValue = $columnValueArray[$this->primaryKey]; |
||
427 | |||
428 | if (empty($primaryKeyValue)) { |
||
429 | throw new \Exception( |
||
430 | 'Primary key value is empty or not set' |
||
431 | ); |
||
432 | } |
||
433 | |||
434 | $this->dictionary[$primaryKeyValue] = $columnValueArray; |
||
435 | } |
||
436 | unset($row); |
||
437 | |||
438 | return $this; |
||
439 | } |
||
440 | |||
441 | |||
442 | /** |
||
443 | * Setter of $columns |
||
444 | * |
||
445 | * @param array $columns |
||
446 | * @return CodeDictionary |
||
447 | */ |
||
448 | public function setColumns(array $columns) |
||
449 | { |
||
450 | $this->columns = $columns; |
||
451 | |||
452 | return $this; |
||
453 | } |
||
454 | |||
455 | |||
456 | /** |
||
457 | * Setter of $primaryKey |
||
458 | * |
||
459 | * @param string|array $primaryKey |
||
460 | * @return CodeDictionary |
||
461 | */ |
||
462 | public function setPrimaryKey($primaryKey) |
||
463 | { |
||
464 | $this->primaryKey = $primaryKey; |
||
0 ignored issues
–
show
|
|||
465 | |||
466 | return $this; |
||
467 | } |
||
468 | |||
469 | |||
470 | /** |
||
471 | * Setter of $table |
||
472 | * |
||
473 | * @param string $table |
||
474 | * @return CodeDictionary |
||
475 | */ |
||
476 | public function setTable($table) |
||
477 | { |
||
478 | $this->table = $table; |
||
479 | |||
480 | return $this; |
||
481 | } |
||
482 | } |
||
483 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.