Completed
Pull Request — master (#2282)
by ྅༻ Ǭɀħ
01:46
created

YDB::get_row()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Aura SQL wrapper for YOURLS that creates the allmighty YDB object.
5
 *
6
 * A fine example of a "class that knows too much" (see https://en.wikipedia.org/wiki/God_object)
7
 *
8
 * Note to plugin authors: you most likely SHOULD NOT use directly methods and properties of this class. Use instead
9
 * function wrappers (eg don't use $ydb->option, or $ydb->set_option(), use yourls_*_options() functions instead).
10
 *
11
 * @since 1.7.3
12
 */
13
14
namespace YOURLS\Database;
15
16
use YOURLS\Admin\Logger;
17
use Aura\Sql\ExtendedPdo;
18
use PDO;
19
20
class YDB extends ExtendedPdo {
21
22
    /**
23
     * Debug mode, default false
24
     * @var bool
25
     */
26
    protected $debug = false;
27
28
    /**
29
     * Page context (ie "infos", "bookmark", "plugins"...)
30
     * @var string
31
     */
32
    protected $context = '';
33
34
    /**
35
     * Information related to a short URL keyword (eg timestamp, long URL, ...)
36
     *
37
     * @var array
38
     *
39
     */
40
    protected $infos = array();
41
42
    /**
43
     * Is YOURLS installed and ready to run?
44
     * @var bool
45
     */
46
    protected $installed = false;
47
48
    /**
49
     * Options
50
     * @var array
51
     */
52
    protected $option = array();
53
54
    /**
55
     * Plugin admin pages informations
56
     * @var array
57
     */
58
    protected $plugin_pages = array();
59
60
    /**
61
     * Plugin informations
62
     * @var array
63
     */
64
    protected $plugins = array();
65
66
    /**
67
     * Class constructor
68
     *
69
     * Don't forget to end with a call to the parent constructor
70
     *
71
     * @since 1.7.3
72
     * @param string $dsn         The data source name
73
     * @param string $user        The username
74
     * @param string $pass        The password
75
     * @param array  $options     Driver-specific options
76
     * @param array  $attributes  Attributes to set after a connection
77
     */
78
    public function __construct($dsn, $user, $pass, $options, $attributes) {
79
        parent::__construct($dsn, $user, $pass, $options, $attributes);
80
81
        // Log query infos
82
        $this->start_profiler();
83
    }
84
85
    /**
86
     * Initiate real connection to DB server
87
     *
88
     * This is to check that the server is running and/or the config is OK
89
     *
90
     * @since  1.7.3
91
     * @return void
92
     */
93
    public function connect_to_DB() {
94
        try {
95
            $this->connect();
96
        } catch ( \Exception $e ) {
97
            // Use any /user/db_error.php file
98
            if( file_exists( YOURLS_USERDIR . '/db_error.php' ) ) {
99
                include_once( YOURLS_USERDIR . '/db_error.php' );
100
                die();
101
            }
102
103
            $message  = yourls__( 'Incorrect DB config, or could not connect to DB' );
104
            $message .= '<br/>' . get_class($e) .': ' . $e->getMessage();
105
106
            yourls_die( yourls__( $message ), yourls__( 'Fatal error' ), 503 );
107
            die();
108
        }
109
    }
110
111
    /**
112
     * Start a Message Logger
113
     *
114
     * @since  1.7.3
115
     * @see    \YOURLS\Admin\Logger
116
     * @see    \Aura\Sql\Profiler
117
     * @return void
118
     */
119
    public function start_profiler() {
120
        $this->profiler = new Logger($this);
121
    }
122
123
    /**
124
     * @param string $context
125
     */
126
    public function set_html_context($context) {
127
        $this->context = $context;
128
    }
129
130
    /**
131
     * @return string
132
     */
133
    public function get_html_context() {
134
        return $this->context;
135
    }
136
137
    // Options low level functions, see \YOURLS\Database\Options
138
139
    /**
140
     * @param string $name
141
     * @param mixed  $value
142
     */
143
    public function set_option($name, $value) {
144
        $this->option[$name] = $value;
145
    }
146
147
    /**
148
     * @param  string $name
149
     * @return bool
150
     */
151
    public function has_option($name) {
152
        return array_key_exists($name, $this->option);
153
    }
154
155
    /**
156
     * @param  string $name
157
     * @return string
158
     */
159
    public function get_option($name) {
160
        return $this->option[$name];
161
    }
162
163
    /**
164
     * @param string $name
165
     */
166
    public function delete_option($name) {
167
        unset($this->option[$name]);
168
    }
169
170
171
    // Infos (related to keyword) low level functions
172
173
    /**
174
     * @param string $keyword
175
     * @param mixed  $infos
176
     */
177
    public function set_infos($keyword, $infos) {
178
        $this->infos[$keyword] = $infos;
179
    }
180
181
    /**
182
     * @param  string $keyword
183
     * @return bool
184
     */
185
    public function has_infos($keyword) {
186
        return array_key_exists($keyword, $this->infos);
187
    }
188
189
    /**
190
     * @param  string $keyword
191
     * @return array
192
     */
193
    public function get_infos($keyword) {
194
        return $this->infos[$keyword];
195
    }
196
197
    /**
198
     * @param string $keyword
199
     */
200
    public function delete_infos($keyword) {
201
        unset($this->infos[$keyword]);
202
    }
203
204
    /**
205
     * @todo: infos & options are working the same way here. Abstract this.
206
     */
207
208
209
    // Plugin low level functions, see functions-plugins.php
210
211
    /**
212
     * @return array
213
     */
214
    public function get_plugins() {
215
        return $this->plugins;
216
    }
217
218
    /**
219
     * @param array $plugins
220
     */
221
    public function set_plugins(array $plugins) {
222
        $this->plugins = $plugins;
223
    }
224
225
    /**
226
     * @param string $plugin  plugin filename
227
     */
228
    public function add_plugin($plugin) {
229
        $this->plugins[] = $plugin;
230
    }
231
232
    /**
233
     * @param string $plugin  plugin filename
234
     */
235
    public function remove_plugin($plugin) {
236
        unset($this->plugins[$plugin]);
237
    }
238
239
240
    // Plugin Pages low level functions, see functions-plugins.php
241
242
    /**
243
     * @return array
244
     */
245
    public function get_plugin_pages() {
246
        return $this->plugin_pages;
247
    }
248
249
    /**
250
     * @param array $pages
251
     */
252
    public function set_plugin_pages(array $pages) {
253
        $this->plugin_pages = $pages;
254
    }
255
256
    /**
257
     * @param string   $slug
258
     * @param string   $title
259
     * @param callable $function
260
     */
261
    public function add_plugin_page($slug, $title, $function) {
262
        $this->plugin_pages[$slug] = array(
263
            'slug'     => $slug,
264
            'title'    => $title,
265
            'function' => $function,
266
        );
267
    }
268
269
    /**
270
     * @param string $slug
271
     */
272
    public function remove_plugin_page($slug) {
273
        unset($this->plugin_pages[$slug]);
274
    }
275
276
277
    /**
278
     * Return count of SQL queries performed
279
     *
280
     * @since  1.7.3
281
     * @return int
282
     */
283
    public function get_num_queries() {
284
        return count( (array) $this->get_queries() );
285
    }
286
287
    /**
288
     * Return SQL queries performed
289
     *
290
     * Aura\Sql\Profiler logs every PDO command issued. But depending on PDO::ATTR_EMULATE_PREPARES, some are
291
     * actually sent to the mysql server or not :
292
     *  - if PDO::ATTR_EMULATE_PREPARES is true, prepare() statements are not sent to the server and are performed
293
     *    internally, so they are removed from the logger
294
     *  - if PDO::ATTR_EMULATE_PREPARES is false, prepare() statements are actually performed by the mysql server,
295
     *    and count as an actual query
296
     *
297
     * Resulting array is something like:
298
     *   array (
299
     *      0 => array (
300
     *           'duration' => 1.0010569095611572265625,
301
     *           'function' => 'connect',
302
     *           'statement' => NULL,
303
     *           'bind_values' => array (),
304
     *           'trace' => ...back trace...,
305
     *       ),
306
     *       // key index might not be sequential if 'prepare' function are filtered out
307
     *       2 => array (
308
     *           'duration' => 0.000999927520751953125,
309
     *           'function' => 'perform',
310
     *           'statement' => 'SELECT option_value FROM yourls_options WHERE option_name = :option_name LIMIT 1',
311
     *           'bind_values' => array ( 'option_name' => 'test_option' ),
312
     *           'trace' => ...back trace...,
313
     *       ),
314
     *   );
315
     *
316
     * @since  1.7.3
317
     * @return array
318
     */
319
    public function get_queries() {
320
        $queries = $this->getProfiler()->getProfiles();
321
322
        if ($this->getAttribute(PDO::ATTR_EMULATE_PREPARES)) {
323
            // keep queries if $query['function'] != 'prepare'
324
            $queries = array_filter($queries, function($query) {return $query['function'] !== 'prepare';});
325
        }
326
327
        return $queries;
328
    }
329
330
    /**
331
     * Set YOURLS installed state
332
     *
333
     * @since  1.7.3
334
     * @param  bool $bool
335
     * @return void
336
     */
337
    public function set_installed($bool) {
338
        $this->installed = $bool;
339
    }
340
341
    /**
342
     * Get YOURLS installed state
343
     *
344
     * @since  1.7.3
345
     * @return bool
346
     */
347
    public function is_installed() {
348
        return $this->installed;
349
    }
350
351
    /**
352
     * Return standardized DB version
353
     *
354
     * The regex removes everything that's not a number at the start of the string, or remove anything that's not a number and what
355
     * follows after that.
356
     *   'omgmysql-5.5-ubuntu-4.20' => '5.5'
357
     *   'mysql5.5-ubuntu-4.20'     => '5.5'
358
     *   '5.5-ubuntu-4.20'          => '5.5'
359
     *   '5.5-beta2'                => '5.5'
360
     *   '5.5'                      => '5.5'
361
     *
362
     * @since  1.7.3
363
     * @return string
364
     */
365
    public function mysql_version() {
366
        $version = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
367
        return $version;
368
    }
369
370
    /**
371
     * Deprecated properties since 1.7.3, unused in 3rd party plugins as far as I know
372
     *
373
     * $ydb->DB_driver
374
     * $ydb->captured_errors
375
     * $ydb->dbh
376
     * $ydb->result
377
     * $ydb->rows_affected
378
     * $ydb->show_errors
379
     */
380
381
    /**
382
     * Deprecated functions since 1.7.3
383
     */
384
385
    // @codeCoverageIgnoreStart
386
387
    public function escape($string) {
388
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
389
        // This will escape using PDO->quote(), but then remove the enclosing quotes
390
        return substr($this->quote($string), 1, -1);
391
    }
392
393
    public function get_col($query) {
394
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
395
        yourls_debug_log('LEGACY SQL: '.$query);
396
        return $this->fetchCol($query);
397
    }
398
399
    public function get_results($query) {
400
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
401
        yourls_debug_log('LEGACY SQL: '.$query);
402
        $stm = parent::query($query);
0 ignored issues
show
Comprehensibility Bug introduced by ozh
It seems like you call parent on a different method (query() instead of get_results()). Are you sure this is correct? If so, you might want to change this to $this->query().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
403
        return($stm->fetchAll(PDO::FETCH_OBJ));
404
    }
405
406
    public function get_row($query) {
407
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
408
        yourls_debug_log('LEGACY SQL: '.$query);
409
        $row = $this->fetchObjects($query);
410
        return isset($row[0]) ? $row[0] : null;
411
    }
412
413
    public function get_var($query) {
414
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
415
        yourls_debug_log('LEGACY SQL: '.$query);
416
        return $this->fetchValue($query);
417
    }
418
419
    public function query($query) {
420
        yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
421
        yourls_debug_log('LEGACY SQL: '.$query);
422
        return $this->fetchAffected($query);
0 ignored issues
show
Bug Best Practice introduced by ozh
The return type of return $this->fetchAffected($query); (integer) is incompatible with the return type declared by the interface Aura\Sql\PdoInterface::query of type PDOStatement.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
423
    }
424
    // @codeCoverageIgnoreEnd
425
}
426