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 | declare(strict_types=1); |
||
3 | |||
4 | namespace Crossjoin\Browscap\Parser\Sqlite; |
||
5 | |||
6 | use Crossjoin\Browscap\Exception\ParserConditionNotSatisfiedException; |
||
7 | use Crossjoin\Browscap\Exception\ParserRuntimeException; |
||
8 | use Crossjoin\Browscap\Exception\UnexpectedValueException; |
||
9 | use Crossjoin\Browscap\Parser\ReaderInterface; |
||
10 | use Crossjoin\Browscap\Parser\Sqlite\Adapter\AdapterFactory; |
||
11 | use Crossjoin\Browscap\Parser\Sqlite\Adapter\AdapterInterface; |
||
12 | use Crossjoin\Browscap\PropertyFilter\PropertyFilterTrait; |
||
13 | use Crossjoin\Browscap\Source\Ini\GetRegExpForPatternTrait; |
||
14 | use Crossjoin\Browscap\Type; |
||
15 | |||
16 | /** |
||
17 | * Class Reader |
||
18 | * |
||
19 | * @package Crossjoin\Browscap\Parser\Sqlite |
||
20 | * @author Christoph Ziegenberg <[email protected]> |
||
21 | * @link https://github.com/crossjoin/browscap |
||
22 | */ |
||
23 | class Reader implements ReaderInterface |
||
24 | { |
||
25 | use DataDirectoryTrait; |
||
26 | use DataVersionHashTrait; |
||
27 | use PropertyFilterTrait; |
||
28 | use GetRegExpForPatternTrait; |
||
29 | |||
30 | /** |
||
31 | * @var AdapterInterface |
||
32 | */ |
||
33 | protected $adapter; |
||
34 | |||
35 | /** |
||
36 | * @var string |
||
37 | */ |
||
38 | protected $databaseFile; |
||
39 | |||
40 | /** |
||
41 | * @var int |
||
42 | */ |
||
43 | protected $browserId; |
||
44 | |||
45 | /** |
||
46 | * @var int |
||
47 | */ |
||
48 | protected $browserParentId; |
||
49 | |||
50 | /** |
||
51 | * @var array |
||
52 | */ |
||
53 | protected $browserPatternKeywords; |
||
54 | |||
55 | /** |
||
56 | * @var string |
||
57 | */ |
||
58 | protected $sqliteVersion; |
||
59 | |||
60 | /** |
||
61 | * Writer constructor. |
||
62 | * |
||
63 | * @param string $dataDirectory |
||
64 | * |
||
65 | * @throws ParserConditionNotSatisfiedException |
||
66 | */ |
||
67 | public function __construct(string $dataDirectory) |
||
68 | { |
||
69 | $this->setDataDirectory($dataDirectory); |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * @inheritdoc |
||
74 | * |
||
75 | * @throws ParserConditionNotSatisfiedException |
||
76 | * @throws ParserRuntimeException |
||
77 | * @throws UnexpectedValueException |
||
78 | */ |
||
79 | View Code Duplication | protected function getAdapter() : AdapterInterface |
|
80 | { |
||
81 | if ($this->adapter === null) { |
||
82 | $databaseFile = $this->getDatabasePath(); |
||
83 | $adapter = AdapterFactory::getInstance($databaseFile); |
||
84 | $this->setAdapter($adapter); |
||
85 | } |
||
86 | |||
87 | return $this->adapter; |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * @return string |
||
92 | * |
||
93 | * @throws ParserRuntimeException |
||
94 | */ |
||
95 | protected function getDatabasePath() : string |
||
96 | { |
||
97 | $databasePath = $this->getDataDirectory() . DIRECTORY_SEPARATOR . $this->getDatabaseFileName(); |
||
98 | |||
99 | if (!$this->isFileReadable($databasePath)) { |
||
100 | if (!file_exists($databasePath)) { |
||
101 | throw new ParserRuntimeException( |
||
102 | "Linked database file '$databasePath' not found. Parser data need to be generated.", |
||
0 ignored issues
–
show
|
|||
103 | 1458898365 |
||
104 | ); |
||
105 | } else { |
||
106 | throw new ParserRuntimeException("Linked database file '$databasePath' is not readable.", 1458898366); |
||
0 ignored issues
–
show
As per coding-style, please use concatenation or
sprintf for the variable $databasePath instead of interpolation.
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings. // Instead of
$x = "foo $bar $baz";
// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
![]() |
|||
107 | } |
||
108 | } elseif (!is_file($databasePath)) { |
||
109 | throw new ParserRuntimeException("Invalid database file name '$databasePath' in link file.", 1458898367); |
||
0 ignored issues
–
show
As per coding-style, please use concatenation or
sprintf for the variable $databasePath instead of interpolation.
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings. // Instead of
$x = "foo $bar $baz";
// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
![]() |
|||
110 | } |
||
111 | |||
112 | return $databasePath; |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * @return string |
||
117 | * |
||
118 | * @throws ParserRuntimeException |
||
119 | */ |
||
120 | protected function getDatabaseFileName() : string |
||
121 | { |
||
122 | // Get database to use, saved in the link file (as symlinks are not available or only |
||
123 | // with admin permissions on Windows). |
||
124 | $linkFile = $this->getDataDirectory() . DIRECTORY_SEPARATOR . Parser::LINK_FILENAME; |
||
125 | if ($this->isFileReadable($linkFile)) { |
||
126 | return (string)file_get_contents($linkFile); |
||
127 | } elseif (!file_exists($linkFile)) { |
||
128 | throw new ParserRuntimeException( |
||
129 | "Database link file '$linkFile' not found. Parser data need to be generated.", |
||
0 ignored issues
–
show
As per coding-style, please use concatenation or
sprintf for the variable $linkFile instead of interpolation.
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings. // Instead of
$x = "foo $bar $baz";
// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
![]() |
|||
130 | 1458898368 |
||
131 | ); |
||
132 | } else { |
||
133 | throw new ParserRuntimeException("Database link file '$linkFile' is not readable.", 1458898369); |
||
0 ignored issues
–
show
As per coding-style, please use concatenation or
sprintf for the variable $linkFile instead of interpolation.
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings. // Instead of
$x = "foo $bar $baz";
// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
![]() |
|||
134 | } |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * @param $file |
||
139 | * |
||
140 | * @return bool |
||
141 | */ |
||
142 | protected function isFileReadable(string $file) : bool |
||
143 | { |
||
144 | return is_readable($file); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * @param AdapterInterface $adapter |
||
149 | */ |
||
150 | protected function setAdapter(AdapterInterface $adapter) |
||
151 | { |
||
152 | $this->adapter = $adapter; |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * @inheritdoc |
||
157 | * |
||
158 | * @throws UnexpectedValueException |
||
159 | */ |
||
160 | public function isUpdateRequired() : bool |
||
161 | { |
||
162 | try { |
||
163 | $query = 'SELECT data_hash FROM info LIMIT 1'; |
||
164 | $dataHash = ''; |
||
165 | foreach ($this->getAdapter()->query($query) as $row) { |
||
166 | $dataHash = $row['data_hash']; |
||
167 | } |
||
168 | $return = ($dataHash !== $this->getDataVersionHash()); |
||
169 | } catch (ParserConditionNotSatisfiedException $e) { |
||
170 | $return = true; |
||
171 | } catch (ParserRuntimeException $e) { |
||
172 | $return = true; |
||
173 | } |
||
174 | |||
175 | return $return; |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * @inheritdoc |
||
180 | * |
||
181 | * @throws ParserConditionNotSatisfiedException |
||
182 | * @throws ParserRuntimeException |
||
183 | * @throws UnexpectedValueException |
||
184 | */ |
||
185 | public function getReleaseTime() : int |
||
186 | { |
||
187 | $query = 'SELECT release_time FROM info LIMIT 1'; |
||
188 | |||
189 | $releaseTime = 0; |
||
190 | foreach ($this->getAdapter()->query($query) as $row) { |
||
191 | $releaseTime = (int)$row['release_time']; |
||
192 | } |
||
193 | |||
194 | return $releaseTime; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * @inheritdoc |
||
199 | * |
||
200 | * @throws ParserConditionNotSatisfiedException |
||
201 | * @throws ParserRuntimeException |
||
202 | * @throws UnexpectedValueException |
||
203 | */ |
||
204 | public function getVersion() : int |
||
205 | { |
||
206 | $query = 'SELECT version_id FROM info LIMIT 1'; |
||
207 | |||
208 | $versionId = 0; |
||
209 | foreach ($this->getAdapter()->query($query) as $row) { |
||
210 | $versionId = (int)$row['version_id']; |
||
211 | } |
||
212 | |||
213 | return $versionId; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * @inheritdoc |
||
218 | * |
||
219 | * @throws ParserConditionNotSatisfiedException |
||
220 | * @throws ParserRuntimeException |
||
221 | * @throws UnexpectedValueException |
||
222 | */ |
||
223 | public function getType() : int |
||
224 | { |
||
225 | $query = 'SELECT type_id FROM info LIMIT 1'; |
||
226 | |||
227 | $typeId = Type::UNKNOWN; |
||
228 | foreach ($this->getAdapter()->query($query) as $row) { |
||
229 | $typeId = (int)$row['type_id']; |
||
230 | } |
||
231 | |||
232 | return $typeId; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * @inheritdoc |
||
237 | * |
||
238 | * @throws ParserConditionNotSatisfiedException |
||
239 | * @throws ParserRuntimeException |
||
240 | * @throws UnexpectedValueException |
||
241 | */ |
||
242 | public function getBrowser(string $userAgent) : array |
||
243 | { |
||
244 | // Init variables |
||
245 | $browserId = $this->getBrowserId($userAgent); |
||
246 | $browserParentId = $this->getBrowserParentId($userAgent); |
||
247 | |||
248 | // Get all settings for the found browser |
||
249 | // |
||
250 | // It's best to use a recursive query here, but some linux distributions like CentOS and RHEL |
||
251 | // use old sqlite library versions (like 3.6.20 or 3.7.17), so that we have to check the |
||
252 | // version here and fall back to multiple single queries in case of older versions. |
||
253 | if (version_compare($this->getSqliteVersion(), '3.8.3', '>=')) { |
||
254 | $query = 'SELECT t3.property_key, t4.property_value FROM ('; |
||
255 | $query .= 'WITH RECURSIVE browser_recursive(browser_id,browser_parent_id) AS ('; |
||
256 | $query .= 'SELECT browser_id, browser_parent_id FROM browser WHERE browser_id = :id'; |
||
257 | $query .= ' UNION ALL '; |
||
258 | $query .= 'SELECT browser.browser_id, browser.browser_parent_id FROM browser, browser_recursive '; |
||
259 | $query .= 'WHERE browser_recursive.browser_parent_id IS NOT NULL '; |
||
260 | $query .= 'AND browser.browser_id = browser_recursive.browser_parent_id'; |
||
261 | $query .= ') '; |
||
262 | $query .= 'SELECT MAX(t2.browser_id) AS browser_id, t2.property_key_id FROM browser_recursive t1 '; |
||
263 | $query .= 'JOIN browser_property t2 ON t2.browser_id = t1.browser_id '; |
||
264 | $query .= 'GROUP BY t2.property_key_id'; |
||
265 | $query .= ') t1 '; |
||
266 | $query .= 'JOIN browser_property t2 ON t2.browser_id = t1.browser_id '; |
||
267 | $query .= 'AND t2.property_key_id = t1.property_key_id '; |
||
268 | $query .= 'JOIN browser_property_key t3 ON t3.property_key_id = t2.property_key_id '; |
||
269 | $query .= 'JOIN browser_property_value t4 ON t4.property_value_id = t2.property_value_id'; |
||
270 | $query .= ' UNION ALL '; |
||
271 | $query .= 'SELECT \'browser_name_pattern\' AS property_key, browser_pattern AS property_value '; |
||
272 | $query .= 'FROM browser WHERE browser_id = :id'; |
||
273 | $query .= ' UNION ALL '; |
||
274 | $query .= 'SELECT \'Parent\' AS property_key, browser_pattern AS property_value '; |
||
275 | $query .= 'FROM browser WHERE browser_id = :parent'; |
||
276 | $statement = $this->getAdapter()->prepare($query); |
||
277 | } else { |
||
278 | $query = 'SELECT browser_id, browser_parent_id FROM browser WHERE browser_id = :id'; |
||
279 | $statement = $this->getAdapter()->prepare($query); |
||
280 | $browserIds = []; |
||
281 | $lastBrowserId = $browserId; |
||
282 | while ($lastBrowserId !== null) { |
||
283 | $result = $statement->execute(['id' => $lastBrowserId]); |
||
284 | |||
285 | $lastBrowserId = null; |
||
286 | if (count($result) > 0) { |
||
287 | $browserIds[] = (int)$result[0]['browser_id']; |
||
288 | if ($result[0]['browser_parent_id'] !== null) { |
||
289 | $lastBrowserId = (int)$result[0]['browser_parent_id']; |
||
290 | } |
||
291 | } |
||
292 | } |
||
293 | |||
294 | $query = 'SELECT t3.property_key, t4.property_value FROM ('; |
||
295 | $query .= 'SELECT MAX(browser_id) AS browser_id, property_key_id '; |
||
296 | $query .= 'FROM browser_property WHERE browser_id IN (' . implode(', ', $browserIds) . ') '; |
||
297 | $query .= 'GROUP BY property_key_id'; |
||
298 | $query .= ') t1 '; |
||
299 | $query .= 'JOIN browser_property t2 ON t2.browser_id = t1.browser_id '; |
||
300 | $query .= 'AND t2.property_key_id = t1.property_key_id '; |
||
301 | $query .= 'JOIN browser_property_key t3 ON t3.property_key_id = t2.property_key_id '; |
||
302 | $query .= 'JOIN browser_property_value t4 ON t4.property_value_id = t2.property_value_id'; |
||
303 | $query .= ' UNION ALL '; |
||
304 | $query .= 'SELECT \'browser_name_pattern\' AS property_key, browser_pattern AS property_value '; |
||
305 | $query .= 'FROM browser WHERE browser_id = :id'; |
||
306 | $query .= ' UNION ALL '; |
||
307 | $query .= 'SELECT \'Parent\' AS property_key, browser_pattern AS property_value '; |
||
308 | $query .= 'FROM browser WHERE browser_id = :parent'; |
||
309 | $statement = $this->getAdapter()->prepare($query); |
||
310 | } |
||
311 | |||
312 | $properties = []; |
||
313 | foreach ($statement->execute(['id' => $browserId, 'parent' => $browserParentId]) as $row) { |
||
314 | $properties[$row['property_key']] = $row['property_value']; |
||
315 | } |
||
316 | |||
317 | // Set regular expression properties |
||
318 | $propertyFilter = $this->getPropertyFilter(); |
||
319 | if (!$propertyFilter->isFiltered('browser_name_regex')) { |
||
320 | $properties['browser_name_regex'] = $this->getRegExpForPattern($properties['browser_name_pattern']); |
||
321 | } |
||
322 | if ($propertyFilter->isFiltered('browser_name_pattern')) { |
||
323 | unset($properties['browser_name_pattern']); |
||
324 | } |
||
325 | if ($propertyFilter->isFiltered('Parent')) { |
||
326 | unset($properties['Parent']); |
||
327 | } |
||
328 | |||
329 | // IMPORTANT: Reset browserId and browserParentId for next call |
||
330 | $this->browserId = null; |
||
331 | $this->browserParentId = null; |
||
332 | |||
333 | // The settings are in random order, so sort them |
||
334 | return $this->sortProperties($properties); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * @return string |
||
339 | * @throws \Crossjoin\Browscap\Exception\UnexpectedValueException |
||
340 | * @throws \Crossjoin\Browscap\Exception\ParserConditionNotSatisfiedException |
||
341 | */ |
||
342 | protected function getSqliteVersion(): string |
||
343 | { |
||
344 | if ($this->sqliteVersion === null) { |
||
345 | |||
346 | $this->sqliteVersion = '0.0.0'; |
||
347 | |||
348 | // Try to get the version number |
||
349 | $query = 'SELECT sqlite_version() AS version'; |
||
350 | try { |
||
351 | $result = $this->getAdapter()->query($query); |
||
352 | if (count($result) > 0) { |
||
353 | $this->sqliteVersion = $result[0]['version']; |
||
354 | } |
||
355 | } catch (ParserRuntimeException $exception) { |
||
356 | // Use default if version could not be detected |
||
357 | } |
||
358 | } |
||
359 | |||
360 | return $this->sqliteVersion; |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * @param string $userAgent |
||
365 | * |
||
366 | * @return int |
||
367 | * @throws ParserConditionNotSatisfiedException |
||
368 | * @throws ParserRuntimeException |
||
369 | * @throws UnexpectedValueException |
||
370 | */ |
||
371 | protected function getBrowserId(string $userAgent) : int |
||
372 | { |
||
373 | if ($this->browserId === null) { |
||
374 | $this->findBrowser($userAgent); |
||
375 | } |
||
376 | |||
377 | return $this->browserId; |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * @param string $userAgent |
||
382 | * |
||
383 | * @return int |
||
384 | * @throws ParserConditionNotSatisfiedException |
||
385 | * @throws ParserRuntimeException |
||
386 | * @throws UnexpectedValueException |
||
387 | */ |
||
388 | protected function getBrowserParentId(string $userAgent) : int |
||
389 | { |
||
390 | if ($this->browserParentId === null) { |
||
391 | $this->findBrowser($userAgent); |
||
392 | } |
||
393 | |||
394 | return $this->browserParentId; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * @param string $userAgent |
||
399 | * |
||
400 | * @throws ParserConditionNotSatisfiedException |
||
401 | * @throws ParserRuntimeException |
||
402 | * @throws UnexpectedValueException |
||
403 | */ |
||
404 | protected function findBrowser(string $userAgent) |
||
405 | { |
||
406 | // Check each keyword table for the browser pattern |
||
407 | $this->findBrowserInKeywordTables($userAgent); |
||
408 | |||
409 | // If no match found in keyword tables, check the default table |
||
410 | // (this also includes the '*' pattern for the default match). |
||
411 | if ($this->browserId === null) { |
||
412 | $this->findBrowserInDefaultTable($userAgent); |
||
413 | } |
||
414 | |||
415 | // Check if data found (the last step should always find the default settings) |
||
416 | if ($this->browserId === null) { |
||
417 | throw new ParserRuntimeException( |
||
418 | "No result found for user agent '$userAgent'. There seems to be something wrong with the data." |
||
0 ignored issues
–
show
As per coding-style, please use concatenation or
sprintf for the variable $userAgent instead of interpolation.
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings. // Instead of
$x = "foo $bar $baz";
// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
![]() |
|||
419 | ); |
||
420 | } |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * @param string $userAgent |
||
425 | * @throws ParserConditionNotSatisfiedException |
||
426 | * @throws ParserRuntimeException |
||
427 | * @throws UnexpectedValueException |
||
428 | */ |
||
429 | protected function findBrowserInKeywordTables(string $userAgent) |
||
430 | { |
||
431 | $query = 'SELECT browser_id, browser_pattern_length FROM "search_[keyword]" '; |
||
432 | $query .= 'WHERE browser_pattern_length >= :length AND :agent GLOB browser_pattern'; |
||
433 | |||
434 | $userAgentLowered = strtolower($userAgent); |
||
435 | $maxLength = 0; |
||
436 | $browserId = null; |
||
437 | foreach ($this->getPatternKeywords() as $patternKeyword) { |
||
438 | if (strpos($userAgentLowered, $patternKeyword) !== false) { |
||
439 | $statement = $this->getAdapter()->prepare(str_replace('[keyword]', $patternKeyword, $query)); |
||
440 | |||
441 | /** @noinspection PdoApiUsageInspection */ |
||
442 | foreach ($statement->execute(['length' => $maxLength, 'agent' => $userAgentLowered]) as $row) { |
||
443 | $tmpLength = (int)$row['browser_pattern_length']; |
||
444 | $tmpBrowserId = (int)$row['browser_id']; |
||
445 | |||
446 | if ($tmpLength < $maxLength) { |
||
447 | continue; // @codeCoverageIgnore |
||
448 | } |
||
449 | if ($tmpLength === $maxLength && $browserId !== null && $tmpBrowserId < $browserId) { |
||
450 | continue; // @codeCoverageIgnore |
||
451 | } |
||
452 | |||
453 | $browserId = (int)$row['browser_id']; |
||
454 | $maxLength = $tmpLength; |
||
455 | } |
||
456 | } |
||
457 | } |
||
458 | |||
459 | if ($browserId !== null) { |
||
460 | $this->browserId = $browserId; |
||
461 | |||
462 | $query = 'SELECT browser_parent_id FROM browser WHERE browser_id = :id'; |
||
463 | $statement = $this->getAdapter()->prepare($query); |
||
464 | /** @noinspection PdoApiUsageInspection */ |
||
465 | foreach ($statement->execute(['id' => $browserId]) as $row) { |
||
466 | $this->browserParentId = (int)$row['browser_parent_id']; |
||
467 | } |
||
468 | } |
||
469 | } |
||
470 | |||
471 | /** |
||
472 | * @param string $userAgent |
||
473 | * |
||
474 | * @throws ParserConditionNotSatisfiedException |
||
475 | * @throws ParserRuntimeException |
||
476 | * @throws UnexpectedValueException |
||
477 | */ |
||
478 | protected function findBrowserInDefaultTable(string $userAgent) |
||
479 | { |
||
480 | // Build query with default table |
||
481 | $query = 'SELECT t2.browser_id, t2.browser_parent_id FROM '; |
||
482 | $query .= '('; |
||
483 | $query .= 'SELECT browser_id '; |
||
484 | $query .= 'FROM search '; |
||
485 | $query .= 'WHERE :agent GLOB browser_pattern '; |
||
486 | $query .= 'ORDER BY browser_pattern_length DESC, browser_id '; |
||
487 | $query .= 'LIMIT 1'; |
||
488 | $query .= ') t1 '; |
||
489 | $query .= 'JOIN browser t2 ON t2.browser_id = t1.browser_id'; |
||
490 | $statement = $this->getAdapter()->prepare($query); |
||
491 | |||
492 | /** @noinspection PdoApiUsageInspection */ |
||
493 | foreach ($statement->execute(['agent' => strtolower($userAgent)]) as $row) { |
||
494 | $this->browserId = (int)$row['browser_id']; |
||
495 | $this->browserParentId = (int)$row['browser_parent_id']; |
||
496 | } |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * @return array |
||
501 | * @throws ParserConditionNotSatisfiedException |
||
502 | * @throws ParserRuntimeException |
||
503 | * @throws UnexpectedValueException |
||
504 | */ |
||
505 | protected function getPatternKeywords() : array |
||
506 | { |
||
507 | if ($this->browserPatternKeywords === null) { |
||
508 | $this->browserPatternKeywords = []; |
||
509 | $query = 'SELECT keyword_value FROM keyword ORDER BY keyword_id'; |
||
510 | foreach ($this->getAdapter()->query($query) as $row) { |
||
511 | $this->browserPatternKeywords[] = $row['keyword_value']; |
||
512 | } |
||
513 | } |
||
514 | |||
515 | return $this->browserPatternKeywords; |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * @param array $properties |
||
520 | * |
||
521 | * @return array |
||
522 | */ |
||
523 | protected function sortProperties(array $properties) : array |
||
524 | { |
||
525 | ksort($properties); |
||
526 | |||
527 | return $properties; |
||
528 | } |
||
529 | } |
||
530 |
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.